Custom monster examples

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!
Post Reply
sleepy
Posts: 15
Joined: Wed Feb 04, 2015 2:34 am

Custom monster examples

Post by sleepy »

It took me a little bit to cobble all of the information together to do this, so I figure this might save somebody time; answer a lot of questions all in one place.
I'm going to be doing more testing with monsters. I'll update this post with additional definitions for use as a community resource.
Feel free to import, use, modify, whatever. All I ask is that improvements made be posted and merged into the resource bank.

I also feel obligated to drop these links:
viewtopic.php?f=22&t=9505 <-- Asset Pack if anyone missed it.
https://github.com/JKos/log2doc/wiki <--The definitive reference.
http://grimwiki.net/wiki/Model_Retexturing_Tutorial <-- Good texture Tut

https://www.youtube.com/playlist?list=P ... z-bvhKpGkS <-- Excellent Blender tutorial series
viewtopic.php?f=22&t=8086 <-- Blender Import/Export plugin

Onto the list:

Crop Duster:

Features:
SpoilerShow
>uses herder_big as a base monster
>makes poison cloud on movement
>flees when the party gets close
>attempts to align to the party when at range
>moves towards party when out of range
>makes a ranged poison bolt attack when party is in range
>has particle system and light emitter attached
Screenshot:
SpoilerShow
Image
Code:
mod_assets\scripts\objects.lua
SpoilerShow

Code: Select all

-------- crop_duster --------
	
