Index: binaries/data/mods/public/art/actors/props/units/weapons/rock_explosion.xml =================================================================== --- binaries/data/mods/public/art/actors/props/units/weapons/rock_explosion.xml +++ binaries/data/mods/public/art/actors/props/units/weapons/rock_explosion.xml @@ -0,0 +1,15 @@ + + + + + + props/onager_projectile.dae + + + + + + + + + Index: binaries/data/mods/public/art/actors/props/units/weapons/rock_flaming.xml =================================================================== --- binaries/data/mods/public/art/actors/props/units/weapons/rock_flaming.xml +++ binaries/data/mods/public/art/actors/props/units/weapons/rock_flaming.xml @@ -8,7 +8,9 @@ - + + + Index: binaries/data/mods/public/simulation/components/Attack.js =================================================================== --- binaries/data/mods/public/simulation/components/Attack.js +++ binaries/data/mods/public/simulation/components/Attack.js @@ -81,6 +81,11 @@ "2" + "" + "" + + "" + + "props/units/weapons/rock_flaming.xml" + + "props/units/weapons/rock_explosion.xml" + + "0.1" + + "" + "Champion" + "" + "Circular" + @@ -142,6 +147,27 @@ Attack.prototype.preferredClassesSchema + Attack.prototype.restrictedClassesSchema + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + "" + "" + "" + @@ -494,7 +520,29 @@ // Launch the graphical projectile. let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); - let id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity); + + let actorName = ""; + let impactActorName = ""; + let impactAnimationLifetime = 0; + if (this.template.Ranged.Projectile) { + actorName = this.template.Ranged.Projectile.ActorName || ""; + impactActorName = this.template.Ranged.Projectile.ImpactActorName || ""; + impactAnimationLifetime = this.template.Ranged.Projectile.ImpactAnimationLifetime || 0; + } + + let launchPoint = Object.assign({}, selfPosition); + launchPoint.y += 3; + let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); + if (cmpVisual) { + if (!actorName) + actorName = cmpVisual.GetProjectileActor(); + + let visualActorLaunchPoint = cmpVisual.GetProjectileLaunchPoint(); + if (visualActorLaunchPoint.length() > 0) + launchPoint = visualActorLaunchPoint; + } + + let id = cmpProjectileManager.LaunchProjectileAtPoint(launchPoint, realTargetPosition, horizSpeed, gravity, actorName, impactActorName, impactAnimationLifetime); let attackImpactSound = ""; let cmpSound = Engine.QueryInterface(this.entity, IID_Sound); Index: binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml +++ binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml @@ -13,6 +13,10 @@ 5000 4.0 0 + + props/units/weapons/rock_explosion.xml + 0.1 + Circular 10 Index: source/simulation2/components/CCmpProjectileManager.cpp =================================================================== --- source/simulation2/components/CCmpProjectileManager.cpp +++ source/simulation2/components/CCmpProjectileManager.cpp @@ -25,7 +25,6 @@ #include "ICmpPosition.h" #include "ICmpRangeManager.h" #include "ICmpTerrain.h" -#include "ICmpVisual.h" #include "simulation2/MessageTypes.h" #include "graphics/Frustum.h" @@ -107,13 +106,16 @@ } } - virtual uint32_t LaunchProjectileAtPoint(entity_id_t source, const CFixedVector3D& target, fixed speed, fixed gravity) + virtual uint32_t LaunchProjectileAtPoint(const CFixedVector3D& launchPoint, const CFixedVector3D& target, fixed speed, fixed gravity, const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime) { - return LaunchProjectile(source, target, speed, gravity); + return LaunchProjectile(launchPoint, target, speed, gravity, actorName, impactActorName, impactAnimationLifetime); } virtual void RemoveProjectile(uint32_t); + void RenderModel(CModelAbstract& model, const CVector3D& position, SceneCollector& collector, const CFrustum& frustum, bool culling, + ICmpRangeManager::CLosQuerier los, bool losRevealAll) const; + private: struct Projectile { @@ -126,6 +128,8 @@ float gravity; bool stopped; uint32_t id; + std::wstring impactActorName; + float impactAnimationLifetime; CVector3D position(float t) { @@ -141,13 +145,22 @@ } }; + struct ProjectileImpactAnimation { + CUnit* unit; + CVector3D pos; + float time; + }; + std::vector m_Projectiles; + std::vector m_ProjectileImpactAnimations; + uint32_t m_ActorSeed; uint32_t m_NextId; - uint32_t LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, fixed speed, fixed gravity); + uint32_t LaunchProjectile(CFixedVector3D launchPoint, CFixedVector3D targetPoint, fixed speed, fixed gravity, + const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime); void AdvanceProjectile(Projectile& projectile, float dt) const; @@ -158,46 +171,35 @@ REGISTER_COMPONENT_TYPE(ProjectileManager) -uint32_t CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, fixed speed, fixed gravity) +uint32_t CCmpProjectileManager::LaunchProjectile(CFixedVector3D launchPoint, CFixedVector3D targetPoint, fixed speed, fixed gravity, const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime) { // This is network synced so don't use GUI checks before incrementing or it breaks any non GUI simulations uint32_t currentId = m_NextId++; - if (!GetSimContext().HasUnitManager()) + if (!GetSimContext().HasUnitManager() || actorName.empty()) return currentId; // do nothing if graphics are disabled - CmpPtr cmpSourceVisual(GetSimContext(), source); - if (!cmpSourceVisual) - return currentId; - - std::wstring name = cmpSourceVisual->GetProjectileActor(); - if (name.empty()) - { - // If the actor was actually loaded, complain that it doesn't have a projectile - if (!cmpSourceVisual->GetActorShortName().empty()) - LOGERROR("Unit with actor '%s' launched a projectile but has no actor on 'projectile' attachpoint", utf8_from_wstring(cmpSourceVisual->GetActorShortName())); - return currentId; - } - Projectile projectile; projectile.id = currentId; projectile.time = 0.f; projectile.stopped = false; projectile.gravity = gravity.ToFloat(); - projectile.origin = cmpSourceVisual->GetProjectileLaunchPoint(); - if (!projectile.origin) + if (!impactActorName.empty()) + { + projectile.impactActorName = impactActorName; + projectile.impactAnimationLifetime = impactAnimationLifetime.ToFloat(); + } + else { - // If there's no explicit launch point, take a guess based on the entity position - CmpPtr sourcePos(GetSimContext(), source); - if (!sourcePos) - return currentId; - projectile.origin = sourcePos->GetPosition(); - projectile.origin.Y += 3.f; + projectile.impactActorName = L""; + projectile.impactAnimationLifetime = 0.0f; } + projectile.origin = launchPoint; + std::set selections; - projectile.unit = GetSimContext().GetUnitManager().CreateUnit(name, m_ActorSeed++, selections); + projectile.unit = GetSimContext().GetUnitManager().CreateUnit(actorName, m_ActorSeed++, selections); if (!projectile.unit) // The error will have already been logged return currentId; @@ -284,19 +286,49 @@ { // Projectiles hitting targets get removed immediately. // Those hitting the ground stay for a while, because it looks pretty. - if (m_Projectiles[i].stopped) + if (!m_Projectiles[i].stopped) { - if (m_Projectiles[i].time - m_Projectiles[i].timeHit > PROJECTILE_DECAY_TIME) - { - // Delete in-place by swapping with the last in the list - std::swap(m_Projectiles[i], m_Projectiles.back()); - GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles.back().unit); - m_Projectiles.pop_back(); - continue; // don't increment i - } + ++i; + continue; + } + + if (!m_Projectiles[i].impactActorName.empty()) + { + CMatrix3D transform; + CQuaternion quat; + quat.ToMatrix(transform); + transform.Translate(m_Projectiles[i].pos); + + std::set selections; + CUnit* unit = GetSimContext().GetUnitManager().CreateUnit(m_Projectiles[i].impactActorName, m_ActorSeed++, selections); + unit->GetModel().SetTransform(transform); + + ProjectileImpactAnimation projectileImpactAnimation; + projectileImpactAnimation.unit = unit; + projectileImpactAnimation.time = m_Projectiles[i].impactAnimationLifetime; + projectileImpactAnimation.pos = m_Projectiles[i].pos; + m_ProjectileImpactAnimations.push_back(projectileImpactAnimation); } - ++i; + // Delete in-place by swapping with the last in the list + std::swap(m_Projectiles[i], m_Projectiles.back()); + GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles.back().unit); + m_Projectiles.pop_back(); + } + + for (size_t i = 0; i < m_ProjectileImpactAnimations.size();) + { + if (m_ProjectileImpactAnimations[i].time > 0) + { + m_ProjectileImpactAnimations[i].time -= frameTime; + ++i; + } + else + { + std::swap(m_ProjectileImpactAnimations[i], m_ProjectileImpactAnimations.back()); + GetSimContext().GetUnitManager().DeleteUnit(m_ProjectileImpactAnimations.back().unit); + m_ProjectileImpactAnimations.pop_back(); + } } } @@ -316,6 +348,25 @@ } } +void CCmpProjectileManager::RenderModel(CModelAbstract& model, const CVector3D& position, SceneCollector& collector, + const CFrustum& frustum, bool culling, ICmpRangeManager::CLosQuerier los, bool losRevealAll) const +{ + // Don't display objects outside the visible area + ssize_t posi = (ssize_t)(0.5f + position.X / TERRAIN_TILE_SIZE); + ssize_t posj = (ssize_t)(0.5f + position.Z / TERRAIN_TILE_SIZE); + if (!losRevealAll && !los.IsVisible(posi, posj)) + return; + + model.ValidatePosition(); + + if (culling && !frustum.IsBoxVisible(model.GetWorldBoundsRec())) + return; + + // TODO: do something about LOS (copy from CCmpVisualActor) + + collector.SubmitRecursive(&model); +} + void CCmpProjectileManager::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling) const { CmpPtr cmpRangeManager(GetSystemEntity()); @@ -323,23 +374,14 @@ ICmpRangeManager::CLosQuerier los(cmpRangeManager->GetLosQuerier(player)); bool losRevealAll = cmpRangeManager->GetLosRevealAll(player); - for (size_t i = 0; i < m_Projectiles.size(); ++i) + for (const Projectile& projectile : m_Projectiles) { - // Don't display projectiles outside the visible area - ssize_t posi = (ssize_t)(0.5f + m_Projectiles[i].pos.X / TERRAIN_TILE_SIZE); - ssize_t posj = (ssize_t)(0.5f + m_Projectiles[i].pos.Z / TERRAIN_TILE_SIZE); - if (!losRevealAll && !los.IsVisible(posi, posj)) - continue; - - CModelAbstract& model = m_Projectiles[i].unit->GetModel(); - - model.ValidatePosition(); - - if (culling && !frustum.IsBoxVisible(model.GetWorldBoundsRec())) - continue; - - // TODO: do something about LOS (copy from CCmpVisualActor) + RenderModel(projectile.unit->GetModel(), projectile.pos, collector, frustum, culling, los, losRevealAll); + } - collector.SubmitRecursive(&model); + for (const ProjectileImpactAnimation& projectileImpactAnimation : m_ProjectileImpactAnimations) + { + RenderModel(projectileImpactAnimation.unit->GetModel(), projectileImpactAnimation.pos, + collector, frustum, culling, los, losRevealAll); } } Index: source/simulation2/components/CCmpVisualActor.cpp =================================================================== --- source/simulation2/components/CCmpVisualActor.cpp +++ source/simulation2/components/CCmpVisualActor.cpp @@ -387,10 +387,10 @@ return m_Unit->GetObject().m_ProjectileModelName; } - virtual CVector3D GetProjectileLaunchPoint() const + virtual CFixedVector3D GetProjectileLaunchPoint() const { if (!m_Unit) - return CVector3D(); + return CFixedVector3D(); if (m_Unit->GetModel().ToCModel()) { @@ -407,10 +407,13 @@ CModelAbstract* ammo = m_Unit->GetModel().ToCModel()->FindFirstAmmoProp(); if (ammo) - return ammo->GetTransform().GetTranslation(); + { + CVector3D vector = ammo->GetTransform().GetTranslation(); + return CFixedVector3D(fixed::FromFloat(vector.X), fixed::FromFloat(vector.Y), fixed::FromFloat(vector.Z)); + } } - return CVector3D(); + return CFixedVector3D(); } virtual void SetVariant(const CStr& key, const CStr& selection) Index: source/simulation2/components/ICmpProjectileManager.h =================================================================== --- source/simulation2/components/ICmpProjectileManager.h +++ source/simulation2/components/ICmpProjectileManager.h @@ -38,9 +38,12 @@ * @param target target point * @param speed horizontal speed in m/s * @param gravity gravitational acceleration in m/s^2 (determines the height of the ballistic curve) + * @param actorName name of the flying projectile actor + * @param impactActorName name of the animation actor played when the projectile hits the target or the ground + * @param impactAnimationLifetime animation lenth * @return id of the created projectile */ - virtual uint32_t LaunchProjectileAtPoint(entity_id_t source, const CFixedVector3D& target, fixed speed, fixed gravity) = 0; + virtual uint32_t LaunchProjectileAtPoint(const CFixedVector3D& launchPoint, const CFixedVector3D& target, fixed speed, fixed gravity, const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime) = 0; /** * Removes a projectile, used when the projectile has hit a target Index: source/simulation2/components/ICmpProjectileManager.cpp =================================================================== --- source/simulation2/components/ICmpProjectileManager.cpp +++ source/simulation2/components/ICmpProjectileManager.cpp @@ -22,6 +22,6 @@ #include "simulation2/system/InterfaceScripted.h" BEGIN_INTERFACE_WRAPPER(ProjectileManager) -DEFINE_INTERFACE_METHOD_4("LaunchProjectileAtPoint", uint32_t, ICmpProjectileManager, LaunchProjectileAtPoint, entity_id_t, CFixedVector3D, fixed, fixed) +DEFINE_INTERFACE_METHOD_7("LaunchProjectileAtPoint", uint32_t, ICmpProjectileManager, LaunchProjectileAtPoint, CFixedVector3D, CFixedVector3D, fixed, fixed, std::wstring, std::wstring, fixed) DEFINE_INTERFACE_METHOD_1("RemoveProjectile", void, ICmpProjectileManager, RemoveProjectile, uint32_t) END_INTERFACE_WRAPPER(ProjectileManager) Index: source/simulation2/components/ICmpVisual.h =================================================================== --- source/simulation2/components/ICmpVisual.h +++ source/simulation2/components/ICmpVisual.h @@ -24,6 +24,7 @@ #include "maths/BoundingBoxOriented.h" #include "maths/BoundingBoxAligned.h" #include "maths/Fixed.h" +#include "maths/FixedVector3D.h" #include "lib/file/vfs/vfs_path.h" class CUnit; @@ -70,7 +71,7 @@ * ammo prop points). * Returns (0,0,0) if no point can be found. */ - virtual CVector3D GetProjectileLaunchPoint() const = 0; + virtual CFixedVector3D GetProjectileLaunchPoint() const = 0; /** * Returns the underlying unit of this visual actor. May return NULL to indicate that no unit exists (e.g. may happen if the Index: source/simulation2/components/ICmpVisual.cpp =================================================================== --- source/simulation2/components/ICmpVisual.cpp +++ source/simulation2/components/ICmpVisual.cpp @@ -24,6 +24,8 @@ BEGIN_INTERFACE_WRAPPER(Visual) DEFINE_INTERFACE_METHOD_2("SetVariant", void, ICmpVisual, SetVariant, CStr, CStr) DEFINE_INTERFACE_METHOD_CONST_0("GetAnimationName", std::string, ICmpVisual, GetAnimationName) +DEFINE_INTERFACE_METHOD_CONST_0("GetProjectileActor", std::wstring, ICmpVisual, GetProjectileActor) +DEFINE_INTERFACE_METHOD_CONST_0("GetProjectileLaunchPoint", CFixedVector3D, ICmpVisual, GetProjectileLaunchPoint) DEFINE_INTERFACE_METHOD_4("SelectAnimation", void, ICmpVisual, SelectAnimation, std::string, bool, fixed, std::wstring) DEFINE_INTERFACE_METHOD_1("SelectMovementAnimation", void, ICmpVisual, SelectMovementAnimation, fixed) DEFINE_INTERFACE_METHOD_1("ResetMoveAnimation", void, ICmpVisual, ResetMoveAnimation, std::string)