berserker trait?

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!
bongobeat
Posts: 1076
Joined: Thu May 16, 2013 5:58 pm
Location: France

berserker trait?

Post by bongobeat »

Does anyone know how to add +40 damage, with any melee weapon, in a trait?
like the aggressive trait.

I want to make a berserker skill, which at 5th level gives the berserker trait
My asset pack: viewtopic.php?f=22&t=9320

Log1 mod : Toorum Manor: viewtopic.php?f=14&t=5505
minmay
Posts: 2790
Joined: Mon Sep 23, 2013 2:24 am

Re: berserker trait?

Post by minmay »

You can't. Aggressive trait is hardcoded, there is no way for modders to give attack power bonuses.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
bongobeat
Posts: 1076
Joined: Thu May 16, 2013 5:58 pm
Location: France

Re: berserker trait?

Post by bongobeat »

minmay wrote:You can't. Aggressive trait is hardcoded, there is no way for modders to give attack power bonuses.
arghh! :cry:

well then is there a way to simply increase damage to any weapons, by percentage?
My asset pack: viewtopic.php?f=22&t=9320

Log1 mod : Toorum Manor: viewtopic.php?f=14&t=5505
minmay
Posts: 2790
Joined: Mon Sep 23, 2013 2:24 am

Re: berserker trait?

Post by minmay »

No. Again, there is no way for modders to give attack power bonuses.

If you really, absolutely must, increase attack power, the best approach (the only remotely workable approach, in fact) is to iterate through champions' hands every frame and modify the attackPower field of wielded weapons. The easiest way is with a TimerComponent attached to the party with an interval of 0.0001 or similar (TimerComponent only activates once per update at most, so this does not waste any resources). You'll want to reset the attack power when the weapon is unwielded of course, so you want an onUnequipItem hook on every weapon as well.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
bongobeat
Posts: 1076
Joined: Thu May 16, 2013 5:58 pm
Location: France

Re: berserker trait?

Post by bongobeat »

argh this is to much complicated for me.

I finally do something, based on Akroma/Drakkan custom traits:
this thread is a gold mine! :lol:
viewtopic.php?f=22&t=8976&hilit=axe+master+trait
I ve modified the axe trait/skill and keep the bonus it add at 3rd and 5th levels. That give more damage with any melee weapon (or any weapon that have a specific onAttack hook)


the editor script called weaponModManager:
(I did not rename the axe traits into berserker traits, and I completely quoted the mace stuff)
I'm not 100% sure that I don't made some errors, but there is no crash in the editor or in the game. Please can someone check?

In fact I did not do much more, I just quoted the mace stuff that I don't need, and added some line between the quadruple comma
SpoilerShow

Code: Select all

    -------------------------------------------------------- Trait lists

    TRAIT_LIST = {
       woodcutter = true,
       master_axeman = true,
       grandmaster_axeman = true--,
--       skullcrusher = true,
--       master_maceman = true,
--       grandmaster_maceman = true
    }

    axe_TRAITS = {
       woodcutter = true,
       master_axeman = true,
       grandmaster_axeman = true
    }

--    mace_TRAITS = {
--       skullcrusher = true,
--       master_maceman = true,
--       grandmaster_maceman = true
--    }

    --------------------------------------------------------- Modifier list

    modList = {}

    ----------------------------------------------------------gatherModifierTraits(self, champion, weapontype1, weapontype2)

    function gatherModifierTraits(self, champion, weapontype1, weapontype2)
       local c = party.party:getChampion(champion)
----
---- this has been modified by adding sword, mace and dagger
       if  weapontype1 == "axe"
       or  weapontype2 == "axe"
       or  weapontype1 == "sword"
       or  weapontype2 == "sword"
       or  weapontype1 == "mace"
       or  weapontype2 == "mace"
       or  weapontype1 == "dagger"
       or  weapontype2 == "dagger" then
----
          for trait,dummy in pairs(axe_TRAITS) do
             if c:hasTrait(trait) then
                modList[trait] = 1
                print(modList[trait])
             else
                modList[trait] = nil
                print(modList[trait])
             end
          end
       end
       
--       if  weapontype1 == "mace"
--       or  weapontype2 == "mace" then
--          for trait,dummy in pairs(mace_TRAITS) do
--             if c:hasTrait(trait) then
--                modList[trait] = 1
--                print(modList[trait])
--             else
--                modList[trait] = nil
--                print(modList[trait])
--             end
--          end
--       end
    end

    ---------------------------------------------------------addWeaponModifiers(self, champion, weapontype1, weapontype2)

    trueAttack = 0