defineObject{
       name = "crop_duster",
       baseObject = "base_monster",
       components = {
          {
             class = "Model",
             model = "assets/models/monsters/herder_big_top.fbx",
             storeSourceData = true,
          },
		  {
             class = "Animation",
             name = "animation",
             currentLevelOnly = true,
             animations = {
                idle = "assets/animations/monsters/herder/herder_big_top_idle.fbx",
                moveForward = "assets/animations/monsters/herder/herder_big_top_walk.fbx",
                turnLeft = "assets/animations/monsters/herder/herder_big_top_turn_left.fbx",
                turnRight = "assets/animations/monsters/herder/herder_big_top_turn_right.fbx",
                attack = "assets/animations/monsters/herder/herder_big_top_attack.fbx",
                getHitFrontLeft = "assets/animations/monsters/herder/herder_big_top_get_hit_front_left.fbx",
                getHitFrontRight = "assets/animations/monsters/herder/herder_big_top_get_hit_front_right.fbx",
                getHitBack = "assets/animations/monsters/herder/herder_big_top_get_hit_back.fbx",
                getHitLeft = "assets/animations/monsters/herder/herder_big_top_get_hit_left.fbx",
                getHitRight = "assets/animations/monsters/herder/herder_big_top_get_hit_right.fbx",
             },
          },
          {
             class = "Monster",
             meshName = "herder_big_top_mesh",
             level=1,
             health = 600,
             evasion = -10,
             exp = 1500,
             lootDrop = { 100, "skull"},
             traits = { "animal" },
             hitEffect = "hit_goo",
             capsuleHeight = 0.2,
             capsuleRadius = 0.7,
             hitSound = "herder_hit",
             dieSound = "herder_die",
			 resistances = { poison="immune"},
             onPerformAction = function(self, name)
		   
			if name == "rangedAttack" then
			
				local pro = spawn("poison_bolt",self.go.level,self.go.x-1,self.go.y,self.go.facing,self.go.elevation)
				pro.projectile:setIgnoreEntity(party)
				pro.projectile:setVelocity(10)

				local left = .0005
				local wpos = self.go:getWorldPosition()
				local dx = nil
				local dz = nil
				
				if self.go.facing == 0 then
					dx = left and -0.1 or 0.1
					dz = -1
				elseif self.go.facing == 1 then
					dz = left and 0.1 or -0.1
					dx = -1
				elseif self.go.facing == 2 then
					dx = left and 0.1 or -0.1
					dz = 1
				else
					dz = left and -0.1 or 0.1
					dx = 1
				end
				pro:setWorldPosition(vec(wpos[1]+dx,wpos[2]+1.35,wpos[3]+dz))
			end
			
             if name == "move" then
				local poison_cloud = spawn("poison_cloud_medium", self.go.level, self.go.x, self.go.y, 0, self.go.elevation)
             end
			end
          },
          {
           class = "Brain",
           name = "brain",
           sight = 7,
           allAroundSight = true,
           onThink = function(self)
			 if math.abs(self.go.x - party.x) + math.abs(self.go.y - party.y) > 6 then
				self:moveTowardsParty()
             elseif math.abs(self.go.x - party.x) + math.abs(self.go.y - party.y) <= 1 then
				self:startFleeing()
             elseif math.abs(self.go.x - party.x) + math.abs(self.go.y - party.y) >= 2 then
				self:stopFleeing()
				
				if self.go.facing == 0 then
					if self.go.x > party.x then
						self:turnLeft()
						self:moveForward()
						self:turnTowardsParty()
					elseif self.go.x < party.x then
						self:turnRight()
						self:moveForward()
						self:turnTowardsParty()
					else
						self:turnTowardsParty()
					end
				elseif self.go.facing == 2 then
					if self.go.x > party.x then
						self:turnRight()
						self:moveForward()
						self:turnTowardsParty()
					elseif self.go.x < party.x then
						self:turnLeft()
						self:moveForward()
						self:turnTowardsParty()
					else
						self:turnTowardsParty()
					end
				elseif self.go.facing == 1 then
					if self.go.y > party.y then
						self:turnLeft()
						self:moveForward()
						self:turnTowardsParty()
					elseif self.go.y < party.y then
						self:turnRight()
						self:moveForward()
						self:turnTowardsParty()
					else
						self:turnTowardsParty()
					end
				elseif self.go.facing == 3 then	
					if self.go.y > party.y then
						self:turnRight()
						self:moveForward()
						self:turnTowardsParty()
					elseif self.go.y < party.y then
						self:turnLeft()
						self:moveForward()
						self:turnTowardsParty()
					else
						self:turnTowardsParty()
					end
				end
                self:rangedAttack()
             else
             end     
          end
          },
          {
             class = "MonsterMove",
             name = "move",
             sound = "herder_walk",
             resetBasicAttack = false,
             turnDir = 0,
             cooldown = 0.5,
             animationSpeed = 4,
          },
          {
             class = "MonsterTurn",
             name = "turn",
             sound = "herder_footstep",
             cooldown = 0.5,
             animationSpeed = 4,
          },
         {
             class = "MonsterAttack",
             name = "basicAttack",
             sound = "herder_attack",
             cooldown = 4,
             attackPower = 35,
             animationSpeed = 3,
             animation = "attack",
          },
		  {
             class = "MonsterAttack",
             name = "rangedAttack",
             sound = "herder_attack",
             cooldown = 1.5,
			 attackPower = 20,
			 maxDistance = 6,
             animationSpeed = 2,
             animation = "attack",
          },
		  {
			 class = "UggardianFlames",
			 particleSystem = "swamp_fume",  -- see: Legend of Grimrock 2\assets\scripts\particles
			 emitFromMaterial = "*",
          },
		  {
			class = "Light",
			parentNode = "chest", -- open mesh with Grimrock Model Toolkit and view nodes with drop list. This requires the LoG2 Asset Pack.
			color = vec(0.0, 2.0, 0.0),
			brightness = 6,
			range = 5,
			fillLight = true,
		},
       }
    }
Twigrock:

Features:
SpoilerShow
>Altered base textures to look like stone
>old_oak_branch modified to twigrock_branch to include eyeballs.
>Weak to Poison, resistant to Fire
>Does not collide with projectiles
Screenshot:
SpoilerShow
Image
Image
mod_assets: Code:
materials.lua
SpoilerShow

Code: Select all

-------- Twigrock
	
	defineMaterial{
	name = "twigrock",
	diffuseMap = "mod_assets/textures/twigrock_dif.tga",
	specularMap = "mod_assets/textures/twigrock_spec.tga",
	normalMap = "mod_assets/textures/twigrock_normal.tga",
	doubleSided = false,
	lighting = true,
	alphaTest = false,
	blendMode = "Opaque",
	textureAddressMode = "Wrap",
	glossiness = 65,
	depthBias = 0,
}
	defineMaterial{
	name = "twigrock_ivy",
	diffuseMap = "mod_assets/textures/twigrock_ivy_dif.tga",
	specularMap = "mod_assets/textures/twigrock_ivy_spec.tga",
	normalMap = "mod_assets/textures/twigrock_ivy_normal.tga",
	doubleSided = true,
	lighting = true,
	alphaTest = true,
	castShadow = false,
	blendMode = "Opaque",
	textureAddressMode = "Wrap",
	glossiness = 25,
	depthBias = 0,
}

