Posts for lordash

Experienced Forum User
Joined: 4/29/2015
Posts: 8
ars4326 wrote:
Lordash, you mentioned that only doors represented by sprites can be hopped over. Do you know if the blocked path leading to Verla is represented by sprites or background?
In this case it is represented by both a sprite and a background change. The big rock that you see is a sprite (same as the big rock blocking the path between Gumi and Ryuma/Mercator), but the background has also been altered to block the door leading to Verla. Presumably this is to stop you from jumping from the top of the cliff through the door. You can verify this by using the game genie code DBAA-BAAC which allows you to float by tapping the jump button. Most sprites have a height of 3.5 - 4.5, but background changes are effective to a height of 64 (but the graphics will corrupt long before you reach this height).
Experienced Forum User
Joined: 4/29/2015
Posts: 8
I've made a few changes to your script: - Only enemies will be displayed (rather than any object) - Magic sword power is taken into account - There is now a damage done field as well as a HP field - I've made some small tweaks to the damage boost / RNG formula to better match the actual game code I've yet to work out whether the damage done field (including damage boost) is accurate, and at what time the damage boost is taken during an attack.
--lua script for gens rerecording+lua: http://code.google.com/p/gens-rerecording/
--purpose: HUD in Landstalker (U).
--written by Truncated 2015-05-03.
-- 

enemy_table = {[0x3C] = "BUBBLE1", [0x3D] = "BUBBLE2", [0x3E] = "BUBBLE3", [0x29] = "SKELETON1",[0x2A] = "SKELETON2",[0x2B] = "SKELETON3",
               [0x04] = "ORC1",    [0x05] = "ORC2",    [0x06] = "ORC3",    [0x07] = "WORM1",    [0x08] = "WORM2",    [0x09] = "WORM3",
			   [0x17] = "NINJA1",  [0x18] = "NINJA2",  [0x19] = "NINJA3",  [0x1A] = "LIZARD1",  [0x1B] = "LIZARD2",  [0x1C] = "LIZARD3",
			   [0x1D] = "SOLDIER1",[0x1E] = "SOLDIER2",[0x1F] = "SOLDIER3",[0x20] = "GHOST1",   [0x21] = "GHOST2",   [0x22] = "GHOST3",
			   [0x23] = "MUMMY1",  [0x24] = "MUMMY2",  [0x25] = "MUMMY3",  [0x26] = "UNICORN1", [0x27] = "UNICORN2", [0x28] = "UNICORN3",
			   [0x2C] = "MIMIC1",  [0x2D] = "MIMIC2",  [0x2E] = "MIMIC3",  [0x36] = "MUSHROOM1",[0x37] = "MUSHROOM2",[0x38] = "MUSHROOM3",
			   [0x39] = "GIANT1",  [0x3A] = "GIANT2",  [0x3B] = "GIANT3",  [0x81] = "BUBBLE4",  [0x82] = "BUBBLE5",  [0x83] = "BUBBLE6",
			   [0x7D] = "BIRD1",   [0x7E] = "BIRD2",   [0x7F] = "BIRD3",   [0x88] = "REAPER1",  [0x89] = "REAPER2",  [0x8A] = "REAPER3",
			   [0x8F] = "GHOSTGN1",[0x90] = "GHOSTGN2",[0x91] = "GHOSTGN3",[0x92] = "GOLEM1",   [0x93] = "GOLEM2",   [0x94] = "GOLEM3",
			   [0x95] = "SPECTRE1",[0x96] = "SPECTRE2",[0x97] = "SPECTRE3",[0x7C] = "MIR",      [0x70] = "DUKE",     [0x85] = "ZAK",
			   [0xA0] = "IFRIT",   [0x9D] = "SPINNER1",[0x9F] = "MIRO",    [0xA7] = "S.WARR1",  [0xAB] = "S.WARR2",  [0xAA] = "SPINNER2",
			   [0xA5] = "NOLE",	   [0xA2] = "GOLA"}

sword_modifiers = {1.75,1.50,2.00,1.25}


GetDecimalVal = function(val)
	frac = val % 0x100
	intg = math.floor(val / 0x100)
	frac = math.ceil(frac * 100 / 0x100) / 100
	return intg + frac
end