--    truePierce = 1

    function addWeaponModifiers(self, champion, weapontype1, weapontype2)
       local c = party.party:getChampion(champion)
       
       trueAttack = self:getAttackPower()
--       truePierce = self:getPierce()
       
--       if truePierce == nil then
--          print("you must define weapon with 'pierce = 0'")
--          print("pierce = 1")
--          truePierce = 1
--       end
       
       gatherModifierTraits(weaponName, champion, weapontype1, weapontype2)   
----       
       if  weapontype1 == "axe"
       or  weapontype2 == "axe"
       or  weapontype1 == "sword"
       or  weapontype2 == "sword"
       or  weapontype1 == "mace"
       or  weapontype2 == "mace"
       or  weapontype1 == "dagger"
       or  weapontype2 == "dagger" then
----
          local cutterAttack = 0
          local masterAttack = 0
          local grandmasterAttack = 0
          
          if modList["woodcutter"] == 1 then
             local dx,dy = getForward(party.facing)
             for i in party.map:entitiesAt(party.x + dx,party.y + dy) do
                if i and i.monster then
                   if i.monster:hasTrait("plant") then
                      cutterAttack = 100
                      self:setAttackPower(trueAttack + cutterAttack + masterAttack + grandmasterAttack)
                      print(""..self:getAttackPower().."")
                   end
                end
             end
          end
          if modList["master_axeman"] == 1 then
             masterAttack = 20
             self:setAttackPower(trueAttack + cutterAttack + masterAttack + grandmasterAttack)
             print(""..self:getAttackPower().."")
          end
          if modList["grandmaster_axeman"] == 1 then
             grandmasterAttack = 50
             self:setAttackPower(trueAttack + cutterAttack + masterAttack + grandmasterAttack)
             print(""..self:getAttackPower().."")
          end
       end
       
--       if  weapontype1 == "mace"
--       or  weapontype2 == "mace" then
--          local crusherPierce = 0
--          local masterPierce = 0
--          local grandmasterPierce = 0
          
--          if modList["skullcrusher"] == 1 then
--             crusherPierce = 10
--             self:setPierce(truePierce + crusherPierce + masterPierce + grandmasterPierce)
--             print(""..self:getPierce().."")
--          end
--          if modList["master_maceman"] == 1 then
--             masterPierce = 20
--             self:setPierce(truePierce + crusherPierce + masterPierce + grandmasterPierce)
--             print(""..self:getPierce().."")
--          end
--          if modList["grandmaster_maceman"] == 1 then
--             grandmasterPierce = 50
--             self:setPierce(truePierce + crusherPierce + masterPierce + grandmasterPierce)
--             print(""..self:getPierce().."")
--          end
--       end
    end

    function removeWeaponModifiers(self, champion, weapontype1, weapontype2)
       local c = party.party:getChampion(champion)
       local aP = self:getAttackPower()
----       
       if  weapontype1 == "axe"
       or  weapontype2 == "axe"
       or  weapontype1 == "sword"
       or  weapontype2 == "sword"
       or  weapontype1 == "mace"
       or  weapontype2 == "mace"
       or  weapontype1 == "dagger"
       or  weapontype2 == "dagger" then
----
          for trait,dummy in pairs(axe_TRAITS) do
             modList[trait] = nil
          end
          self:setAttackPower(trueAttack)
          print(""..self:getAttackPower().."")
       end
       
--       if  weapontype1 == "mace"
--       or  weapontype2 == "mace" then
--          for trait,dummy in pairs(mace_TRAITS) do
--             modList[trait] = nil
--          end
--          self:setPierce(truePierce)
--          print(""..self:getPierce().."")
--       end
    end




    -------------------------------------------------------------------------------------stunstrikeCleanup(champion, weapon, stun, stunChance)

    function stunstrikeCleanup(champion, weapon, stun, stunChance)
       local tempStunChance = weapon.go.meleeattack:getConditionChance("stunned")
       
       if stun == false then
          
          weapon.go.meleeattack:setCauseCondition("")    
          weapon.go.meleeattack:setConditionChance(0)
          --print(weapon.go.meleeattack:getConditionChance())
       else
          weapon.go.meleeattack:setCauseCondition("stunned")    
          weapon.go.meleeattack:setConditionChance(stunChance)
          --print(weapon.go.meleeattack:getConditionChance())
       end
    end

I don't understand the last part of the script with the stun stuff, but if the stun is here, should I need to make something similar for other power attack?

