wheybags / freeablo

[ARCHIVED] Modern reimplementation of the Diablo 1 game engine
GNU General Public License v3.0
2.16k stars 195 forks source link

Sample data for combat tests #488

Open wheybags opened 4 years ago

wheybags commented 4 years ago

We need some sample data from the original game to verify our combat calculatiosn against. I have implemented melee combat in accordance with the formulae from Jarulfs' guide, and from what I can tell they should be correct. However, it still feels to me like there is something off when attacking monsters in the first dungeon level, sometimes they seem to survive too long.

It would be useful to have some test data from the original game (ie, with this gear, hit this enemy, and then measure the damage dealt). Ideally we could rig the RNG to always do the max damage in range, never miss etc, to make it easier to compare.

We could use this sample data for automated tests.

wheybags commented 4 years ago

ping @AJenbo I know it's probably a bunch of work, but given your involvement with the devilution project, is there any chance you would be interested in gathering some data for this? No pressure :)

wheybags commented 4 years ago

To start with just some values for a level 1 warrior in default gear hitting a zombie would be enough.

AJenbo commented 4 years ago

warrior in default gear hitting a zombie

RNG locked to 0 (because zero is normally success)

INFO: MonsterTrapHit: monster -64
INFO: PlrHitMonst: monster -128
INFO: M_StartKill: Zombie
INFO: PlrHitMonst: monster -128
INFO: M_StartKill: Zombie
INFO: PlrHitMonst: monster -128
INFO: M_StartKill: Zombie
INFO: PlrHitMonst: monster -128
INFO: M_StartKill: Zombie
--- At this point I got stunlocked by the zombies because they always hit
INFO: M_TryH2HHit: player -35
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -31
INFO: M_TryH2HHit: player -8
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -31
INFO: M_TryH2HHit: player -8
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -29
INFO: M_TryH2HHit: player -31
INFO: M_TryH2HHit: player -8
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -27
INFO: M_TryH2HHit: player -31
INFO: M_TryH2HHit: player -8
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -31
INFO: M_TryH2HHit: player -29
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -35
INFO: M_TryH2HHit: player -31
INFO: M_TryH2HHit: player -8
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -31
INFO: M_TryH2HHit: player -27
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -32765
INFO: M_TryH2HHit: player -31
INFO: M_TryH2HHit: player -8
--- palyer dead

Note that health is health << 6 internally so that you can take fractional damage.

Some fighting with RNG enabled:

INFO: PlrHitMonst: monster -192
INFO: M_StartKill: Skeleton Captain
INFO: PlrHitMonst: monster -64
INFO: PlrHitMonst: monster -192
INFO: M_StartKill: Skeleton
INFO: PlrHitMonst: monster -448
INFO: M_StartKill: Scavenger
INFO: PlrHitMonst: monster -192
INFO: M_StartKill: Skeleton
INFO: PlrHitMonst: monster -576
INFO: M_StartKill: Fallen One
INFO: PlrHitMonst: monster -64
INFO: PlrHitMonst: monster -128
INFO: M_StartKill: Skeleton
INFO: PlrHitMonst: monster -192
INFO: M_StartKill: Skeleton
INFO: PlrHitMonst: monster -64
INFO: PlrHitMonst: monster -192
INFO: M_StartKill: Zombie
INFO: PlrHitMonst: monster -64
INFO: M_TryH2HHit: player -35
INFO: PlrHitMonst: monster -128
INFO: M_StartKill: Zombie
INFO: PlrHitMonst: monster -192
INFO: M_StartKill: Zombie
INFO: PlrHitMonst: monster -448
INFO: M_StartKill: Fallen One
INFO: PlrHitMonst: monster -128
INFO: M_StartKill: Skeleton
INFO: MonsterTrapHit: monster -128
INFO: M_StartKill: Fallen One
INFO: PlayerMHit: player -728
INFO: PlrHitMonst: monster -192
INFO: M_StartKill: Scavenger
INFO: PlrHitMonst: monster -128
INFO: M_StartKill: Skeleton
INFO: PlrHitMonst: monster -192
INFO: M_StartKill: Fallen One
INFO: PlrHitMonst: monster -448
INFO: M_StartKill: Fallen One
INFO: PlrHitMonst: monster -192
INFO: M_StartKill: Skeleton Captain
INFO: M_TryH2HHit: player -28
INFO: M_TryH2HHit: player -28
INFO: PlrHitMonst: monster -192
INFO: M_StartKill: Scavenger
INFO: PlrHitMonst: monster -192
INFO: M_StartKill: Skeleton
INFO: PlrHitMonst: monster -256
INFO: M_StartKill: Scavenger
INFO: PlrHitMonst: monster -256
INFO: M_StartKill: Fallen One

