You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
958 lines
31 KiB
Lua
958 lines
31 KiB
Lua
----------------------------------------------------------------------------------------------------
|
|
--
|
|
-- Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
--
|
|
-- SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
--
|
|
--
|
|
--
|
|
----------------------------------------------------------------------------------------------------
|
|
EntityCommon =
|
|
{
|
|
TempPhysParams = { mass=0,density=0 },
|
|
TempPhysicsFlags = { flags_mask=0, flags=0 };
|
|
TempSimulationParams = { max_time_step=0.02 };
|
|
}
|
|
|
|
----------------------------------------------------------
|
|
-- Creates a new table that is derived class of parent entity.
|
|
----------------------------------------------------------
|
|
function MakeDerivedEntity( _DerivedClass,_Parent )
|
|
local derivedProperties = _DerivedClass.Properties;
|
|
_DerivedClass.Properties = {};
|
|
mergef(_DerivedClass,_Parent,1);
|
|
|
|
-- Add derived class properties.
|
|
mergef(_DerivedClass.Properties,derivedProperties,1);
|
|
|
|
_DerivedClass.__super = BasicEntity;
|
|
return _DerivedClass;
|
|
end
|
|
|
|
----------------------------------------------------------
|
|
-- Creates a new table that is derived class of parent entity.
|
|
-- The Child's Properties will override the ones from the parent
|
|
----------------------------------------------------------
|
|
function MakeDerivedEntityOverride( _DerivedClass,_Parent )
|
|
--local derivedProperties = _Parent.Properties;
|
|
--_Parent.Properties = {};
|
|
mergef(_DerivedClass,_Parent,1);
|
|
|
|
-- Add derived class properties.
|
|
--mergef(_DerivedClass.Properties,derivedProperties,1);
|
|
|
|
_DerivedClass.__super = _Parent;
|
|
return _DerivedClass;
|
|
end
|
|
|
|
|
|
----------------------------------
|
|
function BroadcastEvent( sender,Event )
|
|
-- Check if Event Target for this input event exists.
|
|
sender:ProcessBroadcastEvent( Event );
|
|
if (sender.Events) then
|
|
--System.Log( "Events found" );
|
|
local eventTargets = sender.Events[Event];
|
|
if (eventTargets) then
|
|
--System.Log( "Events Targets found" );
|
|
for i, target in pairs(eventTargets) do
|
|
local TargetId = target[1];
|
|
local TargetEvent = target[2];
|
|
--System.Log( "Target: "..TargetId.."/"..TargetEvent );
|
|
--System.Log( "Target: "..TargetEvent );
|
|
|
|
if (TargetId == 0) then
|
|
-- If TargetId refer to global Mission table.
|
|
if Mission then
|
|
local func = Mission["Event_"..TargetEvent];
|
|
if (func ~= nil) then
|
|
func( sender )
|
|
else
|
|
System.Log( "Mission does not support event "..TargetEvent );
|
|
end
|
|
end
|
|
else
|
|
-- If TargetId refer to Entity.
|
|
local entity = System.GetEntity(TargetId);
|
|
if (entity ~= nil) then
|
|
|
|
local TargetName=entity:GetName();
|
|
--System.Log( "Entity Named "..TargetName.." Found." );
|
|
--System.Log( "Calling method: "..TargetName..":Event_"..TargetEvent );
|
|
local func = entity["Event_"..TargetEvent];
|
|
if (func ~= nil) then
|
|
func( entity,sender )
|
|
-- else
|
|
-- System.Log( "Entity "..TargetName.." does not support event "..TargetEvent );
|
|
end
|
|
-- else
|
|
-- System.Log( "Entity Named "..TargetName.." Not Found." );
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function DumpEntities()
|
|
local ents=System.GetEntities();
|
|
System.Log("Entities dump");
|
|
for idx,e in pairs(ents) do
|
|
local pos=e:GetPos();
|
|
local ang=e:GetAngles();
|
|
System.Log("["..tostring(e.id).."]..name="..e:GetName().." clsid="..e.class..format(" pos=%.03f,%.03f,%.03f",pos.x,pos.y,pos.z)..format(" ang=%.03f,%.03f,%.03f",ang.x,ang.y,ang.z));
|
|
end
|
|
end
|
|
|
|
|
|
function MakeTargetableByAI( entity )
|
|
if (not entity.Properties) then entity.Properties = {} end
|
|
if (not entity.Properties.esFaction) then
|
|
entity.Properties.esFaction = "";
|
|
end
|
|
|
|
function entity:RegisterWithAI()
|
|
if (self.Properties.esFaction ~= "") then
|
|
CryAction.RegisterWithAI(self.id, AIOBJECT_TARGET);
|
|
AI.ChangeParameter(self.id, AIPARAM_FACTION, self.Properties.esFaction);
|
|
end
|
|
end
|
|
|
|
local _onReset = entity.OnReset;
|
|
function entity:OnReset(...)
|
|
if (_onReset) then
|
|
_onReset(self, ...);
|
|
end
|
|
|
|
self:RegisterWithAI();
|
|
end
|
|
|
|
local _onSpawn = entity.OnSpawn;
|
|
function entity:OnSpawn(...)
|
|
if (_onSpawn) then
|
|
_onSpawn(self, ...);
|
|
end
|
|
|
|
self:RegisterWithAI();
|
|
end
|
|
end
|
|
|
|
|
|
function MakeAICoverEntity(entity)
|
|
if (not entity.Properties) then entity.Properties = {} end
|
|
if (not entity.Properties.bProvideAICover) then entity.Properties.bProvideAICover = 1 end
|
|
|
|
local tbl = entity.Server and entity.Server or entity
|
|
local _onStartGame = tbl.OnStartGame
|
|
tbl.OnStartGame = function(self)
|
|
if (self.PropertiesInstance.bProvideAICover ~= 0) and (AI ~= nil) then
|
|
AI.AddCoverEntity(self.id)
|
|
end
|
|
|
|
if (_onStartGame) then
|
|
_onStartGame(self)
|
|
end
|
|
end
|
|
end
|
|
|
|
function MakeKillable( entity )
|
|
if (not entity.Properties) then entity.Properties = {} end
|
|
if (not entity.Properties.Health) then entity.Properties.Health = {} end
|
|
|
|
local Health = entity.Properties.Health;
|
|
Health.MaxHealth = 500;
|
|
Health.bInvulnerable = 0;
|
|
Health.bOnlyEnemyFire = 1;
|
|
|
|
function entity:IsDead()
|
|
return self.dead;
|
|
end
|
|
|
|
function entity:SetupHealthProperties()
|
|
self.dead = nil;
|
|
self.health = self.Properties.Health.MaxHealth;
|
|
self.invulnerable = self.Properties.Health.bInvulnerable ~= 0;
|
|
self.friendlyFire = self.Properties.Health.bOnlyEnemyFire == 0;
|
|
end
|
|
|
|
if (not entity.Server) then entity.Server = {} end
|
|
if (not entity.Client) then entity.Client = {} end
|
|
|
|
function entity:GetHealthRatio()
|
|
local healthR = 1;
|
|
local maxHealth = self.Properties.Health.MaxHealth;
|
|
if (maxHealth > 0) then
|
|
healthR = self.health / maxHealth;
|
|
end
|
|
|
|
return healthR;
|
|
end
|
|
|
|
function entity:IsInvulnerable()
|
|
return self.invulnerable
|
|
end
|
|
|
|
function entity:GetMaxHealth()
|
|
return self.Properties.Health.MaxHealth;
|
|
end
|
|
|
|
local _onHit = entity.Server.OnHit;
|
|
function entity.Server:OnHit(hit)
|
|
if ((not self.health) or (self.IsInvulnerable == nil)) then
|
|
Log("$4%s:%s Health not initialized!", self.class, self:GetName());
|
|
|
|
self:SetupHealthProperties();
|
|
end
|
|
|
|
local result = false;
|
|
if (_onHit) then
|
|
result = _onHit(self, hit);
|
|
end
|
|
|
|
if (not result) then
|
|
if (self:IsInvulnerable()) then
|
|
self:ActivateOutput("Health", self:GetHealthRatio() * 100);
|
|
self:Event_Hit();
|
|
|
|
return false;
|
|
end
|
|
|
|
if (not self.friendlyFire) then
|
|
if (System.GetEntity(hit.shooterId) ~= nil) then
|
|
local reaction = AI.GetReactionOf(self.id, hit.shooterId);
|
|
|
|
if (reaction == Friendly) then
|
|
self:ActivateOutput("Health", self:GetHealthRatio() * 100);
|
|
self:Event_Hit();
|
|
|
|
return false;
|
|
end
|
|
end
|
|
end
|
|
|
|
self.health = self.health - hit.damage;
|
|
end
|
|
|
|
self:ActivateOutput("Health", self:GetHealthRatio() * 100);
|
|
self:Event_Hit();
|
|
|
|
if (self.health <= 0) then
|
|
self.dead = true;
|
|
|
|
self:Event_Dead();
|
|
|
|
return true;
|
|
end
|
|
end
|
|
|
|
|
|
local _onReset = entity.OnReset;
|
|
function entity:OnReset(...)
|
|
if (_onReset) then
|
|
_onReset(self, ...);
|
|
end
|
|
|
|
self:SetupHealthProperties();
|
|
end
|
|
|
|
local _onSpawn = entity.OnSpawn;
|
|
function entity:OnSpawn(...)
|
|
if (_onSpawn) then
|
|
_onSpawn(self, ...);
|
|
end
|
|
|
|
self:SetupHealthProperties();
|
|
end
|
|
|
|
function entity:Event_ResetHealth()
|
|
self.dead = nil;
|
|
self.health = self.Properties.Health.MaxHealth;
|
|
end
|
|
|
|
function entity:SetInvulnerability(invulnerable)
|
|
self.invulnerable = invulnerable;
|
|
|
|
if(not self.overrode_saveload) then
|
|
local _onSave = self.OnSave;
|
|
function self:OnSave(table)
|
|
if(_onSave) then
|
|
_onSave(self, table);
|
|
end
|
|
|
|
if(self.invulnerable) then
|
|
table.invulnerable = self.invulnerable;
|
|
end
|
|
|
|
if(self.dead) then
|
|
table.dead = self.dead;
|
|
end
|
|
|
|
if(self.health) then
|
|
table.health = self.health;
|
|
end
|
|
end
|
|
|
|
local _onLoad = self.OnLoad;
|
|
function self:OnLoad(table)
|
|
if(_onLoad) then
|
|
_onLoad(self, table);
|
|
end
|
|
|
|
if(table.invulnerable) then
|
|
self.invulnerable = table.invulnerable;
|
|
else
|
|
self.invulnerable = false;
|
|
end
|
|
|
|
if(table.dead) then
|
|
self.dead = table.dead;
|
|
else
|
|
self.dead = false;
|
|
end
|
|
|
|
if(table.health) then
|
|
self.health = table.health;
|
|
else
|
|
self.health = self.Properties.Health.MaxHealth;
|
|
end
|
|
end
|
|
|
|
self.overrode_saveload = true;
|
|
end
|
|
end
|
|
|
|
function entity:Event_MakeVulnerable()
|
|
self:SetInvulnerability(false);
|
|
end
|
|
|
|
function entity:Event_MakeInvulnerable()
|
|
self:SetInvulnerability(true);
|
|
end
|
|
|
|
function entity:Event_Dead()
|
|
self:TriggerEvent(AIEVENT_DISABLE);
|
|
|
|
BroadcastEvent(self, "Dead");
|
|
end
|
|
|
|
function entity:Event_Hit()
|
|
BroadcastEvent(self, "Hit");
|
|
end
|
|
|
|
if not entity.FlowEvents then entity.FlowEvents = {} end
|
|
local fe = entity.FlowEvents
|
|
fe.Inputs = fe.Inputs or {}
|
|
fe.Outputs = fe.Outputs or {}
|
|
|
|
fe.Inputs["ResetHealth"] = { entity.Event_ResetHealth, "any" };
|
|
fe.Inputs["MakeVulnerable"] = { entity.Event_MakeVulnerable, "any" };
|
|
fe.Inputs["MakeInvulnerable"] = { entity.Event_MakeInvulnerable, "any" };
|
|
|
|
fe.Outputs["Dead"] = "bool";
|
|
fe.Outputs["Hit"] = "bool";
|
|
fe.Outputs["Health"] = "float";
|
|
end
|
|
|
|
function MakeRenderProxyOptions( entity )
|
|
if (not entity.Properties) then entity.Properties = {} end
|
|
if (not entity.Properties.RenderProxyOptions) then entity.Properties.RenderProxyOptions = {} end
|
|
|
|
entity.Properties.RenderProxyOptions.bAnimateOffScreenShadow = 0;
|
|
|
|
function entity:SetRenderProxyOptions()
|
|
self.bAnimateOffScreenShadow = self.Properties.RenderProxyOptions.bAnimateOffScreenShadow ~= 0;
|
|
if (self.bAnimateOffScreenShadow) then
|
|
self:CreateRenderProxy();
|
|
end
|
|
self:SetAnimateOffScreenShadow(self.bAnimateOffScreenShadow);
|
|
end
|
|
|
|
local _onReset = entity.OnReset;
|
|
function entity:OnReset(...)
|
|
if (_onReset) then
|
|
_onReset(self, ...);
|
|
end
|
|
|
|
self:SetRenderProxyOptions();
|
|
end
|
|
|
|
local _onSpawn = entity.OnSpawn;
|
|
function entity:OnSpawn(...)
|
|
if (_onSpawn) then
|
|
_onSpawn(self, ...);
|
|
end
|
|
|
|
self:SetRenderProxyOptions();
|
|
end
|
|
|
|
end
|
|
|
|
-- makes an OnUsed event for designers on an entity...
|
|
-- usage:
|
|
-- MyEntity = { ... whatever you usually put here ... }
|
|
-- MakeUsable(MyEntity)
|
|
-- function MyEntity:OnSpawn() ...
|
|
-- function MyEntity:OnReset()
|
|
-- self:ResetOnUsed()
|
|
-- ...
|
|
-- end
|
|
function MakeUsable( entity )
|
|
if not entity.Properties then entity.Properties = {} end
|
|
entity.Properties.UseMessage = "";
|
|
entity.Properties.bUsable = 0;
|
|
function entity:IsUsable()
|
|
if not self.__usable then
|
|
self.__origUsable = self.Properties.bUsable;
|
|
self.__origPickable = self.Properties.bPickable;
|
|
if (self.Properties.bUsable==1 or self.Properties.bPickable==1) then
|
|
self.__usable = 1;
|
|
else
|
|
self.__usable = 0;
|
|
end
|
|
end
|
|
return self.__usable;
|
|
end
|
|
function entity:ResetOnUsed()
|
|
self.__usable = nil;
|
|
end
|
|
function entity:GetUsableMessage()
|
|
return self.Properties.UseMessage;
|
|
end
|
|
function entity:OnUsed(user, idx)
|
|
BroadcastEvent(self, "Used");
|
|
if (self.Base_OnUsed) then
|
|
self:Base_OnUsed(user, idx);
|
|
end
|
|
end
|
|
function entity:Event_Used()
|
|
BroadcastEvent(self, "Used");
|
|
end
|
|
function entity:Event_EnableUsable()
|
|
self.__usable = 1;
|
|
BroadcastEvent(self, "EnableUsable");
|
|
end
|
|
function entity:Event_DisableUsable()
|
|
self.__usable = 0;
|
|
BroadcastEvent(self, "DisableUsable");
|
|
end
|
|
end
|
|
|
|
function MakePickable( entity )
|
|
if not entity.Properties then entity.Properties = {} end;
|
|
entity.Properties.bPickable = 0;
|
|
end
|
|
|
|
function AddHeavyObjectProperty(entity)
|
|
if (not entity.Properties) then
|
|
entity.Properties = {};
|
|
end;
|
|
entity.Properties.bHeavyObject = 0;
|
|
end;
|
|
|
|
function MakeThrownObjectTargetable( entity )
|
|
-- Add property
|
|
if not entity.Properties then
|
|
entity.Properties = {};
|
|
end
|
|
if not entity.Properties.AutoAimTarget then
|
|
entity.Properties.AutoAimTarget = {};
|
|
end
|
|
entity.Properties.AutoAimTarget.bMakeTargetableOnThrown = 0;
|
|
entity.Properties.AutoAimTarget.InnerRadiusVolumeFactor = 0.35;
|
|
entity.Properties.AutoAimTarget.OuterRadiusVolumeFactor = 0.6;
|
|
entity.Properties.AutoAimTarget.SnapRadiusVolumeFactor = 1.25;
|
|
entity.Properties.AutoAimTarget.AfterThrownTargetableTime = 3.0;
|
|
|
|
-- Add callback functions
|
|
function entity:OnThrown()
|
|
if ((self.Properties.AutoAimTarget.bMakeTargetableOnThrown ~= 0) and (self:CanBeMadeTargetable())) then
|
|
Game.RegisterWithAutoAimManager(self.id, self.Properties.AutoAimTarget.InnerRadiusVolumeFactor, self.Properties.AutoAimTarget.OuterRadiusVolumeFactor, self.Properties.AutoAimTarget.SnapRadiusVolumeFactor);
|
|
Script.SetTimer(self.Properties.AutoAimTarget.AfterThrownTargetableTime * 1000, function() self:AfterThrownTimer(); end)
|
|
self.isTargetable = 1;
|
|
end
|
|
end
|
|
|
|
function entity:AfterThrownTimer()
|
|
if (self.isTargetable) then
|
|
Game.UnregisterFromAutoAimManager(self.id);
|
|
self.isTargetable = nil;
|
|
end
|
|
end
|
|
|
|
local _CanBeMadeTargetable = entity.CanBeMadeTargetable;
|
|
function entity:CanBeMadeTargetable(...)
|
|
if (_CanBeMadeTargetable) then
|
|
return _CanBeMadeTargetable(self, ...);
|
|
end
|
|
return true;
|
|
end
|
|
|
|
-- Override shutdown/reset
|
|
local _OnShutDown = entity.OnShutDown;
|
|
function entity:OnShutDown(...)
|
|
if _OnShutDown then
|
|
_OnShutDown(self, ...);
|
|
end
|
|
|
|
if (self.isTargetable) then
|
|
Game.UnregisterFromAutoAimManager(self.id);
|
|
self.isTargetable = nil;
|
|
end
|
|
end
|
|
|
|
local _OnReset = entity.OnReset;
|
|
function entity:OnReset(...)
|
|
if _OnReset then
|
|
_OnReset(self, ...);
|
|
end
|
|
|
|
if (self.isTargetable) then
|
|
Game.UnregisterFromAutoAimManager(self.id);
|
|
self.isTargetable = nil;
|
|
end
|
|
end
|
|
end
|
|
|
|
function AddInteractLargeObjectProperty(entity)
|
|
if (not entity.Properties) then
|
|
entity.Properties = {};
|
|
end;
|
|
entity.Properties.bInteractLargeObject = 0;
|
|
end;
|
|
|
|
function MakeSpawnable( entity )
|
|
entity.spawnedEntity = nil
|
|
-- setup some basic properties
|
|
if not entity.Properties then entity.Properties = {} end
|
|
local p = entity.Properties;
|
|
p.bSpawner = false;
|
|
p.SpawnedEntityName = "";
|
|
|
|
local _OnDestroy = entity.OnDestroy;
|
|
|
|
function entity:OnDestroy(...)
|
|
-- System.Log("OnDestroy"..tostring(self.id));
|
|
if self.whoSpawnedMe then
|
|
-- inform that I'm dead
|
|
self.whoSpawnedMe:NotifyRemoval(self.id);
|
|
end
|
|
if _OnDestroy then
|
|
_OnDestroy(self, ...);
|
|
end
|
|
end
|
|
|
|
function entity:NotifyRemoval(spawnedEntityId)
|
|
-- System.Log("NotifyRemoval"..tostring(self.id).." spawned="..tostring(spawnedEntityId));
|
|
-- clear spawnedEntity on original
|
|
if (self.spawnedEntity and self.spawnedEntity == spawnedEntityId) then
|
|
--System.Log("...Cleared");
|
|
self.spawnedEntity = nil;
|
|
self.lastSpawnedEntity = nil;
|
|
end
|
|
end
|
|
|
|
-- override some functions to have our code called also
|
|
local _OnReset = entity.OnReset;
|
|
function entity:OnReset(...)
|
|
--System.Log("reset");
|
|
self.lastSpawnedEntity = nil;
|
|
if self.spawnedEntity then
|
|
System.RemoveEntity(self.spawnedEntity);
|
|
self.spawnedEntity = nil;
|
|
end
|
|
if self.whoSpawnedMe then
|
|
System.RemoveEntity( self.id );
|
|
return
|
|
end
|
|
_OnReset(self, ...);
|
|
end
|
|
|
|
local _OnEditorSetGameMode = entity.OnEditorSetGameMode;
|
|
function entity:OnEditorSetGameMode(...)
|
|
self.lastSpawnedEntity = nil;
|
|
if self.spawnedEntity then
|
|
self.spawnedEntity = nil;
|
|
end
|
|
|
|
if (_OnEditorSetGameMode) then
|
|
_OnEditorSetGameMode(self, ...);
|
|
end
|
|
end
|
|
|
|
-- allow flowgraph forwarding
|
|
function entity:GetFlowgraphForwardingEntity()
|
|
if (self.spawnedEntity) then
|
|
return self.spawnedEntity;
|
|
else
|
|
return self.lastSpawnedEntity;
|
|
end
|
|
end
|
|
-- OnSpawned event
|
|
function entity:Event_Spawned()
|
|
BroadcastEvent(self, "Spawned")
|
|
end
|
|
|
|
if not entity.FlowEvents then entity.FlowEvents = {} end
|
|
local fe = entity.FlowEvents
|
|
-- normalize events
|
|
fe.Inputs = fe.Inputs or {}
|
|
fe.Outputs = fe.Outputs or {}
|
|
|
|
-- collate events
|
|
local allEvents = {}
|
|
local name, data
|
|
for name, data in pairs(fe.Outputs) do
|
|
allEvents[name] = data
|
|
end
|
|
for name, data in pairs(fe.Inputs) do
|
|
allEvents[name] = data
|
|
end
|
|
|
|
-- event rebinding
|
|
for name, data in pairs(allEvents) do
|
|
local isInput = fe.Inputs[name]
|
|
local isOutput = fe.Outputs[name]
|
|
local isDeath = (name=="Dead")
|
|
local _event = data
|
|
if type(_event) == "table" then
|
|
_event = _event[1]
|
|
else
|
|
_event = nil
|
|
end
|
|
entity["Event_"..name] = function(self, sender, param)
|
|
-- auto broadcast received things for outputs
|
|
if isOutput and (sender and sender.id == self.spawnedEntity or sender==self) then
|
|
-- AI.LogEvent( ">>broadcasting output event "..name );
|
|
BroadcastEvent(self, name)
|
|
end
|
|
-- forward events where necessary
|
|
if isInput and (self.spawnedEntity and ((not sender) or (self.spawnedEntity ~= sender.id))) then
|
|
local ent = System.GetEntity(self.spawnedEntity)
|
|
if _event and ent and ent ~= sender then
|
|
_event(ent, sender, param)
|
|
end
|
|
elseif _event and not self.spawnedEntity then
|
|
-- and pass through where not
|
|
_event(self, sender, param)
|
|
end
|
|
-- handle death events
|
|
if isDeath and (sender and sender.id == self.spawnedEntity) then
|
|
self.spawnedEntity = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
-- spawn event
|
|
function entity:Event_Spawn()
|
|
|
|
local entityIdDone = self:Event_Spawn_Internal();
|
|
|
|
-- the entity needs the output being activated, is not enough to just activate the output on the entity that spawnedMe,
|
|
-- because the flowgraph could be already forwarded to the newly spawned entity (if the entity does not have the flowgraph associated, the output event will be just ignored)
|
|
if (entityIdDone ~= self.id) then
|
|
self:ActivateOutput("Spawned", self.id);
|
|
end
|
|
end
|
|
|
|
|
|
function entity:Event_Spawn_Internal()
|
|
if self.whoSpawnedMe then
|
|
-- we were spawned (and not placed on a level)...
|
|
-- GetForwardingEntity will make sure that this event
|
|
-- is sent here first, but this event *MUST* be handled
|
|
-- by our spawner
|
|
return self.whoSpawnedMe:Event_Spawn_Internal()
|
|
else
|
|
if self.spawnedEntity then
|
|
return nil
|
|
end
|
|
local params = {
|
|
class = self.class;
|
|
position = self:GetPos(),
|
|
orientation = self:GetDirectionVector(1),
|
|
scale = self:GetScale(),
|
|
archetype = self:GetArchetype(),
|
|
properties = self.Properties,
|
|
propertiesInstance = self.PropertiesInstance,
|
|
}
|
|
if (self.InitialPosition) then
|
|
params.position = self.InitialPosition;
|
|
end
|
|
if self.Properties.SpawnedEntityName ~= "" then
|
|
params.name = self.Properties.SpawnedEntityName
|
|
else
|
|
params.name = self:GetName().."_s"
|
|
|
|
end
|
|
local ent = System.SpawnEntity(params, self.id)
|
|
if ent then
|
|
self.spawnedEntity = ent.id
|
|
self.lastSpawnedEntity = ent.id;
|
|
if not ent.Events then ent.Events = {} end
|
|
local evts = ent.Events
|
|
for name, data in pairs(self.FlowEvents.Outputs) do
|
|
if not evts[name] then evts[name] = {} end
|
|
table.insert(evts[name], {self.id, name})
|
|
end
|
|
ent.whoSpawnedMe = self;
|
|
|
|
ent:SetupTerritoryAndWave();
|
|
|
|
--self:Event_Spawned();
|
|
self:ActivateOutput("Spawned", ent.id);
|
|
return self.id;
|
|
end
|
|
end
|
|
end
|
|
|
|
-- spawn event keep
|
|
function entity:Event_SpawnKeep()
|
|
local params =
|
|
{
|
|
class = self.class;
|
|
position = self:GetPos(),
|
|
orientation = self:GetDirectionVector(1),
|
|
scale = self:GetScale(),
|
|
archetype = self:GetArchetype(),
|
|
properties = self.Properties,
|
|
propertiesInstance = self.PropertiesInstance,
|
|
}
|
|
local rndOffset = 1;
|
|
params.position.x = params.position.x + random(0,rndOffset*2)-rndOffset;
|
|
params.position.y = params.position.y + random(0,rndOffset*2)-rndOffset;
|
|
params.name = self:GetName()
|
|
local ent = System.SpawnEntity(params, self.id)
|
|
if ent then
|
|
self.spawnedEntity = ent.id
|
|
self.lastSpawnedEntity = ent.id;
|
|
if not ent.Events then ent.Events = {} end
|
|
local evts = ent.Events
|
|
for name, data in pairs(self.FlowEvents.Outputs) do
|
|
if not evts[name] then evts[name] = {} end
|
|
table.insert(evts[name], {self.id, name})
|
|
end
|
|
-- ent.whoSpawnedMe = self;
|
|
--self:Event_Spawned();
|
|
self:ActivateOutput("Spawned", ent.id);
|
|
end
|
|
end
|
|
|
|
-- hidhing/unhiding should be done inside disable/enable
|
|
-- function entity:Event_Hide()
|
|
-- self:Hide(1)
|
|
-- end
|
|
|
|
fe.Inputs["Spawn"] = {entity.Event_Spawn, "bool"}
|
|
-- fe.Inputs["Hide"] = {entity.Event_Hide, "bool"}
|
|
fe.Outputs["Spawned"] = "entity";
|
|
end
|
|
|
|
|
|
-----------------------------------------------------------------------------------------
|
|
-- Setup the collision filtering for the entity
|
|
-----------------------------------------------------------------------------------------
|
|
function SetupCollisionFiltering( entity )
|
|
-- Have we got an entity and a physics collision filtering table
|
|
if (entity == nil) then return end
|
|
if (entity.Properties == nil) then entity.Properties = {} end
|
|
if (entity.Properties.Physics == nil) then entity.Properties.Physics = {} end
|
|
|
|
-- Populate the table with the basic collision classes
|
|
-- These are enumurated in the global g_PhysicsCollisionClass
|
|
-- which is created by the game code
|
|
entity.Properties.Physics.CollisionFiltering = {};
|
|
local c = entity.Properties.Physics.CollisionFiltering;
|
|
c.collisionType = {}
|
|
c.collisionIgnore = {}
|
|
if (g_PhysicsCollisionClass == nil) then return end
|
|
for i, v in pairs(g_PhysicsCollisionClass) do
|
|
c.collisionType[i] = 0;
|
|
c.collisionIgnore[i] = 0;
|
|
end
|
|
end
|
|
|
|
|
|
function GetCollisionFiltering( entity )
|
|
local output = {}
|
|
output.collisionClass = 0;
|
|
output.collisionClassIgnore = 0;
|
|
if (entity.Properties.Physics==nil) then return output; end
|
|
if (entity.Properties.Physics.CollisionFiltering==nil) then return output; end
|
|
local c = entity.Properties.Physics.CollisionFiltering;
|
|
for i,v in pairs(c.collisionType) do
|
|
local gameSideFlag = g_PhysicsCollisionClass[i];
|
|
if (gameSideFlag ~= nil and v==1) then
|
|
output.collisionClass = output.collisionClass + gameSideFlag;
|
|
end
|
|
end
|
|
for i,v in pairs(c.collisionIgnore) do
|
|
local gameSideFlag = g_PhysicsCollisionClass[i];
|
|
if (gameSideFlag ~= nil and v==1) then
|
|
output.collisionClassIgnore = output.collisionClassIgnore + gameSideFlag;
|
|
end
|
|
end
|
|
return output;
|
|
end
|
|
|
|
function ApplyCollisionFiltering(entity, filtering)
|
|
if (filtering.collisionClass ~= 0 or filtering.collisionClassIgnore ~= 0) then
|
|
entity:SetPhysicParams(PHYSICPARAM_COLLISION_CLASS, filtering );
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------------------------------------
|
|
-- Physicalize rigid body.
|
|
---------------------------------------------------------------------------------------------------
|
|
--GlobalPhysicsSimParams = { max_logged_collisions = 1 };
|
|
|
|
|
|
EntityCommon.PhysicalizeRigid = function( entity,nSlot,Properties,bActive )
|
|
local Mass = Properties.Mass;
|
|
local Density = Properties.Density;
|
|
if bActive and bActive==0 then
|
|
Mass = 0.0; Density = 0.0;
|
|
end
|
|
|
|
local physType;
|
|
|
|
if (Properties.bArticulated == 1) then
|
|
physType = PE_ARTICULATED;
|
|
else
|
|
if (Properties.bRigidBody == 1) then
|
|
physType = PE_RIGID;
|
|
else
|
|
physType = PE_STATIC;
|
|
end
|
|
|
|
end
|
|
|
|
local TempPhysParams = EntityCommon.TempPhysParams;
|
|
|
|
TempPhysParams.density = Density;
|
|
TempPhysParams.mass = Mass;
|
|
TempPhysParams.flags = 0;
|
|
|
|
if (Properties.CGFPropsOverride) then
|
|
TempPhysParams.CGFprops = "";
|
|
for key,value in pairs(Properties.CGFPropsOverride) do
|
|
if (type(value)=="table") then
|
|
for key1,value1 in pairs(value) do
|
|
if (value1~="") then
|
|
TempPhysParams.CGFprops = TempPhysParams.CGFprops..key1.."="..value1.."\n";
|
|
end
|
|
end
|
|
else
|
|
if (value~="") then
|
|
TempPhysParams.CGFprops = TempPhysParams.CGFprops..key.."="..value.."\n";
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
entity:Physicalize( nSlot, physType, TempPhysParams );
|
|
|
|
--entity:SetPhysicParams(PHYSICPARAM_SIMULATION, GlobalPhysicsSimParams );
|
|
|
|
if (Mass>0 or Density>0) then
|
|
ApplyCollisionFiltering(entity, { collisionClass=gcc_rigid; } );
|
|
end
|
|
|
|
if(Properties.bInteractLargeObject==1) then
|
|
ApplyCollisionFiltering(entity, { collisionClass=gcc_large_kickable; } );
|
|
end
|
|
|
|
ApplyCollisionFiltering(entity, GetCollisionFiltering(entity));
|
|
|
|
|
|
if (Properties.Simulation) then
|
|
local SimulationSrc = Properties.Simulation;
|
|
local SimulationDst = EntityCommon.TempSimulationParams;
|
|
for key,value in next,SimulationDst,nil do SimulationDst[key] = nil; end
|
|
for key,value in next,SimulationSrc,nil do
|
|
if (key~="max_time_step" or value<0.0199 or value>0.0201) and (key~="sleep_speed" or value<0.0399 or value>0.0401) then SimulationDst[key] = value; end
|
|
end
|
|
entity:SetPhysicParams(PHYSICPARAM_SIMULATION, SimulationDst);
|
|
end
|
|
|
|
local Buoyancy = Properties.Buoyancy;
|
|
if (Buoyancy) then
|
|
entity:SetPhysicParams(PHYSICPARAM_BUOYANCY, Buoyancy);
|
|
end
|
|
|
|
local ForeignData = Properties.ForeignData;
|
|
if(ForeignData and ForeignData.bMovingPlatform == 1) then
|
|
entity:SetPhysicParams(PHYSICPARAM_FOREIGNDATA, { foreignFlags=FOREIGNFLAGS_MOVING_PLATFORM } );
|
|
end
|
|
|
|
-----------------------------------------------------------------------------
|
|
-- Set physical flags.
|
|
-----------------------------------------------------------------------------
|
|
local PhysFlags = EntityCommon.TempPhysicsFlags;
|
|
PhysFlags.flags = 0;
|
|
if (Properties.bPushableByPlayers == 1) then
|
|
PhysFlags.flags = pef_pushable_by_players;
|
|
end
|
|
if (Simulation and Simulation.bFixedDamping and Simulation.bFixedDamping==1) then
|
|
PhysFlags.flags = PhysFlags.flags+pef_fixed_damping;
|
|
end
|
|
if (Simulation and Simulation.bUseSimpleSolver and Simulation.bUseSimpleSolver==1) then
|
|
PhysFlags.flags = PhysFlags.flags+ref_use_simple_solver;
|
|
end
|
|
if (Properties.bCanBreakOthers==nil or Properties.bCanBreakOthers==0) then
|
|
PhysFlags.flags = PhysFlags.flags+pef_never_break;
|
|
end
|
|
if (Properties.MP and Properties.MP.bClientOnly) then
|
|
-- allow breaking on the client
|
|
PhysFlags.flags = PhysFlags.flags+pef_override_impulse_scale;
|
|
end
|
|
PhysFlags.flags_mask = pef_fixed_damping + ref_use_simple_solver + pef_pushable_by_players + pef_never_break + pef_override_impulse_scale;
|
|
entity:SetPhysicParams( PHYSICPARAM_FLAGS,PhysFlags );
|
|
-----------------------------------------------------------------------------
|
|
|
|
if (Properties.bResting == 0) then
|
|
entity:AwakePhysics(1);
|
|
else
|
|
entity:AwakePhysics(0);
|
|
end
|
|
end
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- Compare entities by name (for table.sort)
|
|
-------------------------------------------------------------------------------
|
|
|
|
function CompareEntitiesByName( ent1, ent2 )
|
|
return ent1:GetName() < ent2:GetName()
|
|
end
|
|
|
|
function MakeCompareEntitiesByDistanceFromPoint( point )
|
|
function CompareEntitiesByDistanceFromPoint( ent1, ent2 )
|
|
distance1 = DistanceSqVectors( ent1:GetWorldPos(), point )
|
|
distance2 = DistanceSqVectors( ent2:GetWorldPos(), point )
|
|
return distance1 > distance2
|
|
end
|
|
return CompareEntitiesByDistanceFromPoint
|
|
end
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- Called by Pool System when an Entity is bookmarked for pool usage
|
|
-- - Gives us its EntityId and PropertiesInstance tables for logic-driven utilities
|
|
-------------------------------------------------------------------------------
|
|
|
|
function OnEntityBookmarkCreated( entityId, propertiesInstance )
|
|
|
|
local waveName = nil;
|
|
if (propertiesInstance and propertiesInstance.AITerritoryAndWave) then
|
|
waveName = propertiesInstance.AITerritoryAndWave.aiwave_Wave;
|
|
end
|
|
|
|
if (waveName and waveName ~= "<None>") then
|
|
|
|
-- Notify territory and wave
|
|
AddBookmarkedToWave(entityId, waveName);
|
|
return false;
|
|
|
|
end
|
|
|
|
return true;
|
|
|
|
end |