defineMaterial{
	name = "twigrock_branch",
	diffuseMap = "mod_assets/textures/twigrock_branch_dif.tga",
	specularMap = "mod_assets/textures/twigrock_branch_spec.tga",
	normalMap = "mod_assets/textures/twigrock_branch_normal.tga",
	doubleSided = true,
	lighting = true,
	alphaTest = true,
	blendMode = "Opaque",
	textureAddressMode = "Wrap",
	glossiness = 10,
	depthBias = 0,
}
Code:
objects.lua
SpoilerShow

Code: Select all

 ---------- Twigrock
 
 defineObject{
	name = "twigrock",
	baseObject = "base_monster",
	components = {
		{
			class = "Model",
			model = "mod_assets/models/twigrock.fbx",
			storeSourceData = true, -- must be enabled for mesh particles to work
		},
		{
			class = "Animation",
			animations = {
				idle = "mod_assets/animations/twigrock_idle.fbx",
				sleep = "mod_assets/animations/twigrock_idle_to_stealth.fbx",
				wakeUp = "mod_assets/animations/twigrock_stealth_to_idle.fbx",
				moveForward = "mod_assets/animations/twigrock_walk.fbx",
				strafeLeft = "mod_assets/animations/twigrock_strafe_left.fbx",
				strafeRight = "mod_assets/animations/twigrock_strafe_right.fbx",
				turnLeft = "mod_assets/animations/twigrock_turn_left.fbx",
				turnRight = "mod_assets/animations/twigrock_turn_right.fbx",
				attack = "mod_assets/animations/twigrock_attack_left.fbx",
				attack2 = "mod_assets/animations/twigrock_attack_right.fbx",
				getHitFrontLeft = "mod_assets/animations/twigrock_get_hit_front_left.fbx",
				getHitFrontRight = "mod_assets/animations/twigrock_get_hit_front_right.fbx",
				getHitBack = "mod_assets/animations/twigrock_get_hit_back.fbx",
				getHitLeft = "mod_assets/animations/twigrock_get_hit_left.fbx",
				getHitRight = "mod_assets/animations/twigrock_get_hit_right.fbx",
				fall = "mod_assets/animations/twigrock_get_hit_front_left.fbx",
				stealth = "mod_assets/animations/twigrock_stealth.fbx",
			},
			currentLevelOnly = true,
		},
		{
			class = "Monster",
			meshName = "twigroot_mesh",
			hitSound = "twigroot_hit",
			dieSound = "twigroot_die",
			hitEffect = "hit_goo",
			capsuleHeight = 0.2,
			capsuleRadius = 0.7,
			health = 120,
			evasion = 10,
			exp = 90,
			--lootDrop = { 50, "branch", 50, "branch" },
			immunities = { "sleep", "blinded", "frozen" },
			resistances = { ["poison"] = "weak", ["fire"] = "resist" },
			collisionRadius = 0,
		},
		{
			class = "TwigrootBrain",
			name = "brain",
			sight = 4,
		},
		{
			class = "MonsterMove",
			name = "move",
			sound = "twigroot_walk",
			cooldown = 3,
		},
		{
			class = "MonsterTurn",
			name = "turn",
			sound = "twigroot_walk",
		},
		{
			class = "MonsterAttack",
			name = "basicAttack",
			attackPower = 12,
			accuracy = 10,
			cooldown = 2,
			sound = "twigroot_attack",
			onBeginAction = function(self)
				-- randomize animation
				if math.random() < 0.5 then
					self:setAnimation("attack")
				else
					self:setAnimation("attack2")
				end
			end,
		},
	},
}

