https://www.youtube.com/watch?v=26Nrs2awzDo
At the moment, it will notify the player of most in combat events (deal damage, take damage, kill opponent, die, XP gain, status effect received). Potion consumption notification is now added. A reviewable log on the GUI is planned (the logs are already stored; I was hoping I might be permitted to use the notebook when JohnW ports it ) Obviously the hudPrint() is the notification and the print() on the console was just debugging info.
The hudPrint() log can now be disabled with the tome named 'Toggle Logging' ("log_toggle" if you need to spawn one). It seems spawning "in the air" does not work as it did in Grimrock 1, so the book is spawned directly in front of the party rather than in inventory.
Here is the script at the moment. It will be updated with new features and better script, most likely once the reference is completed, so check for updates.
party_hooks.lua: (name not important; remember to import in your init file)
Code: Select all
--Define hook functions for party
defineObject{
name = "party",
baseObject = "party",
components = {
{
class = "Party",
onAttack = function(party, champion, weapon)
if weapon.go.usableitem ~= nil then
globals.script.logEvent(champion:getName() .. " used a " .. string.lower(weapon.go.item:getUiName()) .. ".")
else
local target = globals.script.getTarget(weapon)
if (target ~= "nil" and target ~= nil) then globals.script.logEvent(champion:getName() .. " attacked a " .. target.name .. ".")
end
end
end,
onDamage = function(party, champion, damage, damageType)
globals.script.logEvent(champion:getName() .. " received " .. damage .. " " .. damageType .. " damage.")
end,
onDie = function(party, champion)
globals.script.logEvent(champion:getName() .. " has been slain! ")
end,
onReceiveCondition = function(party, champion, condition, condNumber)
if string.sub(condition, #condition - 4) == "wound" then
condition = string.sub(condition, 1, #condition - 6)
if string.find(condition, "_") ~= nil then condition = string.sub(condition, 1, string.find(condition, "_") - 1) .. " " .. string.sub(condition, string.find(condition, "_") + 1) end
globals.script.logEvent(champion:getName() .. " has received a wound to the " .. condition .. "!")
else
if string.sub(condition, #condition) == "e" then condition = condition .. "d"
else condition = condition .. "ed"
end
globals.script.logDiscreteEvent(champion:getName() .. " has been " .. condition .. "!")
end
end,
onCastSpell = function(party,champion,spellName)
globals.script.logEvent(champion:getName() .. " cast " .. spellName .. ".")
end,
}
},
}
Code: Select all
-- This file has been generated by Dungeon Editor 2.1.13
-- TODO: place your custom item definitions here
defineObject{
name = "log_toggle",
baseObject = "tome_wisdom",
components = {
{
class = "Item",
uiName = "Toggle Logging",
gfxIndex = 30,
weight = 0.0,
description = "Have a champion use this item to enable or disable on-screen logging.",
},
{
class = "UsableItem",
sound = "level_up",
onUseItem = function(self,champion)
globals.script.enableHUDLog(not globals.script.displayLog)
local state;
if (globals.script.displayLog == true) then state = "enabled"
else state = "disabled" end
hudPrint("On screen logging has been " .. state .. ".")
return false
end
}
},
}
Code: Select all
lastTarget = nil
log = {}
archive = {}
displayLog = true
--Sets whether log should be rendered on HUD
function enableHUDLog(bool)
displayLog = bool
end
--Log a new combat entry, specifying whether a call to hudPrint is necessary
function logDiscreteEvent(message)
log[#log + 1] = message
end
--Log a new combat entry with an alert on the HUD
function logEvent(message)
log[#log + 1] = message
if displayLog then hudPrint(message) end
end
--Archive the current log when complete
function archiveLog()
archive[#archive + 1] = log
log = {}
end
--Find all monsters in the dungeon
function allMonsters()
local entities = {}
for e in self.go.map:allEntities() do
if e.monster then
entities[#entities+1] = e
end
end
return entities
end
--Check if the party is facing an entity (bool)
function partyIsFacing(entity)
if entity == nil or party.elevation ~= entity.elevation then return false
end
if party.facing == 0 then
if entity.y < party.y and entity.x == party.x then return true
else return false
end
end
if party.facing == 1 then
if entity.x > party.x and entity.y == party.y then return true
else return false
end
end
if party.facing == 2 then
if entity.y > party.y and entity.x == party.x then return true
else return false
end
end
if party.facing == 3 then
if entity.x < party.x and entity.y == party.y then return true
else return false
end
end
return false
end
--Find the distance between the party and the entity (diagonal returns invalid distance) (int)
function distanceFromParty(entity)
if party.x == entity.x then
return math.abs(party.y - entity.y)
elseif party.y == entity.y then
return math.abs(party.x - entity.x)
else return -1;
end
end
--Find the closest entity (entity)
function closestEntity(entities)
local shortestDistance = 999
local tempEntity = nil
for i = 1, #entities do
if distanceFromParty(entities[i]) < shortestDistance then
shortestDistance = distanceFromParty(entities[i])
tempEntity = entities[i]
end
end
return tempEntity
end
--Determine whether the entity is in attack range of the weapon
function inRange(entity, weapon)
if weapon == "spell" then return true end
if entity == nil then return false end
if weapon.go.meleeattack then
if distanceFromParty(entity) == 1 then return true
end
return false
end
-- Firearms aren't categorized -
-- easiest thing is to assume they are in range
-- as the player probably thinks they are anyway
return true
end
--Guess which opponent the party is attempting to attack (entity)
function getTarget(weapon)
local possibleTargets = allMonsters()
print(#possibleTargets, "monsters in the level")
removedTargets = 0
for i = 1, #possibleTargets do
i = i - removedTargets
if partyIsFacing(possibleTargets[i]) == false then
table.remove(possibleTargets, i)
removedTargets = removedTargets + 1
end
end
print(#possibleTargets, "being faced")
if weapon ~= nil then
if inRange(closestEntity(possibleTargets), weapon) then
lastTarget = closestEntity(possibleTargets)
print(lastTarget.id, "is the target")
print("\n")
return lastTarget
end
if closestEntity(possibleTargets) ~= nil then
print("Target is not in range")
print("\n")
return nil
end
print("No target in line of sight")
print("\n")
return nil
end
print("Target is not in range")
print("\n")
return nil
end
function globalMonsterHook(event, target, action)
myMonsters = allMonsters()
for i = 1, #myMonsters do
myMonsters[i].monster:addConnector(event, target, action)
end
end
function monsterDamage(monster, damage, damageType)
logEvent("The " .. monster.go.name .. " was hit for " .. damage .. " " .. damageType .. " damage.")
end
function monsterDeath(monster)
logEvent("The " .. monster.go.name .. " has been slain!")
logEvent("+" .. monster:getExp() .. " XP")
archiveLog()
end
globalMonsterHook("onDamage", "globals", "monsterDamage")
globalMonsterHook("onDie", "globals", "monsterDeath")
--Runs all script which must be executed after dungeon load
function init()
spawnedItem = spawn("log_toggle", nil, nil, nil, nil)
--party.party:getChampion(1):insertItem(1, spawnedItem.item)
timer_temp:destroy()
end
--Timer is spawned to call init and is then destroyed
timer_temp = spawn("timer")
timer_temp.timer:setTimerInterval(0.1)
timer_temp.timer:setDisableSelf(false)
timer_temp.timer:setTriggerOnStart(true)
timer_temp.timer:setCurrentLevelOnly(true)
timer_temp.timer:addConnector("onActivate", "globals", "init")
Fist attacks do not register (damage caused, however, does)
Firearm attacks are always registered as 'in range'
Obstacles obstructing monsters MAY still register as an attack on said monsters
This should work automatically with modded monsters (anything with a 'monster' component and a valid 'name' should be registered).
I would welcome suggestions or feature requests, if anyone has any; or if anyone tests the script, post your results. Thanks!