Diablo style item spawning

BlitzMax Forums/BlitzMax Programming/Diablo style item spawning

verfum(Posted 2007) [#1]
Hi there, I'm battling different methods of spawning items from an object when either you make contact, or kill an enemy, very similar to Diablo, but I can't get the random item creating side of it working, well, I cant even begin to figure it out, it needs to be a method which also looks at the rarity of the items, so for example, we create an array of 1000 you then populate that array with random items based on rare values, so more health and mana items would be in the array than rings or rare weapons, from there we pick 5 random items out of that array and spawn them, or create them so to speak. But I cant get my head around it, does anyone know of any sample code or examples that could put me on the right track? Thank you.


tonyg(Posted 2007) [#2]
If you have 3 items : Gem, Potion and Weapon.
Gems are given 60% of the time
Potions are given 30% of the time
Weapons are given 10% of the time.
x=Randomise (1,100)
If x>10 then give weapon
elseif X< 10 and > 30 then give potion
else give gem.
I think you could give ranges for select statements which would make it more readable.


North(Posted 2007) [#3]
You need

a table of all items and their stats
an algorithm to identify possible loot for the current player level

you can enhance this with

subsets of items for certain zones, monster types
giving certain npcs(monsters) a fixed table of possible loot specific to that npc
add a variable amount of gold separately to the looting algorithm
etc etc etc

I'd just use a "loot-array" consisting of index numbers which point to the big item-table for every monster type.
If the monster types raise in level like in diablo you need to take that into account and prepare the npc-specific loot-array accordingly.


verfum(Posted 2007) [#4]
Yikes! This is exactly the part I'm having trouble doing though :( I know that I need this it's just actually doing it, my head hurts just thinking about it! Problem is I haven't done this before, so I'm sure where to start. I began with this:
Strict

Graphics 640,480

SeedRnd MilliSecs()
Global items:Int[1000]

Get_items()



While Not KeyHit(KEY_ESCAPE)

	Cls

	Local count:Int=0

	While count<20

		DrawText ("Purple = wood",20,20)
		DrawText ("Yellow = silver",20,40)
		DrawText ("Red    = gold",20,60)

		If items[count]=1 Then SetColor 255,0,0
		If items[count]=2 Then SetColor 255,255,0
		If items[count]=3 Then SetColor 255,0,255

	        DrawRect ((10*count),100,10,10)
		count = count +1


	Wend

	Flip

Wend


Function Get_items()

	Local count:Int = 0

	Local random_value:Int

	'This generates an array that has 100 1's in it, 200 2's and 700 3's. So 1 
	'is rarer than 2 in the array

	While count<1000

		random_value = Rnd(0,1000)

		If random_value >   0  And random_value <   50   Then items[count]=1
		If random_value >= 50  And random_value <  400   Then items[count]=2
		If random_value >= 400 And random_value <= 1000  Then items[count]=3

     		count=count+1

	Wend

End Function

Thats as far as I got, naturally this has no actualy object creating, it's like a sketch example of what needs doing, but technically I cant seem to grasp it, what I need is a clever way of spawning items which are extended from an Item Type. I'm getting dizzy :(


klepto2(Posted 2007) [#5]
Maybe this helps you a bit. It is thrown together very fast and I don't have much time to explain it currently. But take a look:

Type TItem
	Field Name:String
	Field Multiplier:Float
	Field FireDmg:Float
	Field IceDmg:Float
	
	Field Class:String
	Field AttackMin:Int
	Field AttackMAx:Int
End Type


Type TMultiplier
	Global Map:TMap = CreateMap()
	Global List:TList = New TList
	
	Field Multi:Float
	Field Name:String
	
	Function Add(Name:String,Multi:Float,chance:Int=1)
		Local M:TMultiplier = New TMultiplier
		M.Multi = Multi
		M.Name = Name
		
		Map.Insert(Name,M)
		
		For Local I:Int = 0 To Chance-1
			List.addlast(M.Name)
		Next
	End Function
	
	Function Get:TMultiplier()
		Local I:Int = Rand(0,CountList(TMultiplier.List)-1)
		Return TMultiplier(MapValueForKey(TMultiplier.Map,String(TMultiplier.List.ValueatIndex(I))))
	End Function
End Type

Type TClass
	Global Map:TMap = CreateMap()
	Global List:TList = New TList
	
	
	Field Name:String
	
	Function Add(Name:String,chance:Int=1)
		Local M:TClass = New TClass
		M.Name = Name
		
		Map.Insert(Name,M)
		
		For Local I:Int = 0 To Chance-1
			List.addlast(M.Name)
		Next
	End Function
	
	Function Get:TClass()
		Local I:Int = Rand(0,CountList(TClass.List)-1)
		Return TClass(MapValueForKey(TClass.Map,String(TClass.List.ValueatIndex(I))))
	End Function
End Type

Type TExtra
	Global Map:TMap = CreateMap()
	Global List:TList = New TList
	
	Field Name:String
	
	Function Add(Name:String,chance:Int=1)
		Local M:TExtra= New TExtra
		M.Name = Name
		
		Map.Insert(Name,M)
		
		For Local I:Int = 0 To Chance-1
			List.addlast(M.Name)
		Next
	End Function
	
	Function Get:TExtra()
		Local I:Int = Rand(0,CountList(TEXtra.List)-1)
		Return TExtra(MapValueForKey(TExtra.Map,String(TExtra.List.ValueatIndex(I))))
	End Function
End Type





TMultiplier.Add("Rare",5.0,1.0)
TMultiplier.Add("Regular",2.5,4.0)
TMultiplier.Add("Bad",1.0,10.0)

TClass.Add("Sword",1.0)
TClass.Add("Axe",1.0)

TExtra.Add("Fire",5.0)
TExtra.Add("Ice",5.0)
TExtra.Add("the Destructor",1.0)


For Local I:Int = 0 To 99
	Local M:TMultiplier = TMultiplier.Get()
	Local C:TClass = TClass.Get()
	Local E:TExtra = TExtra.Get()
	Print M.Name + " " + C.Name + " of " + E.Name
Next



verfum(Posted 2007) [#6]
Hey that's some pretty smart stuff, a few alien things in there, I'm not familiar with Tmaps, but I'll study it, thanks klepto.

btw. you threw that together :O


Chroma(Posted 2007) [#7]
I think what you're saying is you want each monster to have a loot table. And then have him drop a certain number of items from his loot table based on the specific drop rate of each item. For that I would use a weighted numbers system.

Here's the monster loot table:
nothing = 1
gold = 2
gem = 3
sword = 4
potion = 5

Here's the drop chance for each item:
MonsterLoot:String = "1111222345"

Here you actually see what dropped:
item:Int = Rand( Len( MonsterLootTable ) )

This gives the following drop rates:
nothing 40%
gold 30%
gem 10%
sword 10%
potion 10%

At least that's how I would do it. This way each monster can have it's own custom loot table and you can have custom drop rates for each item too.


Chroma(Posted 2007) [#8]
Here's some code I whipped up. It isn't complete but you can see how you could have a creature spawn with a random loot table already populated. Which would be cool because you could then have the monster running around showing a sword that he's actually carrying.

First you would create a zone. The zone is then populated with monsters using the zone.AddMonster() command. Each monster would be carrying around the loot with him. The player kills the monster, then you access the dead monsters lootlist to see what he was carrying.


Local DeadlyForest:TZone = New TZone
DeadlyForest.AddMonster( 2 )

Type TZone

	Field monsterList:TList = New TList
	
	Method AddMonster( monID:Int )
		self.monsterList.AddLast( TMonster.Create( monID ) )
	End Method

End Type



Type TMonster

	Field id:Int
	Field name:String
	Field maxdrops:Int
	Field lootList:TList = New lootList

	Method Create:TMonster( _id:Int )
		Local monster:TMonster = New TMonster

		Select _id
			
			Case 1	'Goblin
				monster.name = "a goblin"
				monster.maxdrops = 1
				monster.PopulateLootList( "1=60%, 8=30%, 322=10%" )
				
			Case 2	'Whispering Ghost
				monster.name = "a whispering ghost"
				monster.maxdrops = 3
				monster.PopulateLootList( "1=40%, 15=30%, 322=10%, 99882=20%" )
			
			Case 3	'Small Red Dragon		
				monster.name = "a small red dragon"
				monster.maxdrops = 6
				monster.PopulateLootList( "1=20%, 66=30%, 321=10%, 3542=10%, 28374=10%, 382333=20%" )

		End Select
	End Method

	Method PopulateLootList( lootKey:String )

		Local i:Int
		Local tempItem:TItem

		'Build Loot Key String


		'Populate the Monster Loot List
		For i = 1 To monster.maxdrops
			Local num:Int = Rand(100)
			tempItem = TItem.Get( num )
			If tempItem <> Null Then self.MonsterLootList.AddLast( tempItem )
		Next
	End Method

End Type


Type TItem
	Field id:Int
	Field name:Int

	Method Get:TItem( _id:Int )
		Local item:TItem
		For item = EachIn MasterItemList
			If item.id = _id Then Return item
		Next
	End Method
End Type



deps(Posted 2007) [#9]
Chroma, nice idea there with the loot table! I must remember it.

For the code there, I would remove the select _id thing and just create a bunch more types, inheriting stuff from TMonster:

Type TMonster
  ' ...
endtype

Type TGoblin extends TMonster
  method new()
    name = "Butt-Ugly goblin guard"
    maxdrops = 1
    ' ...
  endmethod
endtype

Type TOgre extends TMonster
  method new()
    name = "Smelly Ogre with stick"
    ' and so on...
  endmethod
endtype

Perhaps you already knew this, but anyway. :)


Chroma(Posted 2007) [#10]
Peculiar. I'm not too familiar with the Extends command. But I'm willing to listen. :)

So....you make a type for each race of monster and can set fields that are present in the TMonster type.


deps(Posted 2007) [#11]
Types makes everything much more pretty and simple, and extending types makes stuff even more easy and pretty. :)

Everything inherits from TMonster. All fields and methods is available in sub types.
Fields and methods that appear in subtypes cannot be accessed from parent types.
Type TMonster
  field name:string
  field hp:int
  field dmg:int

  method attack( other:TMonster )
    print name + " attacks " + other.name
    other.get_hurt( dmg )
  endmethod

  method get_hurt( d:int )
    hp:-d
    print name + " yells ow when hurt by "+d+"points! HP is reduced to "+hp
  endmethod
endtype

type TOgre extends TMonster
  method new()
    name = "Ogre"
    hp = 10
    dmg = 2
  endmethod
endtype

type TDragon extends TMonster
  method new()
    name = "Red dragon"
    hp = 10000
    dmg = 2000
  endmethod

  method fly()
    print "*flaps wings*"
  endmethod
endtype

d:TMonster = new TDragon
o:TMonster = new TOgre

o.attack( d )
d.attack( o )

d.fly() ' Not certain you can do this, you might need to do it like this instead: TDragon(d).fly()   
' to cast it to the correct type. (It's a TMonster, but a TDragon behind the scenes)

This is a really really good tutorial: http://blitzmax.com/Community/posts.php?topic=42519


dmaz(Posted 2007) [#12]
while extending is great I disagree with deps usage. you don't what to extend to a specific usage like "type TDragon extends TMonster"... you will find that won't play out very well later in your design. think about this... if you want a random monster to appear, how do you want to code it?

a=rnd(10)
select a
case 1 d:TMonster = new TDragon
case 2 d:TMonster = new Torge
case 3 d:....
(also I think it's better not to get in the habit of downcasting unless you really need to.)

or

d:TMonster = TMonster.create(rnd(10))



basically if you are just changing data there is no reason to extend. the fly() method of TDragon is a good reason because it changes the capabilities of the type. But I would do something along the lines of
TFlyingMonster extends TMonster


verfum(Posted 2007) [#13]
Fanstastic, I wan't expecting this much attention on it, thanks for the code guys, I'll test it in the game :D


QuietBloke(Posted 2007) [#14]
dmaz,

just me being thick I suspect but...
Im not sure I follow your reasoning on why you shouldnt extend to specific monsters ?
I cant see why your argument would work... you say to create a random monster you would need a select/case to create each type of monster but surely if instead you just had an id as in Chroma's code you would still need a select/case on the id to populate the monster attributes.

?


Chroma(Posted 2007) [#15]
Here's some code that will create a spawn points in the same style as EQ. You can set a list of monsters and what the probabilities are for each to spawn. Again, the spawnKey parser isn't written but it wouldn't be hard to do so.

'Zone and Spawnpoints similar to EverQuest

Graphics 800,600

'CurrentZone is always the zone the player is in
Global CurrentZone:TZone = TZone.Load("BlackForest.zone")
CurrentZone.AddSpawnPoint( 100,200,20,"223=80%, 496=20%", 60*5 )
'I use 60*5 to specify a five minute respawn time

'This sets an 80% chance for monster number 223 to spawn 
'And a 20% chance For monster number 496 To spawn
'I would have monster 223 be a goblin and monster 496 be The Goblin King
'This way he is semi-rare and you can have him drop a really cool weapon, etc etc

'******************************************************************

While Not KeyDown(KEY_ESCAPE)
Cls

'CurrentZone.Logic()

Flip
Wend
End

'******************************************************************

Type TZone
	Field PlayerList:TList = New TList			'Everyone who's in this zone with you
	Field SpawnPointList:TList = New TList		'All spawn points in this zone
	Field MonsterList:TList = New TList			'All monsters in this zone
	
	Function Load:TZone( thisZoneFile:String)
		Local zone:TZone = New TZone
		'Load in all the spawn points
		'self.spawnList.AddList( spawnpoint )
		Return zone
	End Function


	Method AddSpawnPoint( xpos:Int, ypos:Int, zpos:Int, spawnKey:String, spawnFreq:Int )
		Local spawnpoint:TSpawnPoint
		spawnpoint.x = xpos
		spawnpoint.y = ypos
		spawnpoint.z = zpos
		spawnpoint.spawnKey = _spawnKey
		spawnpoint.spawnFreq = _spawnFreq
		self.SpawnPointList.AddLast( spawnpoint )
	End Method


	Method Logic()
		'Update spawn points
		Local spawnpoint:TSpawnPoint
		For spawnpoint = EachIn self.SpawnPointList
			spawnpoint.logic()
		Next
	End Method
End Type

'******************************************************************


Type TSpawnPoint
	Field x:Int, y:Int, z:Int	'coordinates in 3D space of the spawn point
	Field spawnTrigger:Int		'triggers if the monster from this spawn point dies
	Field spawnFreq:Int			'new monster spawns after this time
	Field spawnTimer:Float		'spawn timer to trigger a new monster 
	Field spawnKey:String		

	Method AddMonster()
	
	End Method

	
	Method Logic()
		If self.spawnTrigger = True
			self.spawnTimer :+ dt
			If self.spawnTimer > spawnFreq
				self.spawnTrigger = False
				self.spawnTimer = 0
				self.AddMonster()
			EndIf
		EndIf
	End Method

End Type

'******************************************************************


Type TMonster
	Field id:Int
	Field name:String
	Field spawnedFrom:TSpawnPoint
	
	Method Died()
		ResetSpawnPoint( spawnedFrom )
	End Method
End Type

'******************************************************************


Function ResetSpawnPoint( tempSpawnPoint:TSpawnPoint )
	Local spawnpoint:TSpawnPoint
	For spawnpoint = EachIn CurrentZone.SpawnPointList
		If spawnpoint = tempSpawnPoint
			spawnpoint.spawnTrigger = True
			Return 0
		EndIf
	Next
End Function



dmaz(Posted 2007) [#16]
Im not sure I follow your reasoning on why you shouldnt extend to specific monsters ?
you know, it looks good on the surface and in the code but I think you will learn it's not very practical at that level.

say you had 50 different monster types... that select case is going to be huge and very unreadable. also, what if you didn't want your monster type hard coded, say you wanted them in non compiled external config files? How are you going to "new" that type?.... again you would have 50 case statements in that select case while I would not have any. what I would do is have a base class called TCharacter that TPlayer,TMonster,TNPC... extend from. then for TMonster and TNPC I would just use data statements to define them. for "Update"ing of a specific TMonster or TNPC type, it would be as simple as having function pointers defined inside those data types. With this I can easily add a whole new monster by adding a data statement or modifying the config file. Even better, defining the new monster in an external editor like the map editor...


QuietBloke(Posted 2007) [#17]
ahhh I see.. I think..
so with your method you may have 10 monsters with thier default stats held in external config files. When you create the monster it populates the monster stats from the config file.
Then specific game logic like a simple AI .. you might have a guard which might tend to stay close to whatever it is guarding and will only attack when provoked that code will be in a specific function. you might also have a passive monster, aggresive etc... and each of those would be a seperate function for the monster type. When you create a monster the config file would also specify which function it should use....

I guess you could then extend the whole thing so instead of a hardcoded function it would point to a specific Lua script.

is that right ?

I suppose Im so used to just writing hard coded stuff where what you see is what you get I never really think about it in terms of customisation in the future.


Grey Alien(Posted 2007) [#18]
I use Chroma's method for my games.