defineObject{
	name = "twigrock_dormant",
	baseObject = "twigrock",
	components = {
		{
			class = "Null",
			onInit = function(self)
				self.go.brain:disable()
				self.go.animation:play("stealth")
			end,
		},
		{
			class = "MonsterAction",
			name = "wakeUp",
			animation = "wakeUp",
			onEndAction = function(self)
				self.go.brain:enable()
			end,
		},
	}
}

defineObject{
	name = "twigrock_pair",
	baseObject = "base_monster_group",
	components = {
		{
			class = "MonsterGroup",
			monsterType = "twigrock",
			count = 2,
		}
	},
}

defineAnimationEvent{
	animation = "mod_assets/animations/twigrock_attack_left.fbx",
	event = "attack",
	frame = 9,
}

defineAnimationEvent{
	animation = "mod_assets/animations/twigrock_attack_right.fbx",
	event = "attack",
	frame = 9,
}

defineAnimationEvent{
	animation = "mod_assets/animations/twigrock_strafe_left.fbx",
	event = "playSound:twigroot_strafe",
	frame = 0,
}

defineAnimationEvent{
	animation = "mod_assets/animations/twigrock_strafe_right.fbx",
	event = "playSound:twigroot_strafe",
	frame = 0,
}
Code:
sounds.lua
SpoilerShow

Code: Select all

------ Twigrock

defineSound{
	name = "twigroot_walk",
	filename = "assets/samples/monsters/twigroot_walk_01.wav",
	loop = false,
	volume = 0.5,
	minDistance = 1,
	maxDistance = 10,
	clipDistance = 5,
}

defineSound{
	name = "twigroot_strafe",
	filename = "assets/samples/monsters/twigroot_strafe_01.wav",
	loop = false,
	volume = 0.85,
	minDistance = 1,
	maxDistance = 10,
	clipDistance = 5,
}

defineSound{
	name = "twigroot_attack",
	filename = "assets/samples/monsters/twigroot_attack_01.wav",
	loop = false,
	volume = 1,
	minDistance = 1,
	maxDistance = 10,
}

defineSound{
	name = "twigroot_hit",
	filename = { 
		"assets/samples/weapons/hit_bone_01.wav",
		"assets/samples/weapons/hit_bone_02.wav",
		"assets/samples/weapons/hit_flesh_01.wav",
	},
	loop = false,
	volume = 1,
	minDistance = 1,
	maxDistance = 10,
}

defineSound{
	name = "twigroot_die",
	filename = "assets/samples/monsters/twigroot_die_01.wav",
	loop = false,
	volume = 1,
	minDistance = 1,
	maxDistance = 10,
}
________________________________
Previous update: Twigrock animations added to twigrockAssets.zip, objects.lua updated, screenshots updated
Last edited by sleepy on Wed Feb 08, 2017 9:11 am, edited 22 times in total.
kelly1111
Posts: 349
Joined: Sun Jan 20, 2013 6:28 pm

Re: Custom monster examples

Post by kelly1111 »

Looking forward for more of your monster ideas
Slayer82
Posts: 303
Joined: Thu Feb 05, 2015 10:19 am

Re: Custom monster examples

Post by Slayer82 »

Hey, man.

That looks really good.

I second what kelly1111 said.

By the way, have you only recently started playing and/or modding Legend of Grimrock 2?
Mods - Isle of the Deranged & The Allure of Nightfall
http://grimrock.net/forum/viewtopic.php?f=23&t=9513
viewtopic.php?f=23&t=14762
sleepy
Posts: 15
Joined: Wed Feb 04, 2015 2:34 am

Re: Custom monster examples

Post by sleepy »

I've dabbled a bit in the past. I spent a couple weeks with it a couple years ago (check my first post). I haven't been at the scripting long though. Only really been hitting it hard in the past couple of days. Though I've worked with this kind of stuff before. I'm one of those masochists who actually likes staring at text editors all night. No joke, I spent my whole 3 day weekend, virtually uninterrupted, working out some of the finer points of the scripting. Most of this is all pulled from other reference material. I felt like an idiot after realizing the object definitions were all in the asset pack. . . I never bothered to check for monster scripts. . . so that made things a lot easier.