gui.register( function ()

	--bonus damage
	crit = memory.readwordunsigned(0xFF0FAA)
	crit = math.floor((math.floor(crit * 13 + 7) % 0x10000)*0x60)/0x10000
	crit = crit / 0x100 * 100
	message1 = string.format("DMG: %05.2f", crit) 
	gui.text(150, 184, message1.."%", "white", "black") 
	
	--Sword damage
	modifier = 1
	dmgcol = "white"
	sword = memory.readbyteunsigned(0xFF114E)
	chg = memory.readwordunsigned(0xFF120A)
	if sword > 0 and chg == 0x3200 then
		modifier = sword_modifiers[sword]
		dmgcol = "red"
	end
	
	--enemy HP
	start = 0xFF5480
	healthoff = 0x3E
	healthmaxoff = 0x7E
	enemytypeoff = 0x3B
	defoff = 0x7C
	nextoff = 0x80
	count = 0
	nigelhealth = GetDecimalVal(memory.readwordunsigned(0xFF5400 + healthmaxoff))
	for i = 0, 10 do
		enemytype = memory.readbyteunsigned(start + enemytypeoff + i*nextoff)
		if enemy_table[enemytype] ~= nil then
			--whole = memory.readbyteunsigned(start + i*offset)
			--frac = memory.readbyteunsigned(start + i*offset + 1) --/0x100*100
			--gui.text(100, 110+i*10, string.format("%d: %d.%02d", i, whole, frac), "white", "black") --gives rounding errors
			maxhealth = GetDecimalVal(memory.readwordunsigned(start + healthmaxoff + i*nextoff))
			health = GetDecimalVal(memory.readwordunsigned(start + healthoff + i*nextoff))
			def = GetDecimalVal(memory.readwordunsigned(start + defoff + i*nextoff))
			dmg = math.max(1, math.floor((nigelhealth - def*4)*100)/400)
			dmg = dmg + math.floor(dmg) * crit / 100
			dmg = dmg * modifier
			gui.text(100, 110+count*10, string.format("%9s: %4.2f / %4.2f", enemy_table[enemytype], health, maxhealth), "white", "black") 
			gui.text(220, 110+count*10, string.format("%4.2f", dmg), dmgcol, "black") 
			count = count + 1
		end
	end
	if count > 0 then
		gui.text(100, 100, "Enemy HP", "white", "black")
	end
end)