I'm making sure to only fight one monster at a time, all the hits will be on the monster that subsequently appears with the M_StartKill line.

I do at times get attacked by more than one mosnter so you won't be able to see exactly who dealt the damage to the player.

*: player -d The player was hit *: monster -d A monter was hit M_StartKill: * A monter played it death animation

AJenbo commented 4 years ago

Also note that RNG in Diablo is biased.

wheybags commented 4 years ago

Nice, thanks a lot! When you say the RNG is biased, do you mean they do the modulo trick for generating a number in a range? (so, eg, rand(100) = rand() % 101) Or is it something deeper in their RNG implementation? I'm hoping we can just not replicate that defect, do you think it's significant to gameplay?

AJenbo commented 4 years ago

It does affect gameplay a bit, but mostly in a negative way (streaks are more common then they should). If you aim to have comparability with save game files you will need to implement the RNG from Diablo (Borland C/C++) or items will not be regenerated correctly, but you could limit it's use to just the item loading code.

wheybags commented 4 years ago

I probably won't bother with that level of import, but a character stats / inventory importer is definitely on the todo list.

AJenbo commented 4 years ago

Well you will need it for inventory

wheybags commented 4 years ago

Oh, are items stored as an rng seed or something?

wheybags commented 4 years ago

Ok, so I did some tests with damage: We are currently handling damage as ints, so issue # 1 is that we need some fractional support. Apart from that, I tried running the algorithms from Jarulf's guide in a python interpreter, and didn't get results that match the data you posted:

>>> strength = 30
>>> player_level = 1
>>> weapon_dmg = (2,6)
>>> weapon_type_modifier = 0.5 # zombie is undead and weapon is sword https://wheybags.gitlab.io/jarulfs-guide/#how-to-calculate-monster-data
>>> 
>>> def rand_in_range(lo, hi):
...      return lo # hack for testing
...  
... 
>>> base_damage = (strength * player_level) / 100 # https://wheybags.gitlab.io/jarulfs-guide/#damage-done
>>> base_damage
0.3
>>> 
>>> damage_calc = base_damage + rand_in_range(weapon_dmg[0], weapon_dmg[1]) # https://wheybags.gitlab.io/jarulfs-guide/#player-versus-monster
>>> damage_calc
2.3
>>> 
>>> final_dmg = damage_calc * weapon_type_modifier
>>> final_dmg
1.15

Final damage calculation is 1.15, whereas your data indicates it should be 128 / (1 << 6) = 2.

Any idea what's wrong here?

AJenbo commented 4 years ago

Sorry, no clue about how to handle damage.

The first int in the item struct is it's seed value.

wheybags commented 4 years ago

Fair enough, thanks a lot for your help anyway!

wheybags commented 4 years ago

Ok, I did some more digging around, and it seems that while the hp variable for enemies is a fixedpoint value << 6, the damage calculations for melee attacks are actually done in unscaled ints anyway. Some samples, of a warrior in default gear attacking an undead: str 30, level 1: 2/128 str 50, level 4: 4/256

It seems freeablo is actually correct after all :p Still, it would be nice to get some tests in for this, I plan to add that soon.