Diablo style item spawning
BlitzMax Forums/BlitzMax Programming/Diablo style item spawning
| ||
| 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. |
| ||
| 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. |
| ||
| 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. |
| ||
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 :( |
| ||
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
|
| ||
| 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 |
| ||
| 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. |
| ||
| 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 |
| ||
| 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. :) |
| ||
| 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. |
| ||
| 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 |
| ||
| 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 |
| ||
| Fanstastic, I wan't expecting this much attention on it, thanks for the code guys, I'll test it in the game :D |
| ||
| 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. ? |
| ||
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
|
| ||
| 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... |
| ||
| 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. |
| ||
| I use Chroma's method for my games. |