Experienced Forum User
Joined: 4/29/2015
Posts: 8
lordash wrote:
I haven't seen this happen with any other enemy, including the other bosses.
It turns out that this is not entirely true. All bosses that trigger script actions when they are defeated are killed when their HP falls below 100. This is true of Mir (actual HP 28), Duke (actual HP 68), Zak (actual HP 120), Spinner (actual HP 110) and Miro (actual HP 120), as well as Gola and Nole. This does not affect both the stone warriors, Ifrit and the purple spinner, whose HP is as given.
RatFunkZ wrote:
also i would liek a confrim deny about skiping over the heal boots gate using the money bag hop if possible not knowing is killing,( from on top of boots to over door?)
That won't work - you can only hop over locked doors that are represented by sprites. These doors all look the same (e.g. http://shrines.rpgclassics.com/genesis/landstalker/images/walkthroughpics/walk10/08.gif). The one in the stone warrior room is part of the actual background of the room and cannot be jumped over.
Experienced Forum User
Joined: 4/29/2015
Posts: 8
Truncated wrote:
But something is off here... - You say that Silver golems die from 11 hits at 100 HP. According to the chart it's between 9-12, so that seems right. - You say that King Nole dies from 9 hits at 100 HP. According to the chart it's between 16-23, which can't be right. I also checked at 84 HP, the TAS takes 15 hits, but the chart says 28-40. My guess is that the listed Defense 31 and HP 200 for King Nole is incorrect. What should it be? Gola isn't listed at all, so his (her?) stats would also be nice.
Gola's stats are 150HP and 31DEF. I can confirm that Nole's stats are accurate, however for some reason he dies when his remaining HP falls below 100. You can confirm this from looking at his health in RAM (0xFF55BE). This is also the same for Gola as well (0xFF553E). I'm not sure why this happens, but this effectively makes their HP 100 and 50 respectively. This seems to fit in with the recorded number of hits to kill them both. I haven't seen this happen with any other enemy, including the other bosses.
Experienced Forum User
Joined: 4/29/2015
Posts: 8
Truncated wrote:
EDIT: I made a chart of the number of hits it takes to kill an enemy given your HP. The top is the maximum number of hits, bottom is minimum (if you get 3/8 bonus damage on every strike). https://docs.google.com/spreadsheets/d/1bvb_4qVh4YpNMb2CeMxfYUeg-LSfKsTATB-Xr4lbQto/edit?usp=sharing It's a guess until I have answers to the questions above, in which stages of the calculation things are rounded down. It fits the numbers given for slimes and orcs at least. Names are according to the Japanese charts, I don't know the English names. Also, this: >BA (Base Attack) = Min(1,(HP-2*DS)*0.25) should probably be >BA (Base Attack) = Max(1,(HP-2*DS)*0.25)
You are right, it should be BA (Base Attack) = Max(1,(HP-2*DS)*0.25). Good work on putting together that spreadsheet, that looked like it took some time. You can override the random number generator by using these patch codes: 001172:3E3C 001174:xxxx 001176:4E71 Where xxxx is any 2-byte number (this will be the output of the RNG). For example - to have no damage boost, replace xxxx in the above with 0000. For maximum damage, replace xxxx with FFFF. This code will also have some other effects on the game :)
Experienced Forum User
Joined: 4/29/2015
Posts: 8
vayarda wrote:
Whooah, Thanks Lordash ! It's so amazing ! These informations are precious for TAS but also for speedrun haha :D However, it seems to me that when Ryle have 99HP (or 100, i forget) you kill all ennemies with one shot, even Gola.
Most enemies will die immediately at 100HP, but due to the high defence score of the golems they will actually take more hits to kill than King Nole / Gola! Silver golems will die after around 11 hits at 100HP, whereas King Nole seems to take around 9 hits and Gola around 6. Even at 170HP (the highest you can normally get in the game), King Nole takes around 4 hits and Gola 3.
Experienced Forum User
Joined: 4/29/2015
Posts: 8
How did you come by this info? Disassembling the ROM, testing and observing the RAM, something else?
I found the relevant code whilst disassembling the rom (it is located at 0x1657E, at least in the US version of the ROM). I've been looking at making a map viewer / editor for Landstalker for some time now (been working on it on and off for the past 7 or so years) which is how I came across the code. I also verified it by looking at the RAM whilst fighting enemies.
Also, can BA and MA be fractional? (It looks like it.) Does the enemies keep track of fractional health, or is the whole damage floored to an integer before being applied?
Both attacks and health can indeed be fractional. These values are stored as a 16-bit word, the high byte of which refers to the whole number of HP, and the low order byte refers to the fractional part.
How can there be a 1/96 chance of roundoff error? Isn't the same calculation performed the same way every time you attack?
For some reason, your total health is actually 1/256 less than what you would expect (for example, if you have 7HP, this is stored in RAM as 0x06FF) - I'm not entirely sure why, but it means that when damage is calculated by dividing your total health (including the fractional part) by four, this value always gets rounded down. Enemies, on the other hand, have their HP stored as you would expect (e.g. for an enemy with 2HP, this is stored as 0x0200). This means that when you attack the enemy with 8HP above its defence level, you will hit it for 0x1FF leaving it with 0x0001, or 1/256 of a hit point left. In practice, the random element will ensure that this always never happens, but there is a small chance that there will be no random damage boost. This will happen 1 in 96 times (or 3/8 times 256).
Experienced Forum User
Joined: 4/29/2015
Posts: 8
Really good work on the TAS, I enjoyed watching that. In terms of enemy damage, it appears that each enemy has a "defence level" as well as a total HP count. The following image summarises this (taken from Encyclopedia Landstalker): I don't know Japaneese, but I worked out that the columns of numbers from left to right read the following: Attack Power, Defence Level, HP, Gold Drop. The way the Defence level works is that it provides a minimum total HP that Nigel must have before his base attack does more than 1HP damage. Nigel's base attack will increase by 0.25HP for each level he has above the minimum total he needs for that class of enemy. The minimum total HP is calculated from the defence score (DS) as follows: HP = 2*DS + 4 Therefore it takes the following HP to reliably one hit kill an enemy (where EHP is the enemies' HP): HP = 2*DS + 4*EHP (If you want to always one-hit kill enemies, you will need to add one to the calculated HP level, as there is a 1/96 chance that there will be a roundoff error and your total attack will be ever-so-slightly less than the enemies' HP) For example, minimum HP to one-hit kill various enemies: White bubble (DS=3, HP=2): 2*3 + 4*2 = 14 Blue bubble (DS=3, HP=4): 2*3 + 4*4 = 22 Yellow orc (DS=5, HP=4): 2*5 + 4*4 = 26 Silver Golem (DS=60, HP=12): 2*60 + 4*12 = 168(!) In addition to the above, there is a random chance that your attack will be boosted between 0 and 3/8 for each complete HP your base attack provides: BA (Base Attack) = Max(1,(HP-2*DS)*0.25) (EDIT) MA (Modified Attack) = BA + rnd*3/8*floor(BA) Where rnd is a random number between 0 and 1, and floor() is the floor function (rounds down to the nearest whole number) For the magic swords (on a full charge), another modifier is applied to the attack score: FA (Final Attack) = MA * SM Where SM (Sword Modifier) is given by the following: Magic Sword: 1.75 Thunder Sword: 2.00 Ice Sword: 1.5 Gaia Sword: 1.25 Interestingly, the later swords have a lower damage modifier. I suppose this is to balance out the increased range of these swords. You can see the enemies' HP by using a RAM viewer. Nigel's HP is stored as a 2-byte word at address 0xFF543E. Other enemies follow every 0x80 bytes (at addresses 0xFF54BE, 0xFF553E, 0xFF5BE, ...) Sorry for the long post. I hope that made sense.