The berzerker skill/traits, is this "balanced", too strong or lack of something? Any comment is welcome.
SpoilerShow

Code: Select all

defineSkill{
	name = "wayberserker",
	uiName = "The Warrior",
	priority = 200,
	icon = 26,
	description = "Trained as the Barbarian of Nix, you ignore fear and fight until death. Each level spent increase your critical skill by 2, your accuracy by 4 and your strength is increased by 4, also each level decrease your health by 10. On 3rd level you get a bonus in attack power of 20 with melee weapons. On 5th level you get a bonus of 40.",
	onComputeCritChance = function(champion, weapon, attack, attackType, level)
		return level * 2
	end,
	onRecomputeStats = function(champion, level)
		if level > 0 then
	champion:addStatModifier("strength", level*4)
	champion:addStatModifier("max_health", level*-10)
	end
end,
	traits = { [1] = "woodcutter", [2] = "apprentice_axeman", [3] = "expert_axeman", [4] = "master_axeman", [5] = "grandmaster_axeman"},
}
note: in the traits I reduce the fire resistance, that is normally given with the strength. I did that intentionally, to negate the fire resistance of the party, given by the 4 in strength for each level.
SpoilerShow

Code: Select all

defineTrait{
   name = "woodcutter",
   uiName = "Novice Warrior",
   icon = 94,
   description = "You started to learn how to make your ennemys fearing you.",
	onRecomputeStats = function(champion, level)
		if level > 0 then
			champion:addStatModifier("resist_fire", -8)
		end
	end,
}

    defineTrait{
       name = "apprentice_axeman",
       uiName = "Monster's Head Spliter",
       icon = 94,
       description = "Spliting monster's heads is much better than lumbering wood.",
	onRecomputeStats = function(champion, level)
		if level > 0 then
			champion:addStatModifier("resist_fire", -8)
		end
	end,
    }


    defineTrait{
       name = "expert_axeman",
       uiName = "Expert Warrior",
       icon = 94,
       description = "You have been trained in expert fighting techniques and there are scary rumors among the monsters. You gain Attack Power +20 for any melee weapon.",
	onRecomputeStats = function(champion, level)
		if level > 0 then
			champion:addStatModifier("resist_fire", -8)
		end
	end,
    }

    defineTrait{
       name = "master_axeman",
       uiName = "Nix Warrior",
       icon = 94,
       description = "You have mastered the art of the warrior. Only one final training is needed to reach the rank of Berserker.",
	onRecomputeStats = function(champion, level)
		if level > 0 then
			champion:addStatModifier("resist_fire", -8)
		end
	end,
    }

    defineTrait{
           name = "grandmaster_axeman",
           uiName = "The Berzerker",
           icon = 94,
           description = "Finally after all this time spent on your training, you have become a Berzerker. Bonus + 40 to attack power!",
	onRecomputeStats = function(champion, level)
		if level > 0 then
			champion:addStatModifier("resist_fire", -8)
		end
	end,
    }
a weapon for exemple:
SpoilerShow

Code: Select all

defineObject{
	name = "flail",
	baseObject = "base_item",
	components = {
		{
			class = "Model",
			model = "assets/models/items/flail.fbx",
		},
		{
			class = "Item",
			uiName = "Flail",
			gfxIndex = 87,
			gfxIndexPowerAttack = 423,
			impactSound = "impact_blunt",
			weight = 6.5,
			traits = { "heavy_weapon", "mace" },
		},
		{
			class = "MeleeAttack",
			attackPower = 24,
			pierce = 10,
			cooldown = 5,
			swipe = "vertical",
			attackSound = "swipe_heavy",
			requirements = { "heavy_weapons", 3 },
			powerAttackTemplate = "stun",
                onAttack = function(self, champion, action, slot)
             weaponModManager.script.addWeaponModifiers(self, champion:getOrdinal(), "axe")
                       delayedCall("weaponModManager", 0.2, "removeWeaponModifiers", self,  champion:getOrdinal(), "axe")
                 end,

		},
	},
	tags = { "weapon" },
}
My asset pack: viewtopic.php?f=22&t=9320

Log1 mod : Toorum Manor: viewtopic.php?f=14&t=5505
minmay
Posts: 2790
Joined: Mon Sep 23, 2013 2:24 am

Re: berserker trait?

Post by minmay »

Um, that method is terrible. It makes the attack power display wrong. That is a release breaker bug and completely inappropriate to use in a mod. Plus, the specific implementation doesn't even work, and I'm pretty sure it introduces a serialization error.

The method I described is extremely simple. Here is a plug-and-play version I wrote just for you:

