[Script] Combat Log (Script added to post)

Ask for help about creating mods and scripts for Grimrock 2 or share your tips, scripts, tools and assets with other modders here. Warning: forum contains spoilers!
User avatar
Jgwman
Posts: 144
Joined: Thu Jun 28, 2012 10:14 pm

[Script] Combat Log (Script added to post)

Post by Jgwman »

I saw a request for this on the forums somewhere a while ago, and thought it might be interesting practice to work on.

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)
SpoilerShow

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,
      }
   },
}
items.lua: (to add Toggle Logging item)
SpoilerShow

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
	  }
   },
}
Script entity in dungeon named 'globals' (name IS significant and must be changed in party_hooks.lua if changed in dungeon)
SpoilerShow

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")
Known issues:
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!
Last edited by Jgwman on Sun Nov 02, 2014 4:09 pm, edited 6 times in total.
User avatar
Drakkan
Posts: 1318
Joined: Mon Dec 31, 2012 12:25 am

Re: [Script] [WIP] Combat Log

Post by Drakkan »

that reminds me old games, nice addition ! you shoul make some possibility to turn this feature on / off (perhaps some gui button or something like that)
Breath from the unpromising waters.
Eye of the Atlantis
User avatar
Vice Dellos
Posts: 47
Joined: Thu Feb 28, 2013 12:56 pm

Re: [Script] [WIP] Combat Log

Post by Vice Dellos »

neat
User avatar
Jgwman
Posts: 144
Joined: Thu Jun 28, 2012 10:14 pm

Re: [Script] [WIP] Combat Log

Post by Jgwman »

Will definitely look into it Drakkan!
NutJob
Posts: 426
Joined: Sun Oct 19, 2014 6:35 pm

Re: [Script] [WIP] Combat Log

Post by NutJob »

Drakkan wrote:that reminds me old games, nice addition ! you shoul make some possibility to turn this feature on / off (perhaps some gui button or something like that)
A clickable item, perhaps. Click to toggle.
User avatar
Doridion
Posts: 256
Joined: Tue Jun 10, 2014 9:23 pm

Re: [Script] [WIP] Combat Log

Post by Doridion »

Waiting for the Grimwidget and the LoG frameworks for LoG2 :p
User avatar
Lark
Posts: 178
Joined: Wed Sep 19, 2012 4:23 pm
Location: Springfield, MO USA

Re: [Script] [WIP] Combat Log

Post by Lark »

Wow Jgwman, this is really nice! I really like the extra detail that it displays. It does remind me of games of old! I can't wait to see the code.
User avatar
Jgwman
Posts: 144
Joined: Thu Jun 28, 2012 10:14 pm

Re: [Script] [WIP] Combat Log

Post by Jgwman »

Lark wrote:Wow Jgwman, this is really nice! I really like the extra detail that it displays. It does remind me of games of old! I can't wait to see the code.
Thanks! Perhaps I'll have a look at an enable/disable feature this evening and go ahead with adding the script to this post.
User avatar
sapientCrow
Posts: 608
Joined: Sun Apr 22, 2012 10:57 am

Re: [Script] [WIP] Combat Log

Post by sapientCrow »

I like this!! I always want to look back and see how my damage and other numbers are doing.

thanks!!
User avatar
Eleven Warrior
Posts: 745
Joined: Thu Apr 18, 2013 2:32 pm
Location: Australia

Re: [Script] [WIP] Combat Log

Post by Eleven Warrior »

Hi Jgwman. Now your idea sounds very interesting and it will be awesome. Ill be watching this post for sure. Yeah I hope a lot of the old LOG 1 systems are ported over :) anyway have a nice day.
Post Reply