To shake the monsters out of their funk, I needed to add an instanace_nearest check for either a building or a person of any kind. Game Maker allows you to parent objects, so I created generic "obj_building" and "obj_person" objects and childed my buildings and villages. If there's one within a certain distance, the monster will cease wandering and start moving to attack.
The monster stuff was easy. Once it collided with a villager or building, it would enter an attack mode, slowly depleting the hp of the collided object. Once the hp reached 0, the object would be destroyed and the monster would seek a new target or go back to wandering. In order to achieve this, the monster has three states:
- Moving - I'm wandering around looking for stuff.
- Aggro - I've found something I don't like and I'm heading towards it.
- Attacking - I'm in combat mode.
Under a simple test with a building, it all worked perfectly. I added a health bar to the building with the Draw event so I could track hp depletion. Here's the code. The numbers are placeholder until I get them all stored somewhere else:
draw_self(); // you need this so it will still draw the sprite
if(hp < MaxHp) // only show health bar if damaged
draw_rectangle(x-32,y-27,x+32,y-16,false); //healthbar background
draw_rectangle(x-30,y-25, x+(hp*6)-30, y-18,false); //healthbar: this gets smaller with less hp.
Here's where it got tricky. Villages don't stand still. They are off doing things, so they also need to know they are being attacked (or they are in danger) under different circumstances. At first, the villages were completely stupid. They did not react to a monster nearby. This lead to villagers running right into a monster already occupied.
They needed a panic mode. The initial implementation was actually pretty simple. If there's a monster nearby, run in the opposite direction until you are far away, then resume what you're doing. Again, instance_nearest comes to our aid. Here's the basic script:
monster = instance_nearest(x,y,obj_monster);
monsterDist = distance_to_object(monster);
if(monsterDist < 50 && !panic)
path_end(); //stop following current path
path_delete(myPath); //delete path: you need to do this to prevent memory leaks
myPath = path_add(); //prepare for new path.
speed = 0; //stop moving for a moment.
panic = true; //you are now in a state of panic
//get point to run to
dir = point_direction(monster.x, monster.y, x, y); // gets a vector pointing from monster to villager
radius = random_range(dir+45, dir-45); // this helps randomize the final direction to run in.
targetX = x + lengthdir_x(200,radius);
targetY = y + lengthdir_y(200,radius);
targetX = floor(targetX);
targetY = floor(targetY);
//check to see if it's a valid destination and then go there
r = mp_grid_path(grid, myPath, x, y, targetX, targetY,1);
alarm = 90;
panic = false; // if not valid path, setting panic to false resets this condition and it starts over again
That worked ok. But what if the villager was heading to a resource? What if the villager was heading back to his building? What if the villager was actively gathering resources? All of these things need to be accounted for. I already had variables I was tracking to cover these possibilities. Here are the ones for the lumberjack:
- onTree - I'm busy chopping down a tree. At first I just made it so the villager would not run away. But what if the villager finished chopping before the monster is done killing him? He starts walking away, but the monster remains frozen in place because he hasn't completed his task. So I also made the villager stop gathering.
- myTree - The holds the instance_id of the tree to be chopped. Zero means it's not an instance_id and any other number means it's an instance_id. Now I know which path I need to create if the lumberjack leaves panic mode. If it's zero, create a path back to the sawmill. If it's any other number, create a path to that instance.
But what if the sawmill is destroyed by a monster? What do the lumberjacks do? It depends. Are there other sawmills? Was the lumberjack already headed to that destination? My solution was this:
The lumberjack does an initial check for the existence of any sawmill (and then the nearest one) and then goes there. If no sawmill exists, the town center becomes the destination. When the lumberjack reaches the town center, the wood is deposited, and the lumberjack disappears.
If the lumberjack is already heading back to a sawmill, there is a check that happens every couple of seconds to see if it's still there. If it's not, recalculate a path based nearest sawmill or town center.
All of this took a lot more time than I expected, but at this point it all works. Monsters and villagers swarm around doing what they should all on their own.