Code: Select all

defineObject{
	name = "party",
	baseObject = "party",
	components = {
		{
			class = "Timer",
			name = "berserkerUpdateTimer",
			timerInterval = 0.0001,
			onActivate = function(self)
				for champNum = 1,4 do
					local champ = party.party:getChampion(champNum)
					for slot=ItemSlot.Weapon,ItemSlot.OffHand do
						local item = champ:getItem(slot)
						if item and item:hasTrait("berserker_weapon") then
							local attackClasses = {
								FirearmAttackComponent = true,
								MeleeAttackComponent = true,
								RangedAttackComponent = true,
								ThrowAttackComponent = true,
							}
							for _,comp in item.go:componentIterator() do
								if attackClasses[comp:getClass()] then
									local power
									local weaponData = party.berserkerScript.get(item.go.id)
									if weaponData then
										power = weaponData[comp:getName()]
									end
									if not power then
										power = comp:getAttackPower()
										-- store original attack power
										if weaponData then
											weaponData[comp:getName()] = power
										else
											party.berserkerScript.set(item.go.id,{[comp:getName()]=power})
										end
									end
									if champ:hasTrait("expert_axeman") then
										local skillCompensationDiv = 1 -- compensate for weapon skill bonus
										if item:hasTrait("heavy_weapon") then
											skillCompensationDiv = skillCompensationDiv+champ:getSkillLevel("heavy_weapons")*0.2
										end
										if item:hasTrait("light_weapon") then
											skillCompensationDiv = skillCompensationDiv+champ:getSkillLevel("light_weapons")*0.2
										end
										if item:hasTrait("missile_weapon") then
											skillCompensationDiv = skillCompensationDiv+champ:getSkillLevel("missile_weapons")*0.2
										end
										power = power+20/skillCompensationDiv
										if champ:hasTrait("grandmaster_axeman") then
											power = power+20/skillCompensationDiv
										end
									end
									comp:setAttackPower(power)
								end
							end
						end
					end
				end
			end,
		},
		{
			class = "Script",
			name = "berserkerScript",
			source = [[
			vars = {}

			function set(key,val)
			  vars[key] = val
			end

			function get(key)
			  return vars[key]
			end

			function weaponUnequip(item,champion,slot)
				if slot == ItemSlot.Weapon or slot == ItemSlot.OffHand then
					-- reset attack power
					local attackClasses = {
						FirearmAttackComponent = true,
						MeleeAttackComponent = true,
						RangedAttackComponent = true,
						ThrowAttackComponent = true,
					}
					for _,comp in item.go:componentIterator() do
						if attackClasses[comp:getClass()] then
							local weaponData = get(item.go.id)
							if weaponData then
								if weaponData[comp:getName()] then
									comp:setAttackPower(weaponData[comp:getName()])
								end
							end
						end
					end
					set(item.go.id,nil) -- remove stored weapon data
				end
			end]],
		},
	},
}
To make a weapon eligible for the traits, simply add the "berserker_weapon" trait and the following onUnequipItem hook:

Code: Select all

onUnequipItem = function(self,champion,slot)
	party.berserkerScript.weaponUnequip(self,champion,slot)
end,
So this is what your flail would look like:

Code: Select all

defineObject{
	name = "flail",
	baseObject = "base_item",
	components = {
		{
			class = "Model",
			model = "assets/models/items/flail.fbx",
		},
		{
			class = "Item",
			uiName = "Flail",
			gfxIndex = 87,
			gfxIndexPowerAttack = 423,
			impactSound = "impact_blunt",
			weight = 6.5,
			traits = { "heavy_weapon", "mace", "berserker_weapon" },
			onUnequipItem = function(self,champion,slot)
				party.berserkerScript.weaponUnequip(self,champion,slot)
			end,
		},
		{
			class = "MeleeAttack",
			attackPower = 24,
			pierce = 10,
			cooldown = 5,
			swipe = "vertical",
			attackSound = "swipe_heavy",
			requirements = { "heavy_weapons", 3 },
			powerAttackTemplate = "stun",
		},
	},
	tags = { "weapon" },
}
This method is good performance-wise, keeps the attack power display correct, and doesn't cause serialization issues.

edit: added compensation for weapon skill bonuses, since it looks like you didn't want it to stack with those. Also, you probably want to add a note in the introduction to your dungeon that explains why weapons' attack power appears to jump around.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
bongobeat
Posts: 1076
Joined: Thu May 16, 2013 5:58 pm
Location: France

Re: berserker trait?

Post by bongobeat »

aowww that's an awesome script! thanks for that! :shock:

I have just try it, totally insane! :lol:

well I did not think about the 20% damage based on weapons skill, it's good that you added the compensation!
thanks again!
My asset pack: viewtopic.php?f=22&t=9320

Log1 mod : Toorum Manor: viewtopic.php?f=14&t=5505
User avatar
akroma222
Posts: 1029
Joined: Thu Oct 04, 2012 10:08 am

Re: berserker trait?

Post by akroma222 »

Hey minmay,
thanks for posting a better method! ;)
The one a wrote for Drakkan is quite old/outdated/terrible... :?
I will link that thread to this one
Akroma
User avatar
Thorham
Posts: 80
Joined: Sat May 04, 2013 5:12 pm

Re: berserker trait?

Post by Thorham »

Thanks for the update idea minmay, just what I needed, but isn't it easier to use an onRecomputeStats and a script_entity? That way you can store the weapon data in the script entity, you don't have to add a timer to the party and you don't have to loop through all champions. Seems easier and cleaner.
minmay
Posts: 2790
Joined: Mon Sep 23, 2013 2:24 am

Re: berserker trait?

Post by minmay »

Thorham wrote:Thanks for the update idea minmay, just what I needed, but isn't it easier to use an onRecomputeStats and a script_entity? That way you can store the weapon data in the script entity, you don't have to add a timer to the party and you don't have to loop through all champions. Seems easier and cleaner.
The reason I didn't use onRecomputeStats is that onRecomputeStats hooks don't run if the champion doesn't have the trait. If you use onRecomputeStats and the trait is removed from the champion, weapons can get stuck with the wrong damage values.
The onUnequipItem hook exists for a similar reason. The timer/onRecomputeStats approach could be expanded to catch the items as they are unequipped via the mouse or quick weapon swap, since it can look at the mouse item and the champion's inventory (impossible to unequip AND drop an item on the same frame). However, if you have some kind of custom interface for unequipping the items that doesn't put them in any of those slots (maybe you made a monster that throws globs of grease that gets on your hands and your weapons fall on the ground, idk), the timer/onRecomputeStats hooks won't catch those.

If the trait can never be removed in your dungeon, then the timer is unnecessary and you can move it to an onRecomputeStats hook. If weapons always end up in the mouse or inventory on the frame after being unequipped, then the onUnequipItem hook is necessary. But I didn't want to make those assumptions about bongobeat's dungeon.

It already uses a ScriptComponent to store the weapon information...

Here's a very similar trait I wrote for someone else that uses an approach more like the one you were thinking of, aside from storing the attack power bonus in a very silly place (I did this because the person I wrote it for doesn't even know how to add a component to an object):

Code: Select all

    defineTrait{
       name = "munitions",
       uiName = "Munitions Expert",
       description = "You are skilled with firearms. Wielded firearms gain +1 attack power per experience level.",
       onRecomputeStats = function(champion, level)
          -- Change firearm's attack power as soon as it's wielded. Change it back as soon as it's unwielded.
          -- Update constantly in case level changes.
          if level > 0 then
             local clevel = champion:getLevel()
             for slot=ItemSlot.Weapon,ItemSlot.MaxSlots+1 do
                local item
                if (slot == ItemSlot.MaxSlots+1) then
                   item = getMouseItem()
                else
                   item = champion:getItem(slot)
                end
                if item then
                   -- We store the attack power bonus in the item's multiple field (which
                   -- will never matter for an unstackable item, and nobody would make a
                   -- stackable firearm, right?)
                   local wielded = (slot == ItemSlot.Weapon or slot == ItemSlot.OffHand)
                   local setTrait = nil
                   for _,comp in item.go:componentIterator() do
                      if comp:getClass() == "FirearmAttackComponent" then
                         if item:hasTrait("mexpert") then
                            if wielded and item:getMultiple() ~= clevel then
                               comp:setAttackPower(comp:getAttackPower()-item:getMultiple()+clevel)
                               item:setMultiple(clevel)
                            elseif not wielded then
                               comp:setAttackPower(comp:getAttackPower()-item:getMultiple())
                               setTrait = false
                            end
                         elseif wielded then
                            comp:setAttackPower(comp:getAttackPower()+clevel)
                            item:setMultiple(clevel)
                            setTrait = true
                         end
                      end
                   end
                   if setTrait == true then
                      item:addTrait("mexpert")
                   elseif setTrait == false then
                      item:removeTrait("mexpert")
                   end
                end
             end
          end
       end,
    }
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
Post Reply