Wrapping my head around how the component structure works took a minute, but once it clicks, it's pretty simple. It's just confusing when you're not certain whether or not the name of something is arbitrary. Then once you get components: everything else gets a lot easier because it's basically all the same structure. You just need to keep a reference open when you're making definitions so you know what the valid components are for the type you're making. Then you fuck around for an hour or two until you figure out how they work, then spend another hour trying to figure out where you fucked up after it was just working. Somehow this is fun for me.

I can't say how long I'll stick with this; I won't lie, I have an issue with abandoning projects, but I'm into this one right now, so I'm just going with it until I'm not. That's about the best I can hope for. I'm pretty quick with this kind of stuff, but I've no clue how to 'stick with it'. I realize that sounds shitty, and it kind of is, but I'm just saying it now so nobody develops any crushing expectations. I'm into it yet because I haven't hit my wall. Rigging custom models and getting that into .model format is looking problematic. Though I'm going to see if I can't get that sorted before I go to sleep.
User avatar
zimberzimber
Posts: 432
Joined: Fri Feb 08, 2013 8:06 pm

Re: Custom monster examples

Post by zimberzimber »

Hey Sleeper, thats a great start you have there! ;)
Hope to see some interesting things from you, keep it up! :D
My asset pack [v1.10]
Features a bit of everything! :D
sleepy
Posts: 15
Joined: Wed Feb 04, 2015 2:34 am

Re: Custom monster examples

Post by sleepy »

Took a couple of days to learn Blender modeling/rigging/etc. I won't say I'm any good, but I know how to hack it in 3D.

So, Twigrock progress update: I realized pretty quickly I couldn't just scale down the mesh. You have to scale all of the animations. I'm trying to figure out a quick way to do this. I can get all of the scaling done in 2-3 steps, but then you have to re-position everything back down to y(0). I'm going to see if I can't find a better way of doing this other than frame by frame.

But, all of that aside: I have the walking animation scaled, and it works in-game:
SpoilerShow
Image
1 down, 15 to go.
User avatar
zimberzimber
Posts: 432
Joined: Fri Feb 08, 2013 8:06 pm

Re: Custom monster examples

Post by zimberzimber »

You don't have to scale frame by frame every animation, only those that move the model. (Walking/strafing)
There's a method to do that written somewhere on the forum(I remember asking for it a while back), try using the search function. (I'd look for you, but I'm on the phone right now :p )
My asset pack [v1.10]
Features a bit of everything! :D
sleepy
Posts: 15
Joined: Wed Feb 04, 2015 2:34 am

Re: Custom monster examples

Post by sleepy »

zimberzimber wrote:You don't have to scale frame by frame every animation, only those that move the model. (Walking/strafing)
There's a method to do that written somewhere on the forum(I remember asking for it a while back), try using the search function. (I'd look for you, but I'm on the phone right now :p )
I did a little digging, but didn't try very hard.

If it's anything like batch scripting in photoshop: then it should be easy enough to just make such a script.

something like:

Code: Select all

function transpose(arg1,arg2){

mode = pose,
axis = arg1
N = arg2

selected = select.objects.bones(all),
frame = setCurrentFrame(1),
lastFrame = getLastFrame(currentAnimation);

do until frame = lastFrame{
    selection.axis:move(N),
    frame.key.updateKey(Location),
    frame += 1,
},
},
Though I'm not sure how soon I'm going to worry about this. I'm actually planning on rigging and animating monster#3 from scratch. I'm just going to finish the Twigrock this way. Though If i do write a script (or someone digs up the old script): I'll post it in the OP.

I've also added the youtube series I watched to orient myself with Blender. It's quite well done.

EDIT:
all animations are updated and functioning. Upon loading a new instance of blender and importing the twigrock.model: I was able to just import the animations and export directly without any frame tweaking. No script required. Though a note: I tried to do this from smallest to largest file size/number of frames because when you import it doesn't flush out the animation keys in excess of the current one's frames. I think there's some garbage data hanging around and I'll have to go back and do it again, flushing out the keys manually before each import. On the strafe animations: at the end the last frame will pop back to the origin square for a second before cutting to idle. So I know I messed something up.

Whatever. It's good enough and I'm sick of staring at this dude for now. I'm going to update all of the entries in OP to contain the current version, and add the animations to the assets.zip. I might go back and fix the animations during a polish pass later.
Post Reply