MSUTeam/MSU

[REQUEST] new function onSpawnEntity(_entity) function for entities/items/skills

Opened this issue · 3 comments

Darxo commented

Is your feature request related to a problem? Please describe.
Currently no skill, item or entity can actively react to an entity spawning mid fight.
This ability would be useful for

  • items and skills which emit an aura which can only be realised by having every other entity gain an aura-receiving-effect. Examples are the battle_standard_effect and captain_effect: these effects are currently added to every entity regardless of whether an emitter is present
  • new effects similar to night debuff that are depending on some conditions. Examples: reaction to world climate (heat, cold, wet), vampire-debuff for fighting during daylight

Describe the solution you'd like
There is the function spawnEntity in the BB root table that is used to spawn any entity via its scriptPath mid-fight.
That function should be hooked so that it calls the following new onSpawnEntity function on the spawned entity before returning it

Add this new function to the entity.nut:

function onSpawnEntity()
{
	if (!this.World.getTime().IsDaytime && player.getBaseProperties().IsAffectedByNight)
	{
		this.getSkills().add(::new("scripts/skills/special/night_effect"));
	}

	foreach (i, faction in this.getAllInstances())
	{
		foreach (actor in faction)
		{
			actor.getSkills().onSpawnEntity(this);
			actor.getItems().onSpawnEntity(this);
			actor.getSkills().update();
		}
	}
}

Similar functions would be added to skill_container.nut, skill.nut aswell as item_container.nut and item.nut. But here they wouldn't contain any inherent effects.

So as a nice side-effect the night effect is now automatically added to every entitiy that is spawned mid-fight. No longer do we need to do this manually for unleashable animals for example.

This is actually a pretty cool idea I think.

I've been working on this for quite some time and unfortunately it doesn't have a simple/clean solution. Ideally I'd like the onSpawnEntity event to trigger after the entity has been properly set up i.e. its faction has been assigned and its items have been equipped.

My current solution consists of all of the following:

  • Hook setupEntity in tactical_entity_manager and call onSpawn from there.
  • Hook onResurrect in tactical_entity_manager and call onSpawn from there.
  • The above functions are not called for player entities. So, hook addEntity and insertEntity in turn_sequence_bar and check for faction being non-zero (faction is zero when an entity is first inserted into the turn sequence bar, except for player entities. These entities get their factions assigned later). Then call onSpawn.
  • Because of this hook on turn sequence bar, I've had to keep a boolean HasOnSpawnBeenCalled in the actor, to prevent calling the event again and again when the entity is removed from map and placed on map again e.g. after being devoured whole.

An alternative is to call onSpawn from onPlacedOnMap in actor (will still require that Boolean above) - however, the issue here is also that this function is called when the entity's faction is 0 and its item container is empty.

Another alternative is to call onSpawn from addInstance in tactical_entity_manager. However, that event is called via setFaction in actor, so the faction is properly set, but it is called before their items have been set.

Summary:
Using hooks on setupEntity, onResurrect, addEntity, insertEntity, I am able to trigger the event reliably for entities at the start of a combat with all the info (faction, items etc.) been properly set up for the entity spawned. However, currently I do not have a solution for entities spawned mid-combat via the ::Tactical.spawnEntity function as during this time their faction and items are 0.

After a lot more working here is a "dirty" solution that I have that currently seems to work. We basically call onSpawn from the character's onSkillsUpdated function but we build in several checks via Booleans here and there to ensure that it is really only called on the first "valid" skill container update after being spawned i.e. the update that happens after all faction and item setup is complete.

  • Add the following variables in actor:
this.m.HasOnSpawnBeenCalled <- true;
this.m.IsInstanceAdded <- false;
this.m.IsAssigningRandomEquipment <- false;
  • Add the variable this.m.IsResurrecting to tactical_entity_manager which is set to true at the start of the onResurrect function and then to false at the end of it.
  • Add a hookTree on onAfterInit of actor and set this.m.HasOnSpawnBeenCalled to false.
  • Add a hookTree on assignRandomEquipment of actor and set this.m.IsAssigningRandomEquipment to true at the start and false at the end of the function. Manually call this.onSpawn() at the end of this function.
  • Add a hook on onSkillsUpdated of actor and do the following i.e. call the onSpawn event if all these conditions are satisfied. This event will then iterate over all entities on the map and tell them that this entity has spawned.
if (this.isPlacedOnMap() && !this.m.IsAssigningRandomEquipment && this.m.IsInstanceAdded && !::Tactical.Entities.m.IsResurrecting)
      this.onSpawn();
  • Hook addInstance in tactical_entity_manager and if _actor.getFaction() != 0 set _actor.m.IsInstanceAdded = true;

So:

  1. Entities set this.m.HasOnSpawnBeenCalled to false after init. This means that the next call to onSpawn will be allowed. This ensures that skills added during onInit do not trigger the onSpawn event.
  2. When their instance is added to the tactical entity manager, it sets this.m.IsInstanceAdded to true.
  3. When their equipment is being assigned in assignRandomEquipment, then any skills added there do not call onSpawn because of our hook on onSkillsUpdated which checks for IsAssigningRandomEquipment to be false.
  4. Once all their equipment has been assigned, the manual call to this.onSpawn() triggers the event. If they were not assigning any equipment, then the event is triggered due to the hook on onSkillsUpdated which is called when the character's skill container updates after spawning. The this.m.IsInstanceAdded check in this case ensures that entities that spawn during the combat do not trigger the event before their faction has been properly set.

Other thoughts:
While this may work, and if it really works and is robust enough then perhaps it is worth it to do it like this. However, if it is fragile and if there are still edge cases where it may break, then perhaps it is better to just use a normal hook on addInstance and trigger onSpawn with faction info but with the limitation that the item container will be empty (and any skills added in assignRandomEquipment or similar functions will not be present yet). We can then document this so people know.