sapientCrow wrote:Will we see the sequel in LoG2 editor. I really really hope so.
I need to know more about the Drinn.
Yes, and it resolves all the plot points. It'll also be quite a bit longer. I'm actually planning on posting a preview fairly soon once I have a few choice models and textures done.
sapientCrow wrote:I am also curious how you deal with the exp sharing.
It's not pretty. Here's how it works:
- Every monster's onDie hook calls the giveFullXp function.
- giveFullXp spawns a timer with a 1ms delay (so it will trigger on the next frame) with the monster's id stored in its id (unless there's already a timer for that monster), and returns false (leaving the monster alive).
- The timer calls a function that sets the monster's health to 1 and does a damageTile on it that's credited to all 4 champions. This awards full experience to all living champions. Since killing the monster calls the giveFullXp hook again, the monster's id is marked in the "removingMonster" table; when giveFullXp is called on such a marked monster it simply returns true without doing anything else.
- To award experience to dead champions, there is a manually-maintained table with experience values for every monster in the game, awarded with Champion:gainExp(). This is by far the easiest part.
- If the monster is an uggardian, giveFullXp replaces it with a dummy monster because the uggardian's flame effect causes a crash if you return false from its onDie. This puts the light and particle effects in the wrong place if the uggardian was moving, so that's why I don't use it for other monsters. (The dummy monster is spawned because I want the "+500 XP" text.)
Code: Select all
-- XXX: be absolutely sure these are accurate to monsters.lua!
xpvals = {
["monster_herder"] = 50,
["monster_herder_small"] = 100,
["monster_crab"] = 150,
["monster_blue_slime"] = 250,
["monster_uggardian_ghost"] = 600,
["monster_willowhisper"] = 250,
["monster_guardian_red"] = 1000,
["uggardian"] = 0,
["uggardian_fake"] = 1000,
["monster_guardian_green"] = 1000,
["monster_guardian_cyan"] = 1000,
["monster_guardian_blue"] = 1000,
["monster_guardian_magenta"] = 1000,
["goromorg"] = 2000,
["monster_the_simulacrum"] = 2500,
}
-- give full xp to all party members from killing a monster
-- pass the real onDie here
-- note: will not work with monster groups, and would require
-- obvious modification to work with level >1 monsters!
--
-- the damageTile *must* be the hit that kills the monster, otherwise
-- you will still get no xp for monsters killed by no champion at all.
-- i don't see any way to kill the monster on the same frame because
-- of this.
--
-- XXX: i'm not sure of the exact reason but this
-- causes a crash with the "uggardian" id's flame effect.
-- therefore we spawn a fake one instead and kill that.
-- unfortunately this means that the particle effects will be in
-- the wrong place if the uggardian was killed while moving.
givingXp = false
removingMonster = {}
function giveFullXp(mon,onDie)
-- special case to prevent flame effect crash; real uggardian
-- should give 0 xp for this to work
if mon.name == "uggardian" then
onDie(mon)
local l,x,y,f = mon.level,mon.x,mon.y,mon.facing
mon:setPosition(1,1,1,1)
awardDeadXp(spawn("uggardian_fake",l,x,y,f):setHealth(1))
damageTile(l,x,y,f,0x7c,"poison",2)
return true
end
if findEntity("t"..mon.id) then return false end -- killed it twice on the same frame (or 2 consecutive frames?)
if removingMonster[mon.id] then removingMonster[mon.id] = nil return true end
if onDie and not onDie(mon) then return false end
spawn("timer",mon.level,mon.x,mon.y,mon.facing,"t"..mon.id)
:setTimerInterval(0.0001)
:addConnector("activate",ME,"killMonster")
:activate()
return false
end
function killMonster(t)
local mon = findEntity(t.id:sub(2))
t:destroy()
if mon then
awardDeadXp(mon)
removingMonster[mon.id] = true
mon:setHealth(1)
damageTile(mon.level,mon.x,mon.y,mon.facing,0x7c,"poison",2)
else game.bugPrint("NO_DEADMONSTER") end
end
function awardDeadXp(mon)
local xp = xpvals[mon.name]
if not xp then game.bugPrint("BADMONSTER") return true end
for i = 1, 4 do
local champ = party:getChampion(i)
if champ:getEnabled() and not champ:isAlive() then champ:gainExp(xp) end
end
end
Now, to answer your next questions:
Q: Why don't you just do a damageTile in giveFullXp to credit damage to all champions, then return true? You wouldn't have to delay it with a timer that way.
A: This works great, except that it still requires the killing blow be dealt by a party member. You're returning true to the original killing blow, not the damageTile, so if the original killing blow was, say, a projectile from another monster, no experience will be awarded.
Q: Okay, then why don't you return false for the original killing blow, but make your damageTile kill the monster and return true to that? You still wouldn't need a timer.
A: Returning true from an onDie hook and subsequently returning false leaves monsters in a partially-destroyed state. At first they appear to die normally, but you'll find that the entity sticks around for a while and can even be damaged and killed again with burst spells and the like. Calling destroy() does not fix this (and has other undesirable effects).
Q: All right, what if you return true from both?
A: Then the monster dies twice. We wanted full experience, not double experience, and we also didn't want double sound, double particles, and double light...
Q: Well why don't you just award experience to all party members with gainExp() and make the monster kill give 0 XP?
A: I wanted the "+100 XP" text to still pop up.
So the timer approach was really the only option I had left.
Now, if you want to award full XP for all kills in Grimrock 2, it's really easy, because recent versions have a Champion:getExp() method. You don't have to care about how much experience they get from killing the monster because you can just adjust it yourself.