Monster throwing items

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
Isaac
Posts: 3172
Joined: Fri Mar 02, 2012 10:02 pm

Re: Monster throwing items

Post by Isaac »

minmay wrote:Just return false from the onExplode hook and no explosion will be created. (The item will still be destroyed.)
8-)
I should have thought of that.

**Works perfectly.
vieuxchat
Posts: 48
Joined: Wed Mar 02, 2016 3:14 pm

Re: Monster throwing items

Post by vieuxchat »

Nice :)

In fact the trap rune is in the asset pack. It isn't used in the game (as some of the monsters).

My problem is that the monster throw the trap_rune_dispelable_fire (which is displayed by a fire essence as I have absolutely no art skill) but when I'm hit, it just do the base damage and it doesn't create the trap_rune_dispelable_fire. But ! If I use the item and throw it against a wall it create the trap, if I throw it against a monster it works fine too !
If the monster attacks me and miss me, then it should create the trap as it does when I throw it. If the monster hit me it should create the trap and trigger it instantly.

I'll try your solutions :)
User avatar
Isaac
Posts: 3172
Joined: Fri Mar 02, 2012 10:02 pm

Re: Monster throwing items

Post by Isaac »

Here is a different solution; (that you are welcome to use in full or in part).

Image

The runes are harmless for 1.5 seconds; then they turn red, and explode if stepped on. They each fade away in 50 seconds.
(And they work for the snake as well. ;) )
SpoilerShow

Code: Select all

defineObject{
   name = "fire_bomb_trap_rune_utility",
   components = {
      {
         class = "ScriptController",
         name = "controller",
         onInit = function(self) -----------------[utility script that reactivates the traps, and makes them red; also erases them after 50 seconds.]
                     self.go.script:setSource([[function armTheTrap(trap)
                                                local trap = findEntity(trap)
                                                trap.floortrigger:enable()
                                                trap.model:setEmissiveColor(vec(0,-255,-255))
                                                trap:playSound("key_lock")  -------------------[ stock sound effect ]
                                                delayedCall(self.go.id, 50, "eraseBomb", trap.id) -------[the trap duration of 50 can be changed to suit.]
                                             end   

                                             function eraseBomb(trap)    
                                                local trap = findEntity(trap)
                                                if trap then
                                                   trap:playSound("force_field_cast")
                                                   trap:destroy()
                                                end   
                                             end  ]]) 

                  end
      },
      {
         class = "Script",
      },
   },
   placement = "floor",
   tags = { "scripting" },
   editorIcon = 148,
}

defineObject{
   name = "fire_bomb_trap_rune",
   baseObject = "base_spell",
   components = {

      {
         class = "Model",
         model = "assets/models/effects/trap_rune.fbx",
         offset = vec(0, 0.1, 0),
      },
      {
         class = "ProjectileCollider",
         collisionGroup = 2,     -- only dispel projectiles collision with us
         onInit = function(self) 
                   if not fire_bomb_trap_rune_utility then ------------------[Spawns the utillity script and momentarily deactivates the rune trap; and makes it blue.]
                     spawn("fire_bomb_trap_rune_utility",1,0,0,0,0,"fire_bomb_trap_rune_utility")
                   end  
                   self.go.floortrigger:disable() 
                   delayedCall("fire_bomb_trap_rune_utility", 1.5, "armTheTrap", self.go.id) 
                   self.go.model:setEmissiveColor(vec(-255,0,0))
               end
      },
      {
         class = "FloorTrigger",
         triggeredByItem = false,
         onActivate = function(self)
            self.go:spawn("fireburst")
            self.go:destroy()
            end,  
      },
   }
}

