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. |