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,18 @@ // Launch the graphical projectile. let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); - let id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity); + + let actorName = ""; + let hitActorName = ""; + let hitAnimationTime = 0; + if (this.template.Ranged.Projectile) { + actorName = this.template.Ranged.Projectile.Actor || ""; + hitActorName = this.template.Ranged.Projectile.HitActor || ""; + hitAnimationTime = this.template.Ranged.Projectile.HitAnimationTime || 0; + } + + let id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity, + actorName, hitActorName, hitAnimationTime); 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,11 @@ 5000 4.0 0 + + props/units/weapons/rock_flaming.xml + 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 @@ -107,13 +107,17 @@ } } - virtual uint32_t LaunchProjectileAtPoint(entity_id_t source, const CFixedVector3D& target, fixed speed, fixed gravity) + virtual uint32_t LaunchProjectileAtPoint(entity_id_t source, const CFixedVector3D& target, fixed speed, fixed gravity, + const std::wstring& actorName, const std::wstring& hitActorName, fixed hitAnimationTime) { - return LaunchProjectile(source, target, speed, gravity); + return LaunchProjectile(source, target, speed, gravity, actorName, hitActorName, hitAnimationTime); } 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 +130,8 @@ float gravity; bool stopped; uint32_t id; + std::wstring hitName; + float hitAnimationTime; CVector3D position(float t) { @@ -141,13 +147,22 @@ } }; + struct ProjectileHitAnimation { + CUnit* unit; + CVector3D pos; + float time; + }; + std::vector m_Projectiles; + std::vector m_ProjectileHitAnimations; + uint32_t m_ActorSeed; uint32_t m_NextId; - uint32_t LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, fixed speed, fixed gravity); + uint32_t LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, fixed speed, fixed gravity, + const std::wstring& actorName, const std::wstring& hitActorName, fixed hitAnimationTime); void AdvanceProjectile(Projectile& projectile, float dt) const; @@ -158,7 +173,8 @@ REGISTER_COMPONENT_TYPE(ProjectileManager) -uint32_t CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, fixed speed, fixed gravity) +uint32_t CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, fixed speed, fixed gravity, + const std::wstring& actorName, const std::wstring& hitActorName, fixed hitAnimationTime) { // This is network synced so don't use GUI checks before incrementing or it breaks any non GUI simulations uint32_t currentId = m_NextId++; @@ -166,26 +182,48 @@ if (!GetSimContext().HasUnitManager()) 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(); + CmpPtr cmpSourceVisualActor(GetSimContext(), source); + if (!cmpSourceVisualActor) + return currentId; + + std::wstring name; + if (!actorName.empty()) + { + name = actorName; + } + else + { + // TODO: remove this fallback to the cmpSourceVisualActor when all the templates are updated with the projectile actor name + name = cmpSourceVisualActor->GetProjectileActor(); + if (name.empty()) + { + // If the actor was actually loaded, complain that it doesn't have a projectile + if (!cmpSourceVisualActor->GetActorShortName().empty()) + LOGERROR( + "Unit with actor '%s' launched a projectile but has no actor on 'projectile' attachpoint", + utf8_from_wstring(cmpSourceVisualActor->GetActorShortName())); + return currentId; + } + } + + if (!hitActorName.empty()) + { + projectile.hitName = hitActorName; + projectile.hitAnimationTime = hitAnimationTime.ToFloat(); + } + else + { + projectile.hitName = L""; + projectile.hitAnimationTime = 0.0f; + } + + projectile.origin = cmpSourceVisualActor->GetProjectileLaunchPoint(); if (!projectile.origin) { // If there's no explicit launch point, take a guess based on the entity position @@ -284,19 +322,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].hitName.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].hitName, m_ActorSeed++, selections); + unit->GetModel().SetTransform(transform); + + ProjectileHitAnimation projectileHitAnimation; + projectileHitAnimation.unit = unit; + projectileHitAnimation.time = m_Projectiles[i].hitAnimationTime; + projectileHitAnimation.pos = m_Projectiles[i].pos; + m_ProjectileHitAnimations.push_back(projectileHitAnimation); } - ++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_ProjectileHitAnimations.size();) + { + if (m_ProjectileHitAnimations[i].time > 0) + { + m_ProjectileHitAnimations[i].time -= frameTime; + ++i; + } + else + { + std::swap(m_ProjectileHitAnimations[i], m_ProjectileHitAnimations.back()); + GetSimContext().GetUnitManager().DeleteUnit(m_ProjectileHitAnimations.back().unit); + m_ProjectileHitAnimations.pop_back(); + } } } @@ -316,6 +384,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 +410,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 ProjectileHitAnimation& projectileHitAnimation : m_ProjectileHitAnimations) + { + RenderModel(projectileHitAnimation.unit->GetModel(), projectileHitAnimation.pos, + collector, frustum, culling, los, losRevealAll); } } Index: source/simulation2/components/ICmpProjectileManager.h =================================================================== --- source/simulation2/components/ICmpProjectileManager.h +++ source/simulation2/components/ICmpProjectileManager.h @@ -38,9 +38,13 @@ * @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 hitActorName name of the animation actor played when the projectile hits the target or the ground + * @param hitAnimationTime 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(entity_id_t source, const CFixedVector3D& target, fixed speed, fixed gravity, + const std::wstring& actorName, const std::wstring& hitActorName, fixed hitAnimationTime) = 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, entity_id_t, CFixedVector3D, fixed, fixed, std::wstring, std::wstring, fixed) DEFINE_INTERFACE_METHOD_1("RemoveProjectile", void, ICmpProjectileManager, RemoveProjectile, uint32_t) END_INTERFACE_WRAPPER(ProjectileManager)