defineObject{
   name = "trap_bomb_fire",
   baseObject = "base_item",
   components = {
      {
         class = "Model",
         model = "assets/models/items/essence_fire.fbx",
      },
      {
         class = "Item",
         uiName = "Trap Bomb",
         gfxIndex = 346,
         impactSound = "impact_blunt",
         stackable = false,
         sharpProjectile = false,
         projectileRotationY = 90,
         weight = 0.1,
      },
      {
         class = "Particle",
         particleSystem = "essence_fire",
      },
      {
         class = "ThrowAttack",

         cooldown = 4,
      },
       {
         class = "BombItem",
         bombType = "fire",
         bombPower = 4,
         onExplode = function(self,level,x,y,facing,elevation)
                     spawn("fire_bomb_trap_rune",level,x,y,facing,elevation)
                     self.go:playSound("lock_incorrect")  ------------[ stock sound effect ]
                   return false end,
      },
   },
   tags = { "weapon", "weapon_throwing", "custom_vieuxchat" },
}

defineObject{
   name = "giant_snake_trap_thrower",
   baseObject = "base_monster",
   components = {
      {
         class = "Model",
         model = "assets/models/monsters/snake.fbx",
         storeSourceData = true,
         enabled = false,
      },
      {
         class = "Animation",
         animations = {
            idle = "assets/animations/monsters/snake/snake_idle.fbx",
            moveForward = "assets/animations/monsters/snake/snake_walk.fbx",
            turnLeft = "assets/animations/monsters/snake/snake_turn_left.fbx",
            turnRight = "assets/animations/monsters/snake/snake_turn_right.fbx",
            moveForwardTurnLeft = "assets/animations/monsters/snake/snake_walk_turn_left.fbx",
            moveForwardTurnRight = "assets/animations/monsters/snake/snake_walk_turn_right.fbx",
            attack = "assets/animations/monsters/snake/snake_attack.fbx",
            attackFromBehind = "assets/animations/monsters/snake/snake_attack_from_behind.fbx",
            getHitFrontLeft = "assets/animations/monsters/snake/snake_get_hit_front_left.fbx",
            getHitFrontRight = "assets/animations/monsters/snake/snake_get_hit_front_right.fbx",
            getHitBack = "assets/animations/monsters/snake/snake_get_hit_back.fbx",
            getHitLeft = "assets/animations/monsters/snake/snake_get_hit_left.fbx",
            getHitRight = "assets/animations/monsters/snake/snake_get_hit_right.fbx",
            fall = "assets/animations/monsters/snake/snake_get_hit_front_left.fbx",
         },
         currentLevelOnly = true,
      },
      {
         class = "Monster",
         meshName = "snake_mesh",
         hitSound = "snake_hit",
         dieSound = "snake_die",
         hitEffect = "hit_goo",
         capsuleHeight = 0.2,
         capsuleRadius = 0.7,
         health = 300,
         evasion = 10,
         exp = 200,
         traits = { "animal" },
         headRotation = vec(90, 0, 0),
      },
      {  
         class = "UggardianBrain", ---------------------[Added simple onThink hook to allow modest trap avoidance.]
         name = "brain",
         sight = 10,
         allAroundSight = true,
         morale = 100,
         onThink = function(self) --QaD Just enough so that it doesn't follow the party step for step; triggering its own traps.
                        local dX, dY = getForward(self.go.facing)
                        for each in self.go.map:entitiesAt(self.go.x+dX, self.go.y+dY) do
                           if each.name == "fire_bomb_trap_rune" then
                              local action = {"rangedAttack","rangedAttack","rangedAttack", "wait", "turnLeft", "turnRight"}
                              local choice = action[math.random(#action)] 
                               return self.go.brain[choice](self.go.brain)
                           end
                        end   
                     end
      },
      {
         class = "MonsterMove",
         name = "move",
         sound = "snake_walk",
         cooldown = 3,
      },
      {
         class = "MonsterMove",
         name = "moveForwardAndTurnRight",
         animations = { forward="moveForwardTurnRight" },
         sound = "snake_walk",
         turnDir = 1,
         cooldown = 2,
         resetBasicAttack = true,
      },
      {
         class = "MonsterMove",
         name = "moveForwardAndTurnLeft",
         animations = { forward="moveForwardTurnLeft" },
         sound = "snake_walk",
         turnDir = -1,
         cooldown = 2,
         resetBasicAttack = true,
      },
      {
         class = "MonsterTurn",
         name = "turn",
         sound = "snake_walk",
         resetBasicAttack = true,
      },
      {
         class = "MonsterAttack",
         name = "basicAttack",
         attackFromBehindAnimation = "attackFromBehind",
         attackPower = 26,
         accuracy = 20,
         cooldown = 4,
         sound = "snake_attack",
         woundChance = 30,
         causeCondition = "poison",
         conditionChance = 20,
      },
 {
         class = "MonsterAttack",
         name = "rangedAttack",
         attackType = "projectile",
         attackPower = 30,
         cooldown = 4,
         animation = "attack",
         sound = "snake_attack",
         shootProjectile = "trap_bomb_fire",
         projectileHeight = 1.5,
      },
      {
         class = "UggardianFlames",
         particleSystem = "air_elemental", --_vieuxchat",
         emitFromMaterial = "*",
      },
   },
   tags = { "custom_vieuxchat" }
}
vieuxchat
Posts: 48
Joined: Wed Mar 02, 2016 3:14 pm

Re: Monster throwing items

Post by vieuxchat »

Nice idea :D
User avatar
Isaac
Posts: 3172
Joined: Fri Mar 02, 2012 10:02 pm

Re: Monster throwing items

Post by Isaac »

Here are new bomb and snake assests. The bomb explodes on impact with a monster or the party, or it lays a fire-trap rune when it hits a wall or obstacle.

*The snake spins around before spitting the fire bomb.
SpoilerShow
Image
fire bomb:
SpoilerShow

Code: Select all

defineObject{
   name = "fire_bomb_trap_rune_utility",
   components = {
      {
         class = "ScriptController",
         name = "controller",
         onInit = function(self) -----------------[utility script that reactivates the traps, and makes them red; also erases them after 50 seconds.]
                     self.go.script:setSource([[function armTheTrap(trap)
                                                local trap = findEntity(trap)
                                                trap.floortrigger:enable()
                                                trap.model:setEmissiveColor(vec(0,-255,-255))
                                                trap:playSound("key_lock")  -------------------[ stock sound effect ]
                                                delayedCall(self.go.id, 50, "eraseBomb", trap.id) -------[the trap duration of 50 can be changed to suit.]
                                             end   

                                             function eraseBomb(trap)    
                                                local trap = findEntity(trap)
                                                if trap then
                                                   trap:playSound("force_field_cast")
                                                   trap:destroy()
                                                end   
                                             end  ]]) 
                  end
      },
      {
         class = "Script",
      },
   },
   placement = "floor",
   tags = { "scripting" },
   editorIcon = 148,
}

defineObject{
   name = "fire_bomb_trap_rune",
   baseObject = "base_spell",
   components = {

      {
         class = "Model",
         model = "assets/models/effects/trap_rune.fbx",
         offset = vec(0, 0.1, 0),
      },
      {
         class = "ProjectileCollider",
         collisionGroup = 2,     -- only dispel projectiles collision with us
         onInit = function(self) 
                   if not fire_bomb_trap_rune_utility then ------------------[Spawns the utillity script and momentarily deactivates the rune trap; and makes it blue.]
                     spawn("fire_bomb_trap_rune_utility",1,0,0,0,0,"fire_bomb_trap_rune_utility")
                   end  
                   self.go.floortrigger:disable() 
                   delayedCall("fire_bomb_trap_rune_utility", 3, "armTheTrap", self.go.id) 
                   self.go.model:setEmissiveColor(vec(-255,0,0))
               end
      },
      {
         class = "FloorTrigger",
         triggeredByItem = false,
         onActivate = function(self)
            self.go:spawn("fireburst")
            self.go:destroy()
            end,  
      },
   }
}

defineObject{
   name = "trap_bomb_fire",
   baseObject = "base_item",
   components = {
      {
         class = "Model",
         model = "assets/models/items/essence_fire.fbx",
      },
      {
         class = "Item",
         uiName = "Trap Bomb",
         gfxIndex = 346,
         impactSound = "impact_blunt",
         stackable = false,
         sharpProjectile = false,
         projectileRotationY = 90,
         weight = 0.1,
      },
      {
         class = "Particle",
         particleSystem = "essence_fire",
      },
      {
         class = "ThrowAttack",
         cooldown = 4,
      },
       {
         class = "BombItem",
         bombType = "fire",
         bombPower = 4,
         onExplode = function(self,level,x,y,facing,elevation)
                        for each in self.go.map:entitiesAt(self.go.x, self.go.y) do
                           if each.monster or each.party then 
                              return true
                           end
                        end   
                           spawn("fire_bomb_trap_rune",level,x,y,facing,elevation)
                           self.go:playSound("lock_incorrect")  ------------[ stock sound effect ]
                        return false
                    end,
      },
   },
   tags = { "weapon", "weapon_throwing", "custom_vieuxchat" },
}
}
giant_snake_trap_thrower :
SpoilerShow

Code: Select all

defineObject{
   name = "giant_snake_trap_thrower",
   baseObject = "base_monster",
   components = {

      {
         class = "Model",
         model = "assets/models/monsters/snake.fbx",
         storeSourceData = true,
         enabled = false,
      },
      {
         class = "Animation",
         animations = {
            idle = "assets/animations/monsters/snake/snake_idle.fbx",
            moveForward = "assets/animations/monsters/snake/snake_walk.fbx",
            turnLeft = "assets/animations/monsters/snake/snake_turn_left.fbx",
            turnRight = "assets/animations/monsters/snake/snake_turn_right.fbx",
            moveForwardTurnLeft = "assets/animations/monsters/snake/snake_walk_turn_left.fbx",
            moveForwardTurnRight = "assets/animations/monsters/snake/snake_walk_turn_right.fbx",
            attack = "assets/animations/monsters/snake/snake_attack.fbx",
            attackFromBehind = "assets/animations/monsters/snake/snake_attack_from_behind.fbx",
            getHitFrontLeft = "assets/animations/monsters/snake/snake_get_hit_front_left.fbx",
            getHitFrontRight = "assets/animations/monsters/snake/snake_get_hit_front_right.fbx",
            getHitBack = "assets/animations/monsters/snake/snake_get_hit_back.fbx",
            getHitLeft = "assets/animations/monsters/snake/snake_get_hit_left.fbx",
            getHitRight = "assets/animations/monsters/snake/snake_get_hit_right.fbx",
            fall = "assets/animations/monsters/snake/snake_get_hit_front_left.fbx",
            spinAttack = "mod_assets/animations/monsters/snake/spin_around_left.fbx",
         },
         currentLevelOnly = true,
      },
      {
         class = "Monster",
         meshName = "snake_mesh",
         hitSound = "snake_hit",
         dieSound = "snake_die",
         hitEffect = "hit_goo",
         capsuleHeight = 0.2,
         capsuleRadius = 0.7,
         health = 300,
         evasion = 10,
         exp = 200,
         traits = { "animal" },
         headRotation = vec(90, 0, 0),
      },
      {  
         class = "UggardianBrain", 
         name = "brain",
         sight = 10,
         allAroundSight = true,
         morale = 100,
         onThink = function(self) --QaD Just enough so that it doesn't always follow the party step for step; triggering its own traps.
               local dX, dY = getForward(self.go.facing)
               for each in self.go.map:entitiesAt(self.go.x+dX, self.go.y+dY) do
                  if each.name == "fire_bomb_trap_rune" and each.model:getEmissiveColor()[2] ~= "-255" then
                     local action = {"turnTowardsParty", "turnTowardsParty", "turnLeft", "turnRight", "wait" }
                     local choice = action[math.random(#action)]
                      if self.go.brain.partyStraightAhead then
                         choice = "rangedAttack" 
                      end
                      self.go.brain[choice](self.go.brain)
                      return self.go.brain.seesParty 
                  end
               end   
            end,
      },
      {
         class = "MonsterMove",
         name = "move",
         sound = "snake_walk",
         cooldown = 3,
      },
      {
         class = "MonsterMove",
         name = "moveForwardAndTurnRight",
         animations = { forward="moveForwardTurnRight" },
         sound = "snake_walk",
         turnDir = 1,
         cooldown = 2,
         resetBasicAttack = true,
      },
      {
         class = "MonsterMove",
         name = "moveForwardAndTurnLeft",
         animations = { forward="moveForwardTurnLeft" },
         sound = "snake_walk",
         turnDir = -1,
         cooldown = 2,
         resetBasicAttack = true,
      },
      {
         class = "MonsterTurn",
         name = "turn",
         sound = "snake_walk",
         resetBasicAttack = true,
      },
      {
         class = "MonsterAttack",
         name = "basicAttack",
         attackFromBehindAnimation = "attackFromBehind",
         attackPower = 26,
         accuracy = 20,
         cooldown = 4,
         sound = "snake_attack",
         woundChance = 30,
         causeCondition = "poison",
         conditionChance = 20,
      },
      {
        class = "MonsterAttack",
         name = "rangedAttack",
         attackType = "projectile",
         attackPower = 30,
         cooldown = 4,
         animation = "spinAttack",
         sound = 'snake_walk',
         shootProjectile = "trap_bomb_fire",
         projectileHeight = 1.5,
         onAttack = function(self) self.go:playSound("snake_attack") end, 
      },     
      {
         class = "UggardianFlames",
         particleSystem = "air_elemental", --_vieuxchat",
         emitFromMaterial = "*",
      },
   },
   tags = { "custom_vieuxchat" }
}

defineAnimationEvent{
   animation = "mod_assets/animations/monsters/snake/spin_around_left.fbx",
   event = "attack",
   frame = 43,
}
snake animation asset: https://www.dropbox.com/s/pf85bm80vb6w9 ... s.zip?dl=0
vieuxchat
Posts: 48
Joined: Wed Mar 02, 2016 3:14 pm

Re: Monster throwing items

Post by vieuxchat »

Wow ! Nice :D
Especially the OnThink part... I think I'll fidlle with it :)
Very very nice of you.
vieuxchat
Posts: 48
Joined: Wed Mar 02, 2016 3:14 pm

Re: Monster throwing items

Post by vieuxchat »

I've created 4 different kind of traps (one for each element).
So I'd like to change the "if then" line in the brain component.
I tried

Code: Select all

if each.name == "trap_rune_*" then
but it doesn't work.
What's the syntax to check for any name starting with "trap_rune_" ?
minmay
Posts: 2768
Joined: Mon Sep 23, 2013 2:24 am

Re: Monster throwing items

Post by minmay »

If any name containing "trap_rune_" is okay, then it's easiest and fastest to use

Code: Select all

if each.name:find("trap_rune_") then
If it specifically must start with "trap_rune_", then you would do this:

Code: Select all

if each.name:sub(1,10) == "trap_rune_" then
(asdf:sub(1,10) returns a string that is the first 10 characters of asdf)

You could also use pattern matching like you were trying to, using the string.match() function:

Code: Select all

if each.name:match("^trap_rune_*") then
The ^ indicates the beginning of the string, needed if you only want names starting with "trap_rune_" rather than just names containing "trap_rune_" anywhere (e.g. "xzxcnvbzxctrap_rune_zxcbm" would be matched by "trap_rune_*" but not "^trap_rune_*").
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.
vieuxchat
Posts: 48
Joined: Wed Mar 02, 2016 3:14 pm

Re: Monster throwing items

Post by vieuxchat »

Many thanks ! That's exactly what I needed :)

EDIT: as you seem to be very precise in the use of lua, what is the fastest method from those three ? For me, it seems that the "sub" method should be fastest as it doesn't have to check the whole name. Am I right ?
minmay
Posts: 2768
Joined: Mon Sep 23, 2013 2:24 am

Re: Monster throwing items

Post by minmay »

It is unlikely that the string.sub() approach is fastest unless the string you are searching is very long, since it has to allocate a new string for the substring. But I don't really know and can't be bothered to profile it; it's inconsequential in this context, you aren't performing this check very often.
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