diff --git a/core/lua/Actor.lua b/core/lua/Actor.lua index 9959fd9..9a47321 100644 --- a/core/lua/Actor.lua +++ b/core/lua/Actor.lua @@ -15,14 +15,6 @@ class 'Actor' (Entity) Actor.kMapName = "actor" -Actor.PhysicsType = enum - { - 'None', // No physics representation. - 'Dynamic', // Bones are driven by physics simulation (client-side only) - 'DynamicServer', // Bones are driven by physics simulation (synced with server) - 'Kinematic' // Physics model is updated by animation - } - // Maximum number of animations we support on in a model. This is // limited for the sake of propagating animation indices. Actor.maxAnimations = 72 @@ -38,7 +30,7 @@ Actor.networkVars = // Reset to 1 every time a new animation is set. animationSpeed = "compensated float", - physicsType = "enum Actor.PhysicsType", + physicsType = "enum PhysicsType", physicsGroup = "integer (0 to 31)", } @@ -64,6 +56,8 @@ function Actor:OnCreate() Entity.OnCreate(self) + InitMixin(self, TimedCallbackMixin) + self.modelIndex = 0 self.animationSequence = Model.invalidSequence self.animationStart = 0 @@ -75,7 +69,7 @@ function Actor:OnCreate() self.animationSpeed = 1.0 self.boneCoords = CoordsArray() self.poseParams = PoseParams() - self.physicsType = Actor.PhysicsType.None + self.physicsType = PhysicsType.None self.physicsModel = nil self.physicsGroup = 0 //PhysicsGroup.DefaultGroup @@ -103,8 +97,6 @@ end function Actor:OnInit() - InitMixin(self, TimedCallbackMixin) - if Client then self:TriggerEffects("on_init") @@ -238,17 +230,17 @@ function Actor:UpdatePhysicsModelSimulation() if (self.physicsModel ~= nil) then - if (self.physicsType == Actor.PhysicsType.None) then + if (self.physicsType == PhysicsType.None) then self.physicsModel:SetPhysicsType(CollisionObject.None) - elseif (self.physicsType == Actor.PhysicsType.DynamicServer) then + elseif (self.physicsType == PhysicsType.DynamicServer) then if (Server) then self.physicsModel:SetPhysicsType(CollisionObject.Dynamic) else self.physicsModel:SetPhysicsType(CollisionObject.Kinematic) end - elseif (self.physicsType == Actor.PhysicsType.Dynamic) then + elseif (self.physicsType == PhysicsType.Dynamic) then self.physicsModel:SetPhysicsType(CollisionObject.Dynamic) - elseif (self.physicsType == Actor.PhysicsType.Kinematic) then + elseif (self.physicsType == PhysicsType.Kinematic) then self.physicsModel:SetPhysicsType(CollisionObject.Kinematic) end @@ -259,9 +251,9 @@ end function Actor:GetIsDynamic() if (Server) then - return self.physicsType == Actor.PhysicsType.DynamicServer + return self.physicsType == PhysicsType.DynamicServer else - return self.physicsType == Actor.PhysicsType.Dynamic + return self.physicsType == PhysicsType.Dynamic end end @@ -592,13 +584,6 @@ function Actor:OnUpdate(deltaTime) end - // Needed because of the problem where Actors can be created and updated on the client before being inited. - // "Spit created/destroyed in a single update" problem. Once that is fixed, this check won't be needed. - if HasMixin(self, "TimedCallback") then - // From TimedCallbackMixin. - self:UpdateTimedCallbacks(deltaTime) - end - end if (Client) then @@ -670,9 +655,9 @@ function Actor:UpdatePhysicsModelCoords() if (self.physicsModel ~= nil) then - local update = self.physicsType == Actor.PhysicsType.Kinematic or self.physicsType == Actor.PhysicsType.None + local update = self.physicsType == PhysicsType.Kinematic or self.physicsType == PhysicsType.None - if Client and self.physicsType == Actor.PhysicsType.DynamicServer then + if Client and self.physicsType == PhysicsType.DynamicServer then update = true end @@ -909,7 +894,7 @@ function Actor:OnUpdatePhysics() self:UpdateBoneCoords() self:UpdatePhysicsModel() - if (self.physicsType ~= Actor.PhysicsType.None) then + if (self.physicsType ~= PhysicsType.None) then if (self.physicsModel ~= nil and self:GetIsDynamic()) then diff --git a/ns2/gamestrings/enUS.txt b/ns2/gamestrings/enUS.txt index 6c98c4c..0d1cf6b 100644 --- a/ns2/gamestrings/enUS.txt +++ b/ns2/gamestrings/enUS.txt @@ -206,10 +206,10 @@ ENERGIZE = "Energize" DISORIENT = "Disorient" CLOACK = "Cloak" -PHANTASM = "Phantasm" -PHANTASM_FADE = "Phantasm Fade" -PHANTASM_ONOS = "Phantasm Onos" -PHANTASM_HIVE = "Phantasm Hive" +PHANTOM = "Phantom" +PHANTOM_FADE = "Phantom Fade" +PHANTOM_ONOS = "Phantom Onos" +PHANTOM_HIVE = "Phantom Hive" UNROOT_WHIP = "Unroot Whip" ROOT_WHIP = "Root Whip" @@ -323,8 +323,8 @@ SHIFT_TOOLTIP = "Speeds energy recovery for units and shift units around map" UPGRADE_SHIFT_TOOLTIP = "Increase Shift health and gain Echo ability" MATURE_SHIFT_TOOLTIP = "Shift with Echo ability" SHADE_TOOLTIP = "Cloaks nearby units and allows deception upgrades" -UPGRADE_SHADE_TOOLTIP = "Increase Shade health and grant Phantasm ability" -MATURE_SHADE_TOOLTIP = "Shade with Phantasm ability" +UPGRADE_SHADE_TOOLTIP = "Increase Shade health and grant Phantom ability" +MATURE_SHADE_TOOLTIP = "Shade with Phantom ability" CRAG_HEAL_TOOLTIP = "Heals players and structures (+10 every 2 seconds, max 3 targets)" SHIFT_ECHO_TOOLTIP = "Reposition structure elsewhere" SHIFT_RECALL_TOOLTIP = "Aliens can use the shift to teleport to the nearest hive" @@ -445,3 +445,4 @@ FOLLOWING = "Following %s" BEACONING = "Commander issued Distress Beacon. Teleport imminent." BEACONING_COMMANDER = "Distress Beacon triggered. Teleport imminent." TOO_MANY_ENTITES = "Too many entities in area." +ALIEN_HUD_PHANTOM = "Phantom (no damage)" diff --git a/ns2/lua/ARC.lua b/ns2/lua/ARC.lua index 77a5212..254551d 100644 --- a/ns2/lua/ARC.lua +++ b/ns2/lua/ARC.lua @@ -12,8 +12,12 @@ Script.Load("lua/LiveScriptActor.lua") Script.Load("lua/DoorMixin.lua") Script.Load("lua/mixins/ControllerMixin.lua") +Script.Load("lua/RagdollMixin.lua") +Script.Load("lua/UpgradableMixin.lua") +Script.Load("lua/PointGiverMixin.lua") Script.Load("lua/GameEffectsMixin.lua") Script.Load("lua/FlinchMixin.lua") +Script.Load("lua/SelectableMixin.lua") Script.Load("lua/TargetMixin.lua") class 'ARC' (LiveScriptActor) @@ -67,6 +71,7 @@ ARC.networkVars = targetDirection = "vector", } +PrepareClassForMixin(ARC, UpgradableMixin) PrepareClassForMixin(ARC, GameEffectsMixin) PrepareClassForMixin(ARC, FlinchMixin) @@ -75,10 +80,13 @@ function ARC:OnCreate() LiveScriptActor.OnCreate(self) InitMixin(self, ControllerMixin) + InitMixin(self, RagdollMixin) + InitMixin(self, UpgradableMixin) InitMixin(self, GameEffectsMixin) InitMixin(self, FlinchMixin) + InitMixin(self, PointGiverMixin) InitMixin(self, PathingMixin) - + InitMixin(self, SelectableMixin) if Server then InitMixin(self, TargetMixin) end @@ -103,7 +111,7 @@ function ARC:OnInit() { kMarineStaticTargets, kMarineMobileTargets }, { self.FilterTarget(self) }) - self:SetPhysicsType(Actor.PhysicsType.Kinematic) + self:SetPhysicsType(PhysicsType.Kinematic) // Cannons start out mobile self:SetDesiredMode(ARC.kMode.UndeployedStationary) @@ -211,7 +219,7 @@ function ARC:GetInAttackMode() return (self.mode == ARC.kMode.Deployed or self.mode == ARC.kMode.Firing or self.mode == ARC.kMode.Targeting or self.mode == ARC.kMode.FireCooldown) and self.desiredMode ~= ARC.kMode.UndeployedStationary end -function ARC:GetCanDoDamage() +function ARC:GetCanGiveDamageOverride() return true end diff --git a/ns2/lua/Alien.lua b/ns2/lua/Alien.lua index 3db002a..4d6e182 100644 --- a/ns2/lua/Alien.lua +++ b/ns2/lua/Alien.lua @@ -10,6 +10,7 @@ Script.Load("lua/Player.lua") Script.Load("lua/CloakableMixin.lua") Script.Load("lua/CamouflageMixin.lua") +Script.Load("lua/PhantomMixin.lua") class 'Alien' (Player) Alien.kMapName = "alien" @@ -65,6 +66,7 @@ Alien.networkVars = PrepareClassForMixin(Alien, CloakableMixin) PrepareClassForMixin(Alien, CamouflageMixin) +PrepareClassForMixin(Alien, PhantomMixin) function Alien:OnCreate() @@ -87,6 +89,7 @@ function Alien:OnInit() InitMixin(self, CloakableMixin) InitMixin(self, CamouflageMixin) + InitMixin(self, PhantomMixin) self.abilityEnergy = Ability.kMaxEnergy diff --git a/ns2/lua/AlienTeam.lua b/ns2/lua/AlienTeam.lua index 249e538..b56e792 100644 --- a/ns2/lua/AlienTeam.lua +++ b/ns2/lua/AlienTeam.lua @@ -131,7 +131,7 @@ function AlienTeam:UpdateTeamAutoHeal(timePassed) if self.timeOfLastAutoHeal == nil or (time > (self.timeOfLastAutoHeal + AlienTeam.kAutoHealInterval)) then // Heal all players by this amount - local teamEnts = GetEntitiesForTeam("LiveScriptActor", self:GetTeamNumber()) + local teamEnts = GetEntitiesWithMixinForTeam("Live", self:GetTeamNumber()) for index, entity in ipairs(teamEnts) do @@ -179,7 +179,7 @@ function AlienTeam:GetBlipType(entity) local blipType = kBlipType.Undefined - if entity:isa("LiveScriptActor") and entity:GetIsVisible() and entity:GetIsAlive() and not entity:isa("Infestation") then + if entity:GetIsVisible() and HasMixin(entity, "Live") and entity:GetIsAlive() and not entity:isa("Infestation") then if entity:GetTeamNumber() == self:GetTeamNumber() then @@ -478,7 +478,7 @@ function AlienTeam:InitTechTree() // Add special alien menus self.techTree:AddMenu(kTechId.MarkersMenu) self.techTree:AddMenu(kTechId.UpgradesMenu) - self.techTree:AddMenu(kTechId.ShadePhantasmMenu) + self.techTree:AddMenu(kTechId.ShadePhantomMenu) // Add markers (orders) self.techTree:AddSpecial(kTechId.ThreatMarker, true) @@ -551,16 +551,14 @@ function AlienTeam:InitTechTree() // Shade self.techTree:AddUpgradeNode(kTechId.UpgradeShade, kTechId.Shade, kTechId.None) - self.techTree:AddBuildNode(kTechId.MatureShade, kTechId.None, kTechId.None) + self.techTree:AddBuildNode(kTechId.MatureShade, kTechId.TwoHives, kTechId.None) self.techTree:AddActivation(kTechId.ShadeDisorient, kTechId.None, kTechId.None) self.techTree:AddActivation(kTechId.ShadeCloak, kTechId.None, kTechId.None) - // Shade targeted abilities - treat phantasms as build nodes so we show ghost and attach points for fake hive - self.techTree:AddResearchNode(kTechId.PhantasmTech, kTechId.MatureShade, kTechId.None) - self.techTree:AddBuildNode(kTechId.ShadePhantasmFade, kTechId.PhantasmTech, kTechId.MatureShade) - self.techTree:AddBuildNode(kTechId.ShadePhantasmOnos, kTechId.None, kTechId.None) - self.techTree:AddBuildNode(kTechId.ShadePhantasmHive, kTechId.PhantasmTech, kTechId.MatureShade) - + // Shade targeted abilities - treat Phantoms as build nodes so we show ghost and attach points for fake hive + self.techTree:AddResearchNode(kTechId.PhantomTech, kTechId.MatureShade, kTechId.None) + self.techTree:AddBuildNode(kTechId.ShadePhantomFade, kTechId.PhantomTech, kTechId.MatureShade) + self.techTree:AddBuildNode(kTechId.ShadePhantomOnos, kTechId.PhantomTech, kTechId.None) // Crag upgrades self.techTree:AddResearchNode(kTechId.AlienArmor1Tech, kTechId.Crag, kTechId.None) diff --git a/ns2/lua/AlienWeaponEffects.lua b/ns2/lua/AlienWeaponEffects.lua index 1f44980..b8ce640 100644 --- a/ns2/lua/AlienWeaponEffects.lua +++ b/ns2/lua/AlienWeaponEffects.lua @@ -354,11 +354,12 @@ kAlienWeaponEffects = { sporesAttackEffects = { - {looping_sound = "sound/ns2.fev/alien/lerk/spore_spray"}, {overlay_animation = "spore"}, - //{viewmodel_cinematic = "cinematics/alien/lerk/spore_view_fire.cinematic", attach_point = "?"}, - //{weapon_cinematic = "cinematics/alien/lerk/spores.cinematic", attach_point = "?"}, + {viewmodel_cinematic = "cinematics/alien/lerk/spore_view_fire.cinematic", attach_point = "fxnode_hole_left"}, + {viewmodel_cinematic = "cinematics/alien/lerk/spore_view_fire.cinematic", attach_point = "fxnode_hole_right"}, + {weapon_cinematic = "cinematics/alien/lerk/spore_fire.cinematic", attach_point = "fxnode_hole_left"}, + {weapon_cinematic = "cinematics/alien/lerk/spore_fire.cinematic", attach_point = "fxnode_hole_right"}, }, }, diff --git a/ns2/lua/Alien_Client.lua b/ns2/lua/Alien_Client.lua index 745d073..807bd20 100644 --- a/ns2/lua/Alien_Client.lua +++ b/ns2/lua/Alien_Client.lua @@ -420,8 +420,8 @@ end // Bring up evolve menu function Alien:Buy() - // Don't allow display in the ready room - if self:GetTeamNumber() ~= 0 and (Client.GetLocalPlayer() == self) then + // Don't allow display in the ready room, or as phantom + if self:GetTeamNumber() ~= 0 and (Client.GetLocalPlayer() == self) and (not HasMixin(self, "Phantom") or not self:GetIsPhantom()) then if not self.buyMenu then self.buyMenu = GetGUIManager():CreateGUIScript("GUIAlienBuyMenu") diff --git a/ns2/lua/Armory.lua b/ns2/lua/Armory.lua index ba8e505..1f17536 100644 --- a/ns2/lua/Armory.lua +++ b/ns2/lua/Armory.lua @@ -6,6 +6,8 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") + class 'Armory' (Structure) Armory.kMapName = "armory" @@ -35,7 +37,7 @@ else Script.Load("lua/Armory_Client.lua") end -local networkVars = +Armory.networkVars = { // How far out the arms are for animation (0-1) loggedInEast = "boolean", @@ -59,6 +61,14 @@ function GetArmory(entity) end +function Armory:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end + function Armory:OnInit() self:SetModel(Armory.kModelName) @@ -218,7 +228,7 @@ function Armory:OnUpdate(deltaTime) end -Shared.LinkClassToMap("Armory", Armory.kMapName, networkVars) +Shared.LinkClassToMap("Armory", Armory.kMapName, Armory.networkVars) class 'AdvancedArmory' (Armory) diff --git a/ns2/lua/ArmsLab.lua b/ns2/lua/ArmsLab.lua index 622e01a..0d49353 100644 --- a/ns2/lua/ArmsLab.lua +++ b/ns2/lua/ArmsLab.lua @@ -6,11 +6,21 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") + class 'ArmsLab' (Structure) ArmsLab.kMapName = "armslab" ArmsLab.kModelName = PrecacheAsset("models/marine/arms_lab/arms_lab.model") +function ArmsLab:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end + function ArmsLab:GetTechButtons(techId) return { kTechId.Weapons1, kTechId.Weapons2, kTechId.Weapons3, kTechId.CatPackTech, diff --git a/ns2/lua/AttackOrderMixin.lua b/ns2/lua/AttackOrderMixin.lua new file mode 100644 index 0000000..c2542ac --- /dev/null +++ b/ns2/lua/AttackOrderMixin.lua @@ -0,0 +1,181 @@ +// ======= Copyright © 2003-2011, Unknown Worlds Entertainment, Inc. All rights reserved. ======= +// +// lua\AttackOrderMixin.lua +// +// Created by: Brian Cronin (brianc@unknownworlds.com) +// +// ========= For more information, visit us at http://www.unknownworlds.com ===================== + +Script.Load("lua/FunctionContracts.lua") + +/** + * AttackOrderMixin handles processing attack orders. + */ +AttackOrderMixin = { } +AttackOrderMixin.type = "AttackOrder" + +AttackOrderMixin.expectedMixins = +{ + Orders = "Needed for calls to GetCurrentOrder().", + Pathing = "Needed for calls to MoveToTarget().", + GameEffects = "Needed for calls to AdjustAttackDelay()." +} + +AttackOrderMixin.expectedCallbacks = +{ + GetMeleeAttackDamage = "Returns the amount of damage each melee hit does.", + GetMeleeAttackInterval = "Returns how often this Entity melee attacks.", + GetMeleeAttackOrigin = "Returns where the melee attack originates from.", + TriggerEffects = "The melee_attack effect will be triggered through this callback.", + GetOwner = "Returns the owner, if any, of this Entity." +} + +AttackOrderMixin.expectedConstants = +{ + kMoveToDistance = "The distance at which the move part of the Attack order is complete." +} + +function AttackOrderMixin:__initmixin() + + self.timeOfLastAttackOrder = 0 + +end + +// This is an "attack-move" from RTS. Attack the entity specified in our current attack order, if any. +// Otherwise, move to the location specified in the attack order and attack anything along the way. +function AttackOrderMixin:ProcessAttackOrder(targetSearchDistance, moveSpeed, time) + + // If we have a target, attack it. + local currentOrder = self:GetCurrentOrder() + if currentOrder ~= nil then + + local target = Shared.GetEntity(currentOrder:GetParam()) + + if target then + + // How do you kill that which has no life? + if not HasMixin(target, "Live") or not target:GetIsAlive() then + self:CompletedCurrentOrder() + else + + local targetLocation = target:GetEngagementPoint() + if self:GetIsFlying() then + targetLocation = GetHoverAt(self, targetLocation) + end + + self:MoveToTarget(PhysicsMask.AIMovement, targetLocation, moveSpeed, time) + + end + + else + + // Check for a nearby target. If not found, move towards destination. + target = self:_FindTarget(targetSearchDistance) + + end + + if target and HasMixin(target, "Live") then + + // If we are close enough to target, attack it + local targetPosition = Vector(target:GetOrigin()) + if self.GetHoverHeight then + targetPosition.y = targetPosition.y + self:GetHoverHeight() + end + + // Different targets can be attacked from different ranges, depending on size + local attackDistance = GetEngagementDistance(currentOrder:GetParam()) + + local distanceToTarget = (targetPosition - self:GetOrigin()):GetLength() + if (distanceToTarget <= attackDistance) and target:GetIsAlive() then + self:_OrderMeleeAttack(target) + end + + else + + // otherwise move towards attack location and end order when we get there + local targetLocation = currentOrder:GetLocation() + if self:GetIsFlying() then + targetLocation = GetHoverAt(self, targetLocation) + end + + self:MoveToTarget(PhysicsMask.AIMovement, targetLocation, moveSpeed, time) + + local distanceToTarget = (currentOrder:GetLocation() - self:GetOrigin()):GetLength() + if distanceToTarget < self:GetMixinConstants().kMoveToDistance then + self:CompletedCurrentOrder() + end + + end + + end + +end +AddFunctionContract(AttackOrderMixin.ProcessAttackOrder, { Arguments = { "Entity", "number", "number", "number" }, Returns = { } }) + +function AttackOrderMixin:_GetIsTargetValid(target) + return target ~= self and target ~= nil +end + +/** + * Returns valid taret within attack distance, if any. + */ +function AttackOrderMixin:_FindTarget(attackDistance) + + // Find enemy in range + local enemyTeamNumber = GetEnemyTeamNumber(self:GetTeamNumber()) + local potentialTargets = GetEntitiesWithMixinForTeamWithinRange("Live", enemyTeamNumber, self:GetOrigin(), attackDistance) + + local nearestTarget = nil + local nearestTargetDistance = 0 + + // Get closest target + for index, currentTarget in ipairs(potentialTargets) do + + if self:_GetIsTargetValid(currentTarget) then + + local distance = self:GetDistance(currentTarget) + if nearestTarget == nil or distance < nearestTargetDistance then + + nearestTarget = currentTarget + nearestTargetDistance = distance + + end + + end + + end + + return nearestTarget + +end + +function AttackOrderMixin:_OrderMeleeAttack(target) + + local meleeAttackInterval = self:AdjustAttackDelay(self:GetMeleeAttackInterval()) + + if Shared.GetTime() > (self.timeOfLastAttackOrder + meleeAttackInterval) then + + self:TriggerEffects(string.format("%s_melee_attack", string.lower(self:GetClassName()))) + + // Traceline from us to them + local trace = Shared.TraceRay(self:GetMeleeAttackOrigin(), target:GetOrigin(), PhysicsMask.AllButPCs, EntityFilterTwo(self, target)) + + local direction = target:GetOrigin() - self:GetOrigin() + direction:Normalize() + + // Use player or owner (in the case of MACs, Drifters, etc.) + local attacker = self:GetOwner() + if self:isa("Player") then + attacker = self + end + + target:TakeDamage(self:GetMeleeAttackDamage(), attacker, self, trace.endPoint, direction) + + // Play hit effects - doer, target, origin, surface + TriggerHitEffects(self, target, trace.endPoint, trace.surface, true) + + self.timeOfLastAttackOrder = Shared.GetTime() + + end + +end \ No newline at end of file diff --git a/ns2/lua/Balance.lua b/ns2/lua/Balance.lua index ef2b815..6c03249 100644 --- a/ns2/lua/Balance.lua +++ b/ns2/lua/Balance.lua @@ -561,9 +561,9 @@ kMatureShiftMaxEnergy = 150 kShadeInitialEnergy = 25 kShadeMaxEnergy = 100 kShadeCloakCost = 25 -kShadePhantasmFadeCost = 25 -kShadePhantasmOnosCost = 50 -kShadePhantasmCost = 75 +kShadePhantomFadeCost = 25 +kShadePhantomOnosCost = 50 +kShadePhantomCost = 75 kMatureShadeMaxEnergy = 150 kEnergyUpdateRate = 0.5 diff --git a/ns2/lua/BalanceMisc.lua b/ns2/lua/BalanceMisc.lua index b23c4aa..8e65847 100644 --- a/ns2/lua/BalanceMisc.lua +++ b/ns2/lua/BalanceMisc.lua @@ -104,4 +104,7 @@ kCystParentRange = 20 // distance from a cyst another cyst (or minicyst) can be kMiniCystParentRange = 15 // distance from a minicyst a cyst can be placed // Damage over time that all cysts take when not connected -kCystUnconnectedDamage = 12 \ No newline at end of file +kCystUnconnectedDamage = 12 + +kPhantomEffigyLifetime = 120 +kPhantomLifetime = 10 \ No newline at end of file diff --git a/ns2/lua/Bot_Player.lua b/ns2/lua/Bot_Player.lua index 290e32d..f2928ac 100644 --- a/ns2/lua/Bot_Player.lua +++ b/ns2/lua/Bot_Player.lua @@ -325,7 +325,7 @@ function BotPlayer:GenerateMove() if self.pathingEnabled then - Server.MoveToTarget(PhysicsMask.AIMovement, player, player:GetWaypointGroupName(), orderLocation, 1.5) + Server.MoveToTarget(PhysicsMask.AIMovement, player, GetWaypointGroupName(player), orderLocation, 1.5) if self:GetNumPoints() ~= 0 then self:MoveToPoint(player:GetCurrentPathPoint(), move) diff --git a/ns2/lua/CamouflageMixin.lua b/ns2/lua/CamouflageMixin.lua index a0c84f7..089d46f 100644 --- a/ns2/lua/CamouflageMixin.lua +++ b/ns2/lua/CamouflageMixin.lua @@ -84,6 +84,23 @@ function CamouflageMixin:OnUpdate(deltaTime) self:_UpdateCamouflage() end +function CamouflageMixin:OnSynchronized() + + if Client then + + local newHiddenState = self:GetIsCamouflaged() + if self.clientCamoed ~= newHiddenState then + + local isEnemy = GetEnemyTeamNumber(self:GetTeamNumber()) == Client.GetLocalPlayer():GetTeamNumber() + self:TriggerEffects("client_cloak_changed", {cloaked = newHiddenState, enemy = isEnemy}) + self.clientCamoed = newHiddenState + + end + + end + +end + //self.movementModiferState function CamouflageMixin:GetCamouflageMaxSpeed(walking) diff --git a/ns2/lua/CloakableMixin.lua b/ns2/lua/CloakableMixin.lua index c27009c..5da1e15 100644 --- a/ns2/lua/CloakableMixin.lua +++ b/ns2/lua/CloakableMixin.lua @@ -107,6 +107,23 @@ function CloakableMixin:OnUpdate(deltaTime) self:_UpdateCloakState() end +function CloakableMixin:OnSynchronized() + + if Client then + + local newHiddenState = self:GetIsCloaked() + if self.clientCloaked ~= newHiddenState then + + local isEnemy = GetEnemyTeamNumber(self:GetTeamNumber()) == Client.GetLocalPlayer():GetTeamNumber() + self:TriggerEffects("client_cloak_changed", {cloaked = newHiddenState, enemy = isEnemy}) + self.clientCloaked = newHiddenState + + end + + end + +end + function CloakableMixin:OnScan() self:TriggerUncloak() end diff --git a/ns2/lua/Cocoon.lua b/ns2/lua/Cocoon.lua index 0124611..4db5034 100644 --- a/ns2/lua/Cocoon.lua +++ b/ns2/lua/Cocoon.lua @@ -8,6 +8,7 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") class 'Cocoon' (Structure) @@ -18,6 +19,14 @@ Cocoon.kModelName = PrecacheAsset("models/alien/cocoon/cocoon.model") Cocoon.kHealth = 200 Cocoon.kArmor = 50 +function Cocoon:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end + function Cocoon:OnInit() self:SetModel(Cocoon.kModelName) diff --git a/ns2/lua/CommandStructure.lua b/ns2/lua/CommandStructure.lua index ce069c1..066a9aa 100644 --- a/ns2/lua/CommandStructure.lua +++ b/ns2/lua/CommandStructure.lua @@ -7,6 +7,7 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") class 'CommandStructure' (Structure) CommandStructure.kMapName = "commandstructure" @@ -15,7 +16,7 @@ if (Server) then Script.Load("lua/CommandStructure_Server.lua") end -local networkVars = +CommandStructure.networkVars = { occupied = "boolean", commanderId = "entityid", @@ -25,6 +26,8 @@ function CommandStructure:OnCreate() Structure.OnCreate(self) + InitMixin(self, RagdollMixin) + self.occupied = false self.commanderId = Entity.invalidId @@ -45,4 +48,4 @@ function CommandStructure:GetEffectParams(tableParams) end -Shared.LinkClassToMap("CommandStructure", CommandStructure.kMapName, networkVars) \ No newline at end of file +Shared.LinkClassToMap("CommandStructure", CommandStructure.kMapName, CommandStructure.networkVars) \ No newline at end of file diff --git a/ns2/lua/CommandStructure_Server.lua b/ns2/lua/CommandStructure_Server.lua index 3e074fa..4f778f9 100644 --- a/ns2/lua/CommandStructure_Server.lua +++ b/ns2/lua/CommandStructure_Server.lua @@ -82,7 +82,7 @@ function CommandStructure:OnResearchComplete(structure, researchId) if(structure and (structure:GetId() == self:GetId()) and (researchId == self.level1TechId or researchId == self.level2TechId or researchId == self.level3TechId)) then // Also changes current health and maxHealth - success = self:Upgrade(researchId) + success = self:UpgradeToTechId(researchId) end diff --git a/ns2/lua/Commander.lua b/ns2/lua/Commander.lua index 5c2ed7e..79c7c6f 100644 --- a/ns2/lua/Commander.lua +++ b/ns2/lua/Commander.lua @@ -501,10 +501,6 @@ function Commander:GetHostCommandStructure() return Shared.GetEntity(self.commandStationId) end -function Commander:GetCanDoDamage() - return false -end - function Commander:OverrideCheckvision() return false end diff --git a/ns2/lua/Commander_FocusPanel.lua b/ns2/lua/Commander_FocusPanel.lua index fc7d476..4f8caac 100644 --- a/ns2/lua/Commander_FocusPanel.lua +++ b/ns2/lua/Commander_FocusPanel.lua @@ -78,7 +78,7 @@ function CommanderUI_GetFocusSelectionIcons() local entity = player:GetRepresentativeSelectedEntity() - if entity ~= nil and entity:isa("LiveScriptActor") then + if entity ~= nil and HasMixin(entity, "Upgradable") then // Get upgrades local upgrades = entity:GetUpgrades() diff --git a/ns2/lua/Commander_Selection.lua b/ns2/lua/Commander_Selection.lua index 0746499..5dab7d3 100644 --- a/ns2/lua/Commander_Selection.lua +++ b/ns2/lua/Commander_Selection.lua @@ -177,8 +177,7 @@ function Commander:MarqueeSelectEntities(pickStartVec, pickEndVec) local newSelection = {} - // Add more class names here to allow selection of other entity types - local potentials = EntityListToTable(Shared.GetEntitiesWithClassname("LiveScriptActor")) + local potentials = GetEntitiesWithMixin("Selectable") self:GetEntitiesBetweenVecs(potentials, pickStartVec, pickEndVec, newSelection) @@ -523,7 +522,7 @@ function Commander:GetIsEntityValidForSelection(entity) // Select living things on our team that aren't us // For now, don't allow even click selection of enemy units or structures - if ( entity ~= nil and entity:isa("LiveScriptActor") and (entity:GetTeamNumber() == self:GetTeamNumber()) and (entity:GetIsSelectable()) and (entity ~= self) and entity:GetIsAlive() ) or + if ( entity ~= nil and HasMixin(entity, "Live") and (entity:GetTeamNumber() == self:GetTeamNumber()) and (HasMixin(entity, "Selectable") and entity:GetIsSelectable()) and (entity ~= self) and entity:GetIsAlive() ) or // ...and doors (entity ~= nil and entity:isa("Door")) then diff --git a/ns2/lua/Commander_SelectionPanel.lua b/ns2/lua/Commander_SelectionPanel.lua index 84c6f1e..d710167 100644 --- a/ns2/lua/Commander_SelectionPanel.lua +++ b/ns2/lua/Commander_SelectionPanel.lua @@ -89,7 +89,7 @@ function CommanderUI_GetPortraitStatus(entityId) local healthScalar = 1 local entity = Shared.GetEntity(entityId) - if entity ~= nil and entity:isa("LiveScriptActor") then + if entity ~= nil and HasMixin(entity, "Live") then healthScalar = entity:GetHealthScalar() end @@ -231,15 +231,18 @@ function CommanderUI_GetSelectedBargraphs(entityId) table.insert(t, healthText) table.insert(t, healthScalar) - // Build, upgrade or research bar - local statusText, statusScalar = ent:GetStatusDescription() + // Returns text and 0-1 scalar for status bar on commander HUD when selected. Returns nil to display nothing. + if ent.GetStatusDescription then - if statusText ~= nil then - table.insert(t, statusText) - table.insert(t, statusScalar) + // Build, upgrade or research bar + local statusText, statusScalar = ent:GetStatusDescription() + + if statusText ~= nil then + table.insert(t, statusText) + table.insert(t, statusScalar) + end + end - - //Print("CommanderUI_GetSelectedBargraphs() returning %s", table.tostring(t)) end @@ -285,16 +288,18 @@ end * Get custom rightside selection text for a single selection */ function CommanderUI_GetSingleSelectionCustomText(entId) + local customText = "" if entId ~= nil then local ent = Shared.GetEntity(entId) - if (ent ~= nil) then + if ent ~= nil and ent.GetCustomSelectionText then customText = ent:GetCustomSelectionText() end end return customText + end \ No newline at end of file diff --git a/ns2/lua/Commander_Server.lua b/ns2/lua/Commander_Server.lua index 33da468..e203ab8 100644 --- a/ns2/lua/Commander_Server.lua +++ b/ns2/lua/Commander_Server.lua @@ -79,8 +79,10 @@ function GetUnattachedEntityWithinRadius(attachclass, position, radius) end -// Can't take damage. -function Commander:GetCanTakeDamage() +/** + * Commanders cannot take damage. + */ +function Commander:GetCanTakeDamageOverride() return false end @@ -91,8 +93,11 @@ function Commander:AttemptToResearchOrUpgrade(techNode, force) local entity = Shared.GetEntity( self.selectedSubGroupEntityIds[1] ) + // $AS FIXME: We need a better way to do recycling + local canResearch = (techNode:GetTechId() == kTechId.Recycle or entity:GetCanResearch()) + // Don't allow it to be researched while researching - if( (entity ~= nil and entity:isa("Structure") and entity:GetCanResearch() and techNode:GetCanResearch()) or force) then + if( (entity ~= nil and entity:isa("Structure") and canResearch and techNode:GetCanResearch()) or force) then entity:SetResearching(techNode, self) entity:OnResearch(techNode:GetTechId()) diff --git a/ns2/lua/Crag.lua b/ns2/lua/Crag.lua index 5a8e8c0..b9a9b3f 100644 --- a/ns2/lua/Crag.lua +++ b/ns2/lua/Crag.lua @@ -13,6 +13,7 @@ // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") Script.Load("lua/InfestationMixin.lua") +Script.Load("lua/RagdollMixin.lua") class 'Crag' (Structure) @@ -34,6 +35,14 @@ Crag.kUmbraRadius = 10 // Umbra blocks 1 out of this many bullet Crag.kUmbraBulletChance = 2 +function Crag:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end + function Crag:OnConstructionComplete() Structure.OnConstructionComplete(self) @@ -124,7 +133,7 @@ function Crag:OnResearchComplete(structure, researchId) // Transform into mature crag if structure and (structure:GetId() == self:GetId()) and (researchId == kTechId.UpgradeCrag) then - success = self:Upgrade(kTechId.MatureCrag) + success = self:UpgradeToTechId(kTechId.MatureCrag) end diff --git a/ns2/lua/Cyst.lua b/ns2/lua/Cyst.lua index 72d0632..c6c9789 100644 --- a/ns2/lua/Cyst.lua +++ b/ns2/lua/Cyst.lua @@ -7,6 +7,7 @@ // A cyst controls and spreads infestation // // ========= For more information, visit us at http://www.unknownworlds.com ===================== +Script.Load("lua/RagdollMixin.lua") class 'Cyst' (Structure) @@ -68,6 +69,14 @@ if Server then Script.Load("lua/Cyst_Server.lua") end +function Cyst:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end + function Cyst:OnInit() InitMixin(self, InfestationMixin) @@ -242,10 +251,6 @@ function Cyst:GetDeployAnimation() return "" end -function Cyst:GetCanDoDamage() - return false -end - function Cyst:GetEngagementPoint() // Structure:GetEngagementPoint requires a target attachment point on the model, which Cyst doesn't have right now, // so override to get rid of the console spam diff --git a/ns2/lua/Door.lua b/ns2/lua/Door.lua index 00d8536..cbb60c6 100644 --- a/ns2/lua/Door.lua +++ b/ns2/lua/Door.lua @@ -7,7 +7,9 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/LiveScriptActor.lua") +Script.Load("lua/UpgradableMixin.lua") Script.Load("lua/GameEffectsMixin.lua") +Script.Load("lua/SelectableMixin.lua") class 'Door' (LiveScriptActor) @@ -57,14 +59,17 @@ Door.networkVars = { } +PrepareClassForMixin(Door, UpgradableMixin) PrepareClassForMixin(Door, GameEffectsMixin) function Door:OnCreate() LiveScriptActor.OnCreate(self) + InitMixin(self, UpgradableMixin) InitMixin(self, GameEffectsMixin) InitMixin(self, PathingMixin) + InitMixin(self, SelectableMixin) self:SetPathingFlags(Pathing.PolyFlag_NoBuild) @@ -80,7 +85,7 @@ function Door:OnInit() self:SetIsVisible(true) - self:SetPhysicsType(Actor.PhysicsType.Kinematic) + self:SetPhysicsType(PhysicsType.Kinematic) self:SetPhysicsGroup(PhysicsGroup.CommanderUnitGroup) diff --git a/ns2/lua/Door_Server.lua b/ns2/lua/Door_Server.lua index 48eb69d..0d1a256 100644 --- a/ns2/lua/Door_Server.lua +++ b/ns2/lua/Door_Server.lua @@ -15,7 +15,7 @@ function Door:Reset() self:SetOrigin(self.savedOrigin) self:SetAngles(self.savedAngles) - self:SetPhysicsType(Actor.PhysicsType.Kinematic) + self:SetPhysicsType(PhysicsType.Kinematic) self:SetPhysicsGroup(0) self:SetState(Door.kState.Closed) diff --git a/ns2/lua/Drifter.lua b/ns2/lua/Drifter.lua index 7c39f00..e8d3b4b 100644 --- a/ns2/lua/Drifter.lua +++ b/ns2/lua/Drifter.lua @@ -14,8 +14,13 @@ Script.Load("lua/EnergyMixin.lua") Script.Load("lua/BuildingMixin.lua") Script.Load("lua/CloakableMixin.lua") Script.Load("lua/mixins/ControllerMixin.lua") +Script.Load("lua/RagdollMixin.lua") +Script.Load("lua/AttackOrderMixin.lua") +Script.Load("lua/UpgradableMixin.lua") +Script.Load("lua/PointGiverMixin.lua") Script.Load("lua/GameEffectsMixin.lua") Script.Load("lua/FlinchMixin.lua") +Script.Load("lua/SelectableMixin.lua") Script.Load("lua/TargetMixin.lua") Script.Load("lua/LOSMixin.lua") @@ -55,6 +60,7 @@ Drifter.networkVars = { PrepareClassForMixin(Drifter, EnergyMixin) PrepareClassForMixin(Drifter, ControllerMixin) +PrepareClassForMixin(Drifter, UpgradableMixin) PrepareClassForMixin(Drifter, GameEffectsMixin) PrepareClassForMixin(Drifter, FlinchMixin) PrepareClassForMixin(Drifter, CloakableMixin) @@ -64,11 +70,20 @@ function Drifter:OnCreate() LiveScriptActor.OnCreate(self) InitMixin(self, ControllerMixin) + InitMixin(self, RagdollMixin) + InitMixin(self, DoorMixin) + InitMixin(self, EnergyMixin) + InitMixin(self, BuildingMixin) + InitMixin(self, CloakableMixin) + InitMixin(self, PathingMixin) InitMixin(self, GameEffectsMixin) + InitMixin(self, UpgradableMixin) InitMixin(self, FlinchMixin) - InitMixin(self, PathingMixin) + InitMixin(self, PointGiverMixin) + InitMixin(self, SelectableMixin) if Server then + InitMixin(self, AttackOrderMixin, { kMoveToDistance = Drifter.kMoveToDistance }) InitMixin(self, TargetMixin) InitMixin(self, LOSMixin) end @@ -83,15 +98,10 @@ function Drifter:OnCreate() end function Drifter:OnInit() - - InitMixin(self, DoorMixin) - InitMixin(self, EnergyMixin) - InitMixin(self, BuildingMixin) - InitMixin(self, CloakableMixin) self:SetModel(Drifter.kModelName) - self:SetPhysicsType(Actor.PhysicsType.Kinematic) + self:SetPhysicsType(PhysicsType.Kinematic) LiveScriptActor.OnInit(self) @@ -148,7 +158,7 @@ function Drifter:GetDeathIconIndex() return kDeathMessageIcon.Drifter end -function Drifter:GetCanTakeDamage() +function Drifter:GetCanTakeDamageOverride() return not self.landed end @@ -211,12 +221,14 @@ function Drifter:OnOverrideOrder(order) end function Drifter:GetPositionForEntity(hive) + local angle = NetworkRandom() * math.pi*2 local startPoint = self:GetOrigin() + Vector( math.cos(angle)*Drifter.kStartDistance , Drifter.kHoverHeight, math.sin(angle)*Drifter.kStartDistance ) local direction = Vector(self:GetAngles():GetCoords().zAxis) - startPoint = self:GetHoverAt(startPoint) + startPoint = GetHoverAt(self, startPoint) return BuildCoords(Vector(0, 1, 0), direction, startPoint) + end function Drifter:ProcessJustSpawned() @@ -279,6 +291,7 @@ function Drifter:OnThink() self:ProcessMoveOrder(drifterMoveSpeed) elseif(currentOrder:GetType() == kTechId.Attack) then + // From AttackOrderMixin. self:ProcessAttackOrder(5, drifterMoveSpeed, Drifter.kMoveThinkInterval) elseif(currentOrder:GetType() == kTechId.Build) then @@ -416,7 +429,7 @@ function Drifter:ProcessMoveOrder(moveSpeed) if (currentOrder ~= nil) then local isBuild = (currentOrder:GetType() == kTechId.Build) - local hoverAdjustedLocation = self:GetHoverAt(currentOrder:GetLocation()) + local hoverAdjustedLocation = GetHoverAt(self, currentOrder:GetLocation()) self:MoveToTarget(PhysicsMask.AIMovement, hoverAdjustedLocation, moveSpeed, Drifter.kMoveThinkInterval) if(not isBuild and self:IsTargetReached(hoverAdjustedLocation, kEpsilon, true)) then self:CompletedCurrentOrder() @@ -438,7 +451,9 @@ function Drifter:OnUpdate(deltaTime) end - self:UpdateEnergy(deltaTime) + if Server then + self:UpdateEnergy(deltaTime) + end self.timeOfLastUpdate = Shared.GetTime() @@ -591,10 +606,6 @@ function Drifter:PerformParasite() end -function Drifter:GetWaypointGroupName() - return kAirWaypointsGroup -end - function Drifter:GetMeleeAttackDamage() return kDrifterAttackDamage end @@ -603,6 +614,10 @@ function Drifter:GetMeleeAttackInterval() return kDrifterAttackFireDelay end +function Drifter:GetMeleeAttackOrigin() + return self:GetOrigin() +end + function Drifter:OnOverrideDoorInteraction(inEntity) return true, 4 end diff --git a/ns2/lua/DropPack.lua b/ns2/lua/DropPack.lua index 80db3b5..f8997c3 100644 --- a/ns2/lua/DropPack.lua +++ b/ns2/lua/DropPack.lua @@ -19,7 +19,7 @@ function DropPack:OnCreate () ScriptActor.OnCreate(self) - self:SetPhysicsType(Actor.PhysicsType.DynamicServer) + self:SetPhysicsType(PhysicsType.DynamicServer) self:SetPhysicsGroup(PhysicsGroup.ProjectileGroup) diff --git a/ns2/lua/Egg.lua b/ns2/lua/Egg.lua index 27b6b31..a0c76ea 100644 --- a/ns2/lua/Egg.lua +++ b/ns2/lua/Egg.lua @@ -10,6 +10,7 @@ Script.Load("lua/Structure.lua") Script.Load("lua/Onos.lua") Script.Load("lua/InfestationMixin.lua") +Script.Load("lua/RagdollMixin.lua") class 'Egg' (Structure) PrepareClassForMixin(Egg, InfestationMixin) @@ -33,8 +34,11 @@ Egg.kArmor = kEggArmor Egg.kThinkInterval = .5 function Egg:OnCreate() + Structure.OnCreate(self) + InitMixin(self, RagdollMixin) + self:SetModel(Egg.kModelName) end diff --git a/ns2/lua/Embryo.lua b/ns2/lua/Embryo.lua index 647dfa2..a6a442b 100644 --- a/ns2/lua/Embryo.lua +++ b/ns2/lua/Embryo.lua @@ -227,8 +227,4 @@ if Server then end -function Embryo:GetCanDoDamage() - return false -end - Shared.LinkClassToMap("Embryo", Embryo.kMapName, Embryo.networkVars) \ No newline at end of file diff --git a/ns2/lua/EnergyMixin.lua b/ns2/lua/EnergyMixin.lua index b965a40..5069c22 100644 --- a/ns2/lua/EnergyMixin.lua +++ b/ns2/lua/EnergyMixin.lua @@ -50,8 +50,10 @@ end AddFunctionContract(EnergyMixin.SetEnergy, { Arguments = { "Entity", "number" }, Returns = { } }) function EnergyMixin:AddEnergy(amount) + self.energy = self.energy + amount self.energy = math.max(math.min(self.energy, self.maxEnergy), 0) + end AddFunctionContract(EnergyMixin.AddEnergy, { Arguments = { "Entity", "number" }, Returns = { } }) @@ -62,6 +64,8 @@ AddFunctionContract(EnergyMixin.GetMaxEnergy, { Arguments = { "Entity" }, Return function EnergyMixin:UpdateEnergy(timePassed) + assert(Server) + local scalar = ConditionalValue(self:GetGameEffectMask(kGameEffect.OnFire), kOnFireEnergyRecuperationScalar, 1) // Increase energy for entities that are affected by energize (not PowerPoints) diff --git a/ns2/lua/Fade_Server.lua b/ns2/lua/Fade_Server.lua index a7b7fb4..fc10d10 100644 --- a/ns2/lua/Fade_Server.lua +++ b/ns2/lua/Fade_Server.lua @@ -18,8 +18,8 @@ function Fade:InitWeapons() end -function Fade:GetCanTakeDamage() - return Alien.GetCanTakeDamage(self) and not self:GetIsBlinking() +function Fade:GetCanTakeDamageOverride() + return Alien.GetCanTakeDamageOverride(self) and not self:GetIsBlinking() end function Fade:OnUpdate(deltaTime) diff --git a/ns2/lua/FireMixin.lua b/ns2/lua/FireMixin.lua index adaf14f..fb3d653 100644 --- a/ns2/lua/FireMixin.lua +++ b/ns2/lua/FireMixin.lua @@ -10,7 +10,8 @@ FireMixin = { } FireMixin.type = "Fire" function FireMixin.__prepareclass(toClass) - ASSERT(toClass.networkVars ~= nil, "EnergyMixin expects the class to have network fields") + + ASSERT(toClass.networkVars ~= nil, "FireMixin expects the class to have network fields") local addNetworkFields = { @@ -21,24 +22,22 @@ function FireMixin.__prepareclass(toClass) for k, v in pairs(addNetworkFields) do toClass.networkVars[k] = v end + end function FireMixin:__initmixin() + self.fireAttackerId = Entity.invalidId self.fireDoerId = Entity.invalidId self.stopChance = 0 self.timeLastUpdateStopChance = 0 -end - -function FireMixin:OverrideSetFire(attacker, doer) - if self.OnOverrideSetFire then - self:OnOverrideSetFire(attacker, doer) - end + end function FireMixin:SetOnFire(attacker, doer) - if not self:GetCanBeSetOnFire() then + + if not self:GetCanBeSetOnFire() then return end @@ -47,10 +46,10 @@ function FireMixin:SetOnFire(attacker, doer) self.fireAttackerId = attacker:GetId() self.fireDoerId = doer:GetId() - self:OverrideSetFire(attacker, doer) end function FireMixin:ClearFire() + self:SetGameEffectMask(kGameEffect.OnFire, false) self.fireAttackerId = Entity.invalidId @@ -58,21 +57,25 @@ function FireMixin:ClearFire() self.stopChance = 0 self.timeLastUpdateStopChance = nil + end -function FireMixin:GetIsOnFire () +function FireMixin:GetIsOnFire() return self:GetGameEffectMask(kGameEffect.OnFire) end -function FireMixin:_GetStopChance () +function FireMixin:_GetStopChance() + if self.timeLastUpdateStopChance == nil or (Shared.GetTime() > self.timeLastUpdateStopChance + 10) then self.stopChance = self.stopChance + kStopFireProbability self.timeLastUpdateStopChance = Shared.GetTime() end return self.stopChance + end -function FireMixin:GetCanBeSetOnFire () +function FireMixin:GetCanBeSetOnFire() + if self.OnOverrideCanSetFire then return self:OnOverrideCanSetFire(attacker, doer) else @@ -81,18 +84,88 @@ function FireMixin:GetCanBeSetOnFire () end -function FireMixin:UpdateFire(updateEffectsInterval) +function FireMixin:OnUpdate(deltaTime) + if not self:GetIsOnFire() then return end - // Do damage over time - self:TakeDamage(kBurnDamagePerSecond * updateEffectsInterval, Shared.GetEntity(self.fireAttackerId), Shared.GetEntity(self.fireDoerId)) + if Server then + + // Do damage over time + self:TakeDamage(kBurnDamagePerSecond * deltaTime, Shared.GetEntity(self.fireAttackerId), Shared.GetEntity(self.fireDoerId)) + + // See if we put ourselves out + local stopFireChance = deltaTime * self:_GetStopChance() + if (NetworkRandom() < stopFireChance) then + self:ClearFire() + end + + elseif Client then - // See if we put ourselves out - local stopFireChance = updateEffectsInterval * self:_GetStopChance() - if (NetworkRandom() < stopFireChance) then - self:ClearFire() + if self.updateClientSideFireEffects == true then + self:_UpdateClientFireEffects() + self.updateClientSideFireEffects = false + end + end + end +function FireMixin:OnSynchronized() + + PROFILE("FireMixin:OnSynchronized") + + if Client then + self.updateClientSideFireEffects = true + end + +end + +function FireMixin:OnEntityChange(entityId, newEntityId) + + if entityId == self.fireAttackerId then + self.fireAttackerId = newEntityId + end + +end + +function FireMixin:_UpdateClientFireEffects() + + // Play on-fire cinematic every so often if we're on fire + if self:GetGameEffectMask(kGameEffect.OnFire) and self:GetIsAlive() and self:GetIsVisible() then + + // If we haven't played effect for a bit + local time = Shared.GetTime() + + if not self.timeOfLastFireEffect or (time > (self.timeOfLastFireEffect + .5)) then + + local firstPerson = (Client.GetLocalPlayer() == self) + local cinematicName = GetOnFireCinematic(self, firstPerson) + + if firstPerson then + local viewModel = self:GetViewModelEntity() + if viewModel then + Shared.CreateAttachedEffect(self, cinematicName, viewModel, Coords.GetTranslation(Vector(0, 0, 0)), "", true, false) + end + else + Shared.CreateEffect(self, cinematicName, self, self:GetAngles():GetCoords()) + end + + self.timeOfLastFireEffect = time + + end + + end + +end + +function FireMixin:OnGameEffectMaskChanged(effect, state) + + if effect == kGameEffect.OnFire and state then + self:TriggerEffects("fire_start") + elseif effect == kGameEffect.OnFire and not state then + self:TriggerEffects("fire_stop") + end + +end \ No newline at end of file diff --git a/ns2/lua/GUIAlienHUD.lua b/ns2/lua/GUIAlienHUD.lua index 4e416d7..dff2a9b 100644 --- a/ns2/lua/GUIAlienHUD.lua +++ b/ns2/lua/GUIAlienHUD.lua @@ -74,6 +74,11 @@ GUIAlienHUD.kInactiveAbilityBarOffset = Vector(-GUIAlienHUD.kInactiveAbilityBarW GUIAlienHUD.kSelectedAbilityColor = Color(1, 1, 1, 1) GUIAlienHUD.kUnselectedAbilityColor = Color(0.5, 0.5, 0.5, 1) +GUIAlienHUD.kPhantomTextFontSize = 24 +GUIAlienHUD.kPhantomProgressBarWidth = 200 +GUIAlienHUD.kPhantomProgressBarHeight = 10 +GUIAlienHUD.kPhantomProgressBarColor = Color(0.0, 0.24313725490196078431372549019608, 0.48235294117647058823529411764706, 0.5) + function GUIAlienHUD:Initialize() // Stores all state related to fading balls. @@ -164,6 +169,27 @@ function GUIAlienHUD:CreateHealthBall() self.healthBall:GetBackground():AddChild(self.armorText) + // Add bar that goes down over time + self.phantomProgressBar = GUIManager:CreateGraphicItem() + self.phantomProgressBar:SetSize(Vector(GUIAlienHUD.kPhantomProgressBarWidth, GUIAlienHUD.kPhantomProgressBarHeight, 0)) + self.phantomProgressBar:SetAnchor(GUIItem.Middle, GUIItem.Bottom) + self.phantomProgressBar:SetPosition(Vector(0, -20, 0)) + self.phantomProgressBar:SetColor(GUIAlienHUD.kPhantomProgressBarColor) + + // Display "Phantom" help text + self.phantomText = GUIManager:CreateTextItem() + self.phantomText:SetIsVisible(false) + self.phantomText:SetFontSize(GUIAlienHUD.kPhantomTextFontSize) + self.phantomText:SetFontName(GUIAlienHUD.kTextFontName) + self.phantomText:SetPosition(Vector(0, GUIAlienHUD.kHealthTextYOffset, 0)) + self.phantomText:SetColor(GUIAlienHUD.kFontColor) + self.phantomText:SetInheritsParentAlpha(true) + self.phantomText:SetAnchor(GUIItem.Middle, GUIItem.Bottom) + self.phantomText:SetTextAlignmentX(GUIItem.Align_Center) + self.phantomText:SetTextAlignmentY(GUIItem.Align_Center) + self.phantomText:SetPosition(Vector(0, -50, 0)) + self.phantomText:SetText(Locale.ResolveString("ALIEN_HUD_PHANTOM")) + end function GUIAlienHUD:CreateEnergyBall() @@ -303,6 +329,7 @@ function GUIAlienHUD:Update(deltaTime) self:UpdateHealthBall(deltaTime) self:UpdateEnergyBall(deltaTime) + self:UpdatePhantom(deltaTime) end @@ -346,6 +373,26 @@ function GUIAlienHUD:UpdateEnergyBall(deltaTime) end +function GUIAlienHUD:UpdatePhantom(deltaTime) + + local visible = false + local player = Client.GetLocalPlayer() + local progressWidth = GUIAlienHUD.kPhantomProgressBarWidth + + if player and HasMixin(player, "Phantom") and player:GetIsPhantom() then + + visible = true + progressWidth = GUIAlienHUD.kPhantomProgressBarWidth * (player:GetPhantomLifetime() / kPhantomLifetime) + + end + + self.phantomText:SetIsVisible(visible) + + self.phantomProgressBar:SetSize(Vector(progressWidth, GUIAlienHUD.kPhantomProgressBarHeight, 0)) + self.phantomProgressBar:SetIsVisible(visible) + +end + function GUIAlienHUD:UpdateAbilities(deltaTime) local activeHudSlot = 0 diff --git a/ns2/lua/GeneralEffects.lua b/ns2/lua/GeneralEffects.lua index faab8fe..0239483 100644 --- a/ns2/lua/GeneralEffects.lua +++ b/ns2/lua/GeneralEffects.lua @@ -13,6 +13,8 @@ kGeneralEffectData = onCreateEffects = { {parented_sound = "sound/ns2.fev/marine/structures/mac/hover", classname = "MAC", done = true}, + // TODO: + //{sound = "sound/ns2.fev/", classname = "PhantomEffigy", done = true}, }, }, @@ -386,6 +388,25 @@ kGeneralEffectData = }, }, + + phantom_effigy_expire = + { + effigyExpireEffects = + { + {sound = "sound/ns2.fev/alien/structures/shade/cloak_end"}, + {cinematic = "cinematics/marine/clone_structure.cinematic"}, + }, + }, + + // "enter" into effigy when using it + phantom_effigy_start = + { + effigyStartFX = + { + {sound = "sound/ns2.fev/alien/structures/shade/cloak_start"}, + {cinematic = "cinematics/marine/clone_structure.cinematic"}, + }, + }, } diff --git a/ns2/lua/Hydra.lua b/ns2/lua/Hydra.lua index 8429aed..f716154 100644 --- a/ns2/lua/Hydra.lua +++ b/ns2/lua/Hydra.lua @@ -9,6 +9,7 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") class 'Hydra' (Structure) @@ -29,6 +30,14 @@ if Server then Script.Load("lua/HydraSpike.lua") end +function Hydra:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end + function Hydra:GetFov() return Hydra.kFov end @@ -52,7 +61,8 @@ end function Hydra:GetDeployAnimation() return "" end -function Hydra:GetCanDoDamage() + +function Hydra:GetCanGiveDamageOverride() return true end diff --git a/ns2/lua/InfantryPortal.lua b/ns2/lua/InfantryPortal.lua index a863794..62fc508 100644 --- a/ns2/lua/InfantryPortal.lua +++ b/ns2/lua/InfantryPortal.lua @@ -6,6 +6,7 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") class 'InfantryPortal' (Structure) @@ -30,6 +31,14 @@ InfantryPortal.kThinkInterval = 0.25 InfantryPortal.kTransponderPointValue = 15 InfantryPortal.kLoginAttachPoint = "keypad" +function InfantryPortal:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end + function InfantryPortal:OnInit() Structure.OnInit(self) diff --git a/ns2/lua/Infestation.lua b/ns2/lua/Infestation.lua index cf325ca..dce45fc 100644 --- a/ns2/lua/Infestation.lua +++ b/ns2/lua/Infestation.lua @@ -8,6 +8,8 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/LiveScriptActor.lua") +Script.Load("lua/UpgradableMixin.lua") +Script.Load("lua/PointGiverMixin.lua") Script.Load("lua/GameEffectsMixin.lua") Script.Load("lua/FlinchMixin.lua") Script.Load("lua/LOSMixin.lua") @@ -42,6 +44,7 @@ Infestation.networkVars = hostAlive = "boolean", } +PrepareClassForMixin(Infestation, UpgradableMixin) PrepareClassForMixin(Infestation, GameEffectsMixin) PrepareClassForMixin(Infestation, FlinchMixin) @@ -49,8 +52,10 @@ function Infestation:OnCreate() LiveScriptActor.OnCreate(self) + InitMixin(self, UpgradableMixin) InitMixin(self, GameEffectsMixin) InitMixin(self, FlinchMixin) + InitMixin(self, PointGiverMixin) InitMixin(self, PathingMixin) self.health = Infestation.kInitialHealth @@ -139,10 +144,6 @@ function Infestation:GetTechId() return kTechId.Infestation end -function Infestation:GetIsSelectable() - return false -end - function Infestation:OnThink() PROFILE("Infestation:OnThink") diff --git a/ns2/lua/LiveMixin.lua b/ns2/lua/LiveMixin.lua index 7960343..3a2682f 100644 --- a/ns2/lua/LiveMixin.lua +++ b/ns2/lua/LiveMixin.lua @@ -11,11 +11,16 @@ Script.Load("lua/BalanceHealth.lua") LiveMixin = { } LiveMixin.type = "Live" -// Whatever uses the LiveMixin needs to implement the following callback functions. -LiveMixin.expectedCallbacks = { - GetCanTakeDamage = "Should return false if the object cannot take damage.", + +// These may be optionally implemented. +LiveMixin.optionalCallbacks = +{ OnTakeDamage = "A callback to alert when the object has taken damage.", - OnKill = "A callback to alert when the object has been killed." } + OnKill = "A callback to alert when the object has been killed.", + GetCanTakeDamageOverride = "Should return false if the entity cannot take damage. If this function is not provided it will be assumed that the entity can take damage.", + GetCanGiveDamageOverride = "Should return false if the entity cannot give damage to other entities. If this function is not provided it will be assumed that the entity cannot do damage.", + GetSendDeathMessageOverride = "Should return false if the entity doesn't send a death message on death." +} LiveMixin.kHealth = 100 LiveMixin.kArmor = 0 @@ -48,11 +53,11 @@ function LiveMixin:__initmixin() self.alive = true - self.health = LookupTechData(self:GetTechId(), kTechDataMaxHealth, self:GetMixinConstants().kHealth) + self.health = LookupTechData(self:GetTechId(), kTechDataMaxHealth, 100) ASSERT(self.health ~= nil) self.maxHealth = self.health - self.armor = LookupTechData(self:GetTechId(), kTechDataMaxArmor, self:GetMixinConstants().kArmor) + self.armor = LookupTechData(self:GetTechId(), kTechDataMaxArmor, 0) ASSERT(self.armor ~= nil) self.maxArmor = self.armor @@ -181,7 +186,17 @@ AddFunctionContract(LiveMixin.GetIsAlive, { Arguments = { "Entity" }, Returns = function LiveMixin:SetIsAlive(state) - ASSERT(type(state) == "boolean") + // It isn't ideal to be destroying the controller here but it is more robust as there + // are cases where the entity will be marked as not alive without going through the + // TakeDamageServer() function. + if self.alive and not state then + + if HasMixin(self, "Controller") then + self:DestroyController() + end + + end + self.alive = state end @@ -238,6 +253,8 @@ function LiveMixin:ComputeDamageFromUpgrades(attacker, damage, damageType, time) end // Give damage bonus if someone else hit us recently + // Note: Game specific things like "Swarm" should not be in here. The "Swarm" upgrade should + // apply whatever effect is needed itself. if attacker and attacker.GetHasUpgrade and attacker:GetHasUpgrade(kTechId.Swarm) then if self.timeOfLastDamage ~= nil and (time <= (self.timeOfLastDamage + kSwarmInterval)) then @@ -338,12 +355,15 @@ function LiveMixin:ComputeDamage(attacker, damage, damageType, time) end AddFunctionContract(LiveMixin.ComputeDamage, { Arguments = { "Entity", "Entity", "number", "number", { "number", "nil" } }, Returns = { "number", "number", "number" } }) -function LiveMixin:GetLastDamage() - - return self.timeOfLastDamage, self.lastDamageAttackerId +function LiveMixin:GetTimeOfLastDamage() + return self.timeOfLastDamage +end +AddFunctionContract(LiveMixin.GetTimeOfLastDamage, { Arguments = { "Entity" }, Returns = { { "number", "nil" } } }) +function LiveMixin:GetAttackerIdOfLastDamage() + return self.lastDamageAttackerId end -AddFunctionContract(LiveMixin.GetLastDamage, { Arguments = { "Entity" }, Returns = { { "number", "nil" }, "number" } }) +AddFunctionContract(LiveMixin.GetAttackerIdOfLastDamage, { Arguments = { "Entity" }, Returns = { "number" } }) function LiveMixin:SetLastDamage(time, attacker) @@ -355,6 +375,26 @@ function LiveMixin:SetLastDamage(time, attacker) end AddFunctionContract(LiveMixin.SetLastDamage, { Arguments = { "Entity", "number", { "Entity", "nil" } }, Returns = { } }) +function LiveMixin:GetCanTakeDamage() + + if self.GetCanTakeDamageOverride then + return self:GetCanTakeDamageOverride() + end + return true + +end +AddFunctionContract(LiveMixin.GetCanTakeDamage, { Arguments = { "Entity" }, Returns = { "boolean" } }) + +function LiveMixin:GetCanGiveDamage() + + if self.GetCanGiveDamageOverride then + return self:GetCanGiveDamageOverride() + end + return false + +end +AddFunctionContract(LiveMixin.GetCanGiveDamage, { Arguments = { "Entity" }, Returns = { "boolean" } }) + /** * Returns true if the damage has killed the entity. */ @@ -384,7 +424,7 @@ AddFunctionContract(LiveMixin.TakeDamage, { Arguments = { "Entity", "number", "E */ function LiveMixin:TakeDamageClient(damage, attacker, doer, point, direction) - if self:GetIsAlive() then + if self:GetIsAlive() and self.OnTakeDamage then self:OnTakeDamage(damage, attacker, doer, point) @@ -398,7 +438,8 @@ AddFunctionContract(LiveMixin.TakeDamageClient, { Arguments = { "Entity", "numbe function LiveMixin:TakeDamageServer(damage, attacker, doer, point, direction) - if (self:GetIsAlive() and GetGamerules():CanEntityDoDamageTo(attacker, self)) then + local killedFromDamage = false + if self:GetIsAlive() and GetGamerules():CanEntityDoDamageTo(attacker, self) then // Get damage type from source local damageType = kDamageType.Normal @@ -427,7 +468,9 @@ function LiveMixin:TakeDamageServer(damage, attacker, doer, point, direction) if damage > 0 then - self:OnTakeDamage(damage, attacker, doer, point) + if self.OnTakeDamage then + self:OnTakeDamage(damage, attacker, doer, point) + end // Remember time we were last hurt for Swarm upgrade self:SetLastDamage(Shared.GetTime(), attacker) @@ -442,17 +485,19 @@ function LiveMixin:TakeDamageServer(damage, attacker, doer, point, direction) // GetDeathIconIndex used to identify the attack type. Server.SendNetworkMessage(doerPlayer, "GiveDamageIndicator", BuildGiveDamageIndicatorMessage(damage, doer:GetDeathIconIndex(), self:isa("Player"), self:GetTeamNumber()), false) end - + if (oldHealth > 0 and self:GetHealth() == 0) then // Do this first to make sure death message is sent GetGamerules():OnKill(self, damage, attacker, doer, point, direction) - self:OnKill(damage, attacker, doer, point, direction) + if self.OnKill then + self:OnKill(damage, attacker, doer, point, direction) + end - self:ProcessFrenzy(attacker, self) + self:SetIsAlive(false) - self.justKilled = true + killedFromDamage = true end @@ -460,7 +505,7 @@ function LiveMixin:TakeDamageServer(damage, attacker, doer, point, direction) end - return (self.justKilled == true) + return killedFromDamage end AddFunctionContract(LiveMixin.TakeDamageServer, { Arguments = { "Entity", "number", "Entity", "Entity", { "Vector", "nil" }, { "Vector", "nil" } }, Returns = { "boolean" } }) @@ -512,18 +557,27 @@ function LiveMixin:AddHealth(health, playSound, noArmor) end AddFunctionContract(LiveMixin.AddHealth, { Arguments = { "Entity", "number", "boolean" }, Returns = { "number" } }) -function LiveMixin:ProcessFrenzy(attacker, targetEntity) +// This function needs to be tested. +function LiveMixin:Kill(attacker, doer, point, direction) + self:TakeDamage((self:GetMaxHealth() + self:GetMaxArmor()), attacker, doer, nil, nil) +end +AddFunctionContract(LiveMixin.Kill, { Arguments = { "Entity", "Entity", "Entity", { "Vector", "nil" }, { "Vector", "nil" } }, Returns = { } }) - // Process Frenzy - give health back according to the amount of extra damage we did - if attacker and attacker.GetHasUpgrade and attacker:GetHasUpgrade(kTechId.Frenzy) and targetEntity and targetEntity.GetOverkillHealth then - - attacker:TriggerEffects("frenzy") - - local overkillHealth = targetEntity:GetOverkillHealth() - local healthToGiveBack = math.max(overkillHealth, kFrenzyMinHealth) - attacker:AddHealth(healthToGiveBack, false) - +// This function needs to be tested. +function LiveMixin:GetSendDeathMessage() + + if self.GetSendDeathMessageOverride then + return self:GetSendDeathMessageOverride() end + return self:GetIsAlive() end -AddFunctionContract(LiveMixin.ProcessFrenzy, { Arguments = { "Entity", "Entity", "Entity" }, Returns = { } }) \ No newline at end of file +AddFunctionContract(LiveMixin.GetSendDeathMessage, { Arguments = { "Entity" }, Returns = { "boolean" } }) + +/** + * Entities using LiveMixin are only selectable when they are alive. + */ +function LiveMixin:OnGetIsSelectable(selectableTable) + selectableTable.Selectable = selectableTable.Selectable and self:GetIsAlive() +end +AddFunctionContract(LiveMixin.OnGetIsSelectable, { Arguments = { "Entity", "table" }, Returns = { } }) \ No newline at end of file diff --git a/ns2/lua/LiveScriptActor.lua b/ns2/lua/LiveScriptActor.lua index 5cc08d5..58de584 100644 --- a/ns2/lua/LiveScriptActor.lua +++ b/ns2/lua/LiveScriptActor.lua @@ -19,11 +19,6 @@ class 'LiveScriptActor' (ScriptActor) LiveScriptActor.kMapName = "livescriptactor" -LiveScriptActor.kHealth = 100 -LiveScriptActor.kArmor = 0 - -LiveScriptActor.kDefaultPointValue = 10 - LiveScriptActor.kMoveToDistance = 1 if (Server) then @@ -34,12 +29,6 @@ end LiveScriptActor.networkVars = { - // Purchased tech (carapace, piercing, etc.). Also includes - // global and class upgrades we didn't explicitly buy (armor1). - upgrade1 = "enum kTechId", - upgrade2 = "enum kTechId", - upgrade3 = "enum kTechId", - upgrade4 = "enum kTechId", // Number of furys that are affecting this entity furyLevel = string.format("integer (0 to %d)", kMaxStackLevel), @@ -58,7 +47,7 @@ function LiveScriptActor:OnCreate() ScriptActor.OnCreate(self) - InitMixin(self, LiveMixin, { kHealth = LiveScriptActor.kHealth, kArmor = LiveScriptActor.kArmor }) + InitMixin(self, LiveMixin) InitMixin(self, OrdersMixin, { kMoveToDistance = LiveScriptActor.kMoveToDistance }) InitMixin(self, FireMixin) @@ -71,11 +60,6 @@ function LiveScriptActor:OnInit() self.timeLastUpdate = nil - self.upgrade1 = kTechId.None - self.upgrade2 = kTechId.None - self.upgrade3 = kTechId.None - self.upgrade4 = kTechId.None - self.furyLevel = 0 self.activityEnd = 0 @@ -109,102 +93,16 @@ function LiveScriptActor:GetCanNewActivityStart() return false end -// Used for sentries/hydras to figure out what to attack first -function LiveScriptActor:GetCanDoDamage() - return false -end - function LiveScriptActor:GetCanIdle() return self:GetIsAlive() end -function LiveScriptActor:GetHasUpgrade(techId) - return techId ~= kTechId.None and (techId == self.upgrade1 or techId == self.upgrade2 or techId == self.upgrade3 or techId == self.upgrade4) -end - -function LiveScriptActor:GiveUpgrade(techId) - - if not self:GetHasUpgrade(techId) then - - if self.upgrade1 == kTechId.None then - - self.upgrade1 = techId - return true - - elseif self.upgrade2 == kTechId.None then - - self.upgrade2 = techId - return true - - elseif self.upgrade3 == kTechId.None then - - self.upgrade3 = techId - return true - - elseif self.upgrade4 == kTechId.None then - - self.upgrade4 = techId - return true - - end - - Print("%s:GiveUpgrade(%d): Player already has the max of four upgrades.", self:GetClassName()) - - else - Print("%s:GiveUpgrade(%d): Player already has tech %s.", self:GetClassName(), techId, GetDisplayNameForTechId(techId)) - end - - return false - -end - -function LiveScriptActor:OnGiveUpgrade(techId) -end - -function LiveScriptActor:GetUpgrades() - local upgrades = {} - - if self.upgrade1 ~= kTechId.None then - table.insert(upgrades, self.upgrade1) - end - if self.upgrade2 ~= kTechId.None then - table.insert(upgrades, self.upgrade2) - end - if self.upgrade3 ~= kTechId.None then - table.insert(upgrades, self.upgrade3) - end - if self.upgrade4 ~= kTechId.None then - table.insert(upgrades, self.upgrade4) - end - - return upgrades -end - -// Used for flying creatures so they stay at this height off the ground whenever possible -function LiveScriptActor:GetHoverHeight() - return 0 -end - -// Returns text and 0-1 scalar for status bar on commander HUD when selected. Return nil to display nothing. -function LiveScriptActor:GetStatusDescription() - return nil, nil -end - function LiveScriptActor:OnUpdate(deltaTime) PROFILE("LiveScriptActor:OnUpdate") ScriptActor.OnUpdate(self, deltaTime) - // Process outside of OnProcessMove() because animations can't be set there - if Server then - self:UpdateJustKilled() - end - - if (self.controller ~= nil and not self:GetIsAlive()) then - self:DestroyController() - end - // Update expiring stackable game effects if Server then @@ -217,30 +115,6 @@ function LiveScriptActor:OnUpdate(deltaTime) end -function LiveScriptActor:GetIsSelectable() - return self:GetIsAlive() -end - -function LiveScriptActor:GetPointValue() - return LookupTechData(self:GetTechId(), kTechDataPointValue, LiveScriptActor.kDefaultPointValue) -end - -// If the gamerules indicate it's OK an entity to take damage, it calls this. World objects or those without -// health can return false. -function LiveScriptActor:GetCanTakeDamage() - return true -end - -function LiveScriptActor:OnEntityChange(entityId, newEntityId) - - ScriptActor.OnEntityChange(self, entityId, newEntityId) - - if entityId == self.fireAttackerId then - self.fireAttackerId = newEntityId - end - -end - function LiveScriptActor:GetFuryLevel() return self.furyLevel end @@ -259,8 +133,4 @@ function LiveScriptActor:AdjustFuryFireDelay(inDelay) end -function LiveScriptActor:GetSendDeathMessage() - return self:GetIsAlive() -end - Shared.LinkClassToMap("LiveScriptActor", LiveScriptActor.kMapName, LiveScriptActor.networkVars ) \ No newline at end of file diff --git a/ns2/lua/LiveScriptActor_Client.lua b/ns2/lua/LiveScriptActor_Client.lua index 63ca4ab..ed179bc 100644 --- a/ns2/lua/LiveScriptActor_Client.lua +++ b/ns2/lua/LiveScriptActor_Client.lua @@ -5,72 +5,4 @@ // Created by: Charlie Cleveland (charlie@unknownworlds.com) // // -// ========= For more information, visit us at http://www.unknownworlds.com ===================== - -function LiveScriptActor:OnTakeDamage(damage, attacker, doer, point) -end - -function LiveScriptActor:OnKill(damage, attacker, doer, point, direction) -end - -function LiveScriptActor:OnSynchronized() - - PROFILE("LiveScriptActor:OnSynchronized") - - ScriptActor.OnSynchronized(self) - self:UpdateEffects() - -end - -// Display text when selected -function LiveScriptActor:GetCustomSelectionText() - return "" -end - -function LiveScriptActor:UpdateEffects() - - // Play on-fire cinematic every so often if we're on fire - if self:GetGameEffectMask(kGameEffect.OnFire) and self:GetIsAlive() and self:GetIsVisible() then - - // If we haven't played effect for a bit - local time = Shared.GetTime() - - if not self.timeOfLastFireEffect or (time > (self.timeOfLastFireEffect + .5)) then - - local firstPerson = (Client.GetLocalPlayer() == self) - local cinematicName = GetOnFireCinematic(self, firstPerson) - - if firstPerson then - local viewModel = self:GetViewModelEntity() - if viewModel then - Shared.CreateAttachedEffect(self, cinematicName, viewModel, Coords.GetTranslation(Vector(0, 0, 0)), "", true, false) - end - else - Shared.CreateEffect(self, cinematicName, self, self:GetAngles():GetCoords()) - end - - self.timeOfLastFireEffect = time - - end - - end - - // If our cloak state changes, all effects to change - if HasMixin(self, "Cloakable") or HasMixin(self, "Camouflage") then - - if self.clientCloaked == nil then - self.clientCloaked = false - end - - local newHiddenState = (HasMixin(self, "Cloakable") and self:GetIsCloaked()) or (HasMixin(self, "Camouflage") and self:GetIsCamouflaged()) - if self.clientCloaked ~= newHiddenState then - - local isEnemy = GetEnemyTeamNumber(self:GetTeamNumber()) == Client.GetLocalPlayer():GetTeamNumber() - self:TriggerEffects("client_cloak_changed", {cloaked = newHiddenState, enemy = isEnemy}) - self.clientCloaked = newHiddenState - - end - - end - -end +// ========= For more information, visit us at http://www.unknownworlds.com ===================== \ No newline at end of file diff --git a/ns2/lua/LiveScriptActor_Server.lua b/ns2/lua/LiveScriptActor_Server.lua index 42dbab6..0cb3db9 100644 --- a/ns2/lua/LiveScriptActor_Server.lua +++ b/ns2/lua/LiveScriptActor_Server.lua @@ -6,394 +6,15 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== -function LiveScriptActor:CopyDataFrom(player) - - self.gameEffectsFlags = player.gameEffectsFlags - - table.copy(player.gameEffects, self.gameEffects) - - self.timeOfLastDamage = player.timeOfLastDamage - - self.furyLevel = player.furyLevel - - self.activityEnd = player.activityEnd - - self.pathingEnabled = player.pathingEnabled - -end - function LiveScriptActor:SetPathingEnabled(state) self.pathingEnabled = state end -function LiveScriptActor:Upgrade(newTechId) - - if self:GetTechId() ~= newTechId then - - // Preserve health and armor scalars but potentially change maxHealth and maxArmor - local healthScalar = self:GetHealthScalar() - local armorScalar = self:GetArmorScalar() - - self:SetTechId(newTechId) - - self:SetMaxHealth(LookupTechData(newTechId, kTechDataMaxHealth, self:GetMaxHealth())) - self:SetMaxArmor(LookupTechData(newTechId, kTechDataMaxArmor, self:GetMaxArmor())) - - self:SetHealth(healthScalar * self:GetMaxHealth()) - self:SetArmor(armorScalar * self:GetMaxArmor()) - - return true - - end - - return false - -end - -function LiveScriptActor:UpdateJustKilled() - - if self.justKilled then - - // Clear current animation so we know if it was set in TriggerEffects - self:SetAnimation("", true) - - self:TriggerEffects("death") - - // Destroy immediately if death animation or ragdoll wasn't triggered (used queued because we're in OnProcessMove) - local anim = self:GetAnimation() - if (self:GetPhysicsGroup() == PhysicsGroup.RagdollGroup) or (anim ~= nil and anim ~= "") then - - // Set default time to destroy so it's impossible to have things lying around - self.timeToDestroy = Shared.GetTime() + 4 - self:SetNextThink(.1) - - else - self:SafeDestroy() - end - - self.justKilled = nil - - end - -end - -function LiveScriptActor:GetDamageImpulse(damage, doer, point) - if damage and doer and point then - return GetNormalizedVector(doer:GetOrigin() - point) * (damage / 40) * .01 - end - return nil -end - -function LiveScriptActor:OnTakeDamage(damage, attacker, doer, point) - - // Play audio/visual effects when taking damage - local damageType = kDamageType.Normal - if doer then - damageType = doer:GetDamageType() - end - - // Apply directed impulse to physically simulated objects, according to amount of damage - if (self.physicsModel ~= nil and self.physicsType == Actor.PhysicsType.Dynamic) then - local damageImpulse = self:GetDamageImpulse(damage, doer, point) - if damageImpulse then - self.physicsModel:AddImpulse(point, damageImpulse) - end - end - -end - -function LiveScriptActor:GetTimeOfLastDamage() - return self.timeOfLastDamage -end - function LiveScriptActor:SetFuryLevel(level) self.furyLevel = level end -function LiveScriptActor:Reset() - - ScriptActor.Reset(self) - self:ResetUpgrades() - self:ClearOrders() - -end - -function LiveScriptActor:OnKill(damage, attacker, doer, point, direction) - - // Give points to killer - local pointOwner = attacker - - // If the pointOwner is not a player, award it's points to it's owner. - if pointOwner ~= nil and not pointOwner:isa("Player") then - pointOwner = pointOwner:GetOwner() - end - if(pointOwner ~= nil and pointOwner:isa("Player") and pointOwner:GetTeamNumber() ~= self:GetTeamNumber()) then - pointOwner:AddScore(self:GetPointValue()) - end - - self:SetIsAlive(false) - - if point then - self.deathImpulse = self:GetDamageImpulse(damage, doer, point) - self.deathPoint = Vector(point) - end - - self:ResetUpgrades() - self:ClearOrders() - - ScriptActor.OnKill(self, damage, attacker, doer, point, direction) - -end - -function LiveScriptActor:ResetUpgrades() - self.upgrade1 = kTechId.None - self.upgrade2 = kTechId.None - self.upgrade3 = kTechId.None - self.upgrade4 = kTechId.None -end - -function LiveScriptActor:SetRagdoll(deathTime) - - if self:GetPhysicsGroup() ~= PhysicsGroup.RagdollGroup then - - self:SetPhysicsType(Actor.PhysicsType.Dynamic) - - self:SetPhysicsGroup(PhysicsGroup.RagdollGroup) - - // Apply landing blow death impulse to ragdoll (but only if we didn't play death animation) - if self.deathImpulse and self.deathPoint and self.physicsModel and self.physicsType == Actor.PhysicsType.Dynamic then - - self.physicsModel:AddImpulse(self.deathPoint, self.deathImpulse) - self.deathImpulse = nil - - end - - if deathTime then - - self.timeToDestroy = Shared.GetTime() + deathTime - - self:SetNextThink(.1) - - end - - end - -end - -function LiveScriptActor:OnThink() - - ScriptActor.OnThink(self) - - if self.timeToDestroy and (Shared.GetTime() > self.timeToDestroy) then - - self:SafeDestroy() - - else - self:SetNextThink(.1) - end - -end - -function LiveScriptActor:SafeDestroy() - - if bit.bor(self.gameEffectsFlags, kGameEffect.OnFire) then - self:TriggerEffects("fire_stop") - end - - if(self:GetIsMapEntity()) then - - self:SetIsAlive(false) - self:SetIsVisible(false) - self:SetNextThink(-1) - self:SetPhysicsType(Actor.PhysicsType.None) - - else - - DestroyEntity(self) - - end - -end - -function LiveScriptActor:Kill(attacker, doer, point, direction) - self:TakeDamage(1000, attacker, doer, nil, nil) -end - // If false, then MoveToTarget() projects entity down to floor function LiveScriptActor:GetIsFlying() return false -end - -/** - * Return the passed in position casted down to the ground. - */ -function LiveScriptActor:GetGroundAt(position, physicsGroupMask) - - local topOffset = self:GetExtents().y - local startPosition = position + Vector(0, topOffset, 0) - local endPosition = position - Vector(0, 1000, 0) - - local trace = Shared.TraceRay(startPosition, endPosition, physicsGroupMask, EntityFilterOne(self)) - - // If we didn't hit anything, then use our existing position. This - // prevents objects from constantly moving downward if they get outside - // of the bounds of the map. - if trace.fraction ~= 1 then - return trace.endPoint - else - return position - end - -end - -function LiveScriptActor:GetHoverAt(position) - - local ground = self:GetGroundAt(position, PhysicsMask.AIMovement) - local resultY = position.y - // if we have a hover height, use it to find our minimum height above ground, otherwise use zero - - local minHeightAboveGround = 0 - if self.GetHoverHeight then - minHeightAboveGround = self:GetHoverHeight() - end - - local heightAboveGround = resultY - ground.y - - // always snap "up", snap "down" only if not flying - if heightAboveGround <= minHeightAboveGround or not self:GetIsFlying() then - resultY = resultY + minHeightAboveGround - heightAboveGround - end - - if resultY ~= position.y then - return Vector(position.x, resultY, position.z) - end - - return position - -end - -function LiveScriptActor:GetWaypointGroupName() - return ConditionalValue(self:GetIsFlying(), kAirWaypointsGroup, kDefaultWaypointGroup) -end - -function LiveScriptActor:MoveToTarget(physicsGroupMask, location, movespeed, time) - PROFILE("LiveScriptActor:MoveToTarget") - - local movement = nil - local newLocation = self:GetOrigin() - local now = Shared.GetTime() - local hasReachedLocation = false//self:IsTargetReached(location, 0.01, true) - - local direction = (location - self:GetOrigin()):GetUnit(); - if self.pathingEnabled then - if not (hasReachedLocation) then - if not self:IsPathValid(self:GetOrigin(), location) then - if not (self:BuildPath(self:GetOrigin(), location)) then - return - end - end - - if (self:GetCurrentPathPoint() ~= nil and self:GetNumPoints() >= 1) then - self:RestartPathing(now) - local point = self:GetNextPoint(time, movespeed) - if (point ~= nil) then - newLocation = point - direction = self:GetPathDirection() - SetAnglesFromVector(self, direction) - end - end - end - end - - if self:GetIsFlying() then - newLocation = self:GetHoverAt(newLocation) - end - - self:SetOrigin(newLocation) - if (self.controller and not self:GetIsFlying()) then - self:UpdateControllerFromEntity() - self:PerformMovement(Vector(0, -1000, 0), 1) - end - -end - -function LiveScriptActor:PerformAction(techNode, position) - - if(techNode:GetTechId() == kTechId.Stop) then - self:ClearOrders() - return true - end - - return ScriptActor.PerformAction(self, techNode, position) - -end - -function LiveScriptActor:OnWeld(entity, elapsedTime) -end - -// Overrideable by children. Called on server only. -function LiveScriptActor:OnGameEffectMaskChanged(effect, state) - - if effect == kGameEffect.OnFire and state then - self:TriggerEffects("fire_start") - elseif effect == kGameEffect.OnFire and not state then - self:TriggerEffects("fire_stop") - end - -end - -function LiveScriptActor:GetMeleeAttackDamage() - return 5 -end - -function LiveScriptActor:GetMeleeAttackInterval() - return .6 -end - -function LiveScriptActor:GetMeleeAttackOrigin() - return self:GetOrigin() -end - -function LiveScriptActor:MeleeAttack(target, time) - - local meleeAttackInterval = self:AdjustFuryFireDelay(self:GetMeleeAttackInterval()) - - if(Shared.GetTime() > (self.timeOfLastAttack + meleeAttackInterval)) then - - self:TriggerEffects(string.format("%s_melee_attack", string.lower(self:GetClassName()))) - - // Traceline from us to them - local trace = Shared.TraceRay(self:GetMeleeAttackOrigin(), target:GetOrigin(), PhysicsMask.AllButPCs, EntityFilterTwo(self, target)) - - local direction = target:GetOrigin() - self:GetOrigin() - direction:Normalize() - - // Use player or owner (in the case of MACs, Drifters, etc.) - local attacker = self:GetOwner() - if self:isa("Player") then - attacker = self - end - - target:TakeDamage(self:GetMeleeAttackDamage(), attacker, self, trace.endPoint, direction) - - // Play hit effects - doer, target, origin, surface - TriggerHitEffects(self, target, trace.endPoint, trace.surface, true) - - self.timeOfLastAttack = Shared.GetTime() - - end - -end - -// Get target of attack order, if any -function LiveScriptActor:GetTarget() - local target = nil - - local order = self:GetCurrentOrder() - if order ~= nil and (order:GetType() == kTechId.Attack or order:GetType() == kTechId.SetTarget) then - target = Shared.GetEntity(order:GetParam()) - end - - return target -end - +end \ No newline at end of file diff --git a/ns2/lua/MAC.lua b/ns2/lua/MAC.lua index 68a573f..28f6ca5 100644 --- a/ns2/lua/MAC.lua +++ b/ns2/lua/MAC.lua @@ -13,11 +13,19 @@ Script.Load("lua/DoorMixin.lua") Script.Load("lua/EnergyMixin.lua") Script.Load("lua/BuildingMixin.lua") Script.Load("lua/mixins/ControllerMixin.lua") +Script.Load("lua/RagdollMixin.lua") +Script.Load("lua/UpgradableMixin.lua") +Script.Load("lua/PointGiverMixin.lua") Script.Load("lua/GameEffectsMixin.lua") Script.Load("lua/FlinchMixin.lua") +Script.Load("lua/SelectableMixin.lua") Script.Load("lua/TargetMixin.lua") Script.Load("lua/LOSMixin.lua") +if Server then + Script.Load("lua/AttackOrderMixin.lua") +end + class 'MAC' (LiveScriptActor) MAC.kMapName = "mac" @@ -71,6 +79,7 @@ MAC.networkVars = {} PrepareClassForMixin(MAC, EnergyMixin) PrepareClassForMixin(MAC, ControllerMixin) +PrepareClassForMixin(MAC, UpgradableMixin) PrepareClassForMixin(MAC, GameEffectsMixin) PrepareClassForMixin(MAC, FlinchMixin) @@ -79,11 +88,19 @@ function MAC:OnCreate() LiveScriptActor.OnCreate(self) InitMixin(self, ControllerMixin) + InitMixin(self, RagdollMixin) + InitMixin(self, DoorMixin) + InitMixin(self, EnergyMixin) + InitMixin(self, BuildingMixin) + InitMixin(self, UpgradableMixin) InitMixin(self, GameEffectsMixin) InitMixin(self, FlinchMixin) + InitMixin(self, PointGiverMixin) InitMixin(self, PathingMixin) + InitMixin(self, SelectableMixin) if Server then + InitMixin(self, AttackOrderMixin, { kMoveToDistance = MAC.kMoveToDistance }) InitMixin(self, TargetMixin) InitMixin(self, LOSMixin) end @@ -100,16 +117,12 @@ function MAC:OnCreate() end function MAC:OnInit() - - InitMixin(self, DoorMixin) - InitMixin(self, EnergyMixin ) - InitMixin(self, BuildingMixin ) LiveScriptActor.OnInit(self) self:SetModel(MAC.kModelName) - self:SetPhysicsType(Actor.PhysicsType.Kinematic) + self:SetPhysicsType(PhysicsType.Kinematic) if(Server) then @@ -334,7 +347,7 @@ function MAC:ProcessWeldOrder(deltaTime) else // otherwise move towards it - local hoverAdjustedLocation = self:GetHoverAt(target:GetEngagementPoint()) + local hoverAdjustedLocation = GetHoverAt(self, target:GetEngagementPoint()) self:MoveToTarget(PhysicsMask.AIMovement, hoverAdjustedLocation, self:GetMoveSpeed(), deltaTime) self.timeOfLastWeld = time end @@ -372,7 +385,7 @@ end function MAC:ProcessMove(deltaTime) local currentOrder = self:GetCurrentOrder() - local hoverAdjustedLocation = self:GetHoverAt(currentOrder:GetLocation()) + local hoverAdjustedLocation = GetHoverAt(self, currentOrder:GetLocation()) self:MoveToTarget(PhysicsMask.AIMovement, hoverAdjustedLocation, self:GetMoveSpeed(), deltaTime) if(self:IsTargetReached(currentOrder:GetLocation(), kEpsilon)) then @@ -381,13 +394,16 @@ function MAC:ProcessMove(deltaTime) else self:TriggerEffects("mac_move") end + end function MAC:PlayChatSound(soundName) + if self.timeOfLastChatterSound == 0 or (Shared.GetTime() > self.timeOfLastChatterSound + 2) then self:PlaySound(soundName) self.timeOfLastChatterSound = Shared.GetTime() end + end // Look for other MACs and Drifters to greet as we fly by @@ -486,7 +502,7 @@ function MAC:ProcessBuildConstruct(deltaTime) end end else - local hoverAdjustedLocation = self:GetHoverAt(currentOrder:GetLocation()) + local hoverAdjustedLocation = GetHoverAt(self, currentOrder:GetLocation()) self:MoveToTarget(PhysicsMask.AIMovement, hoverAdjustedLocation, self:GetMoveSpeed(), deltaTime) end end @@ -517,7 +533,8 @@ function MAC:UpdateOrders(deltaTime) if(currentOrder:GetType() == kTechId.Move) then self:ProcessMove(deltaTime) self:UpdateGreetings() - elseif(currentOrder:GetType() == kTechId.Attack) then + elseif(currentOrder:GetType() == kTechId.Attack) then + // From AttackOrderMixin. self:ProcessAttackOrder(1, GetDevScalar(MAC.kMoveSpeed, 8), deltaTime) elseif(currentOrder:GetType() == kTechId.Weld) then setNextThink = self:ProcessWeldOrder(deltaTime) @@ -541,7 +558,9 @@ function MAC:OnUpdate(deltaTime) self:UpdateControllerFromEntity() - self:UpdateEnergy(deltaTime) + if Server then + self:UpdateEnergy(deltaTime) + end end @@ -610,10 +629,6 @@ function MAC:GetTechButtons(techId) end -function MAC:GetWaypointGroupName() - return kAirWaypointsGroup -end - function MAC:OnOverrideDoorInteraction(inEntity) // MACs will not open the door if they are currently // welding it shut diff --git a/ns2/lua/NS2ConsoleCommands_Server.lua b/ns2/lua/NS2ConsoleCommands_Server.lua index fef2014..9f02ec4 100644 --- a/ns2/lua/NS2ConsoleCommands_Server.lua +++ b/ns2/lua/NS2ConsoleCommands_Server.lua @@ -776,6 +776,25 @@ function OnCommandTarget(client, cmd) end end +function OnCommandPhantom(client, cmd) + + if client ~= nil and Shared.GetCheatsEnabled() then + + if type(cmd) == "string" then + + local player = client:GetControllingPlayer() + local origin = player:GetOrigin() + + StartPhantomMode(player, cmd, origin) + + else + Print("Must pass map name.") + end + + end + +end + // GC commands Event.Hook("Console_changegcsettingserver", OnCommandChangeGCSettingServer) @@ -847,3 +866,4 @@ Event.Hook("Console_setgameeffect", OnCommandSetGameEffect) Event.Hook("Console_eject", OnCommandEject) Event.Hook("Console_cyst", OnCommandCyst) Event.Hook("Console_target", OnCommandTarget) +Event.Hook("Console_phantom", OnCommandPhantom) \ No newline at end of file diff --git a/ns2/lua/NS2Utility.lua b/ns2/lua/NS2Utility.lua index 8bc4aa3..54b6ae5 100644 --- a/ns2/lua/NS2Utility.lua +++ b/ns2/lua/NS2Utility.lua @@ -182,13 +182,12 @@ function CheckBuildEntityRequirements(techId, position, player, ignoreEntity) local techNode = techTree:GetTechNode(techId) local attachClass = LookupTechData(techId, kStructureAttachClass) - // Build tech can't be built on top of LiveScriptActors + // Build tech can't be built on top of non-attachment entities. if techNode and techNode:GetIsBuild() then local trace = Shared.TraceBox(GetExtents(techId), position + Vector(0, 1, 0), position - Vector(0, 3, 0), PhysicsMask.AllButPCs, EntityFilterOne(ignoreEntity)) - // $AS - We special case Drop Packs as they are not LiveScriptActors but you should not be - // able to build on top of them + // $AS - We special case Drop Packs you should not be able to build on top of them. if trace.entity and trace.entity:GetHasPathingFlag(kPathingFlags.UnBuildable) then legalBuild = false end @@ -323,11 +322,63 @@ function GetIsBuildLegal(techId, position, snapRadius, player, ignoreEntity) end +/** + * Return the passed in position casted down to the ground. + */ +function GetGroundAt(entity, position, physicsGroupMask) + + local topOffset = entity:GetExtents().y + local startPosition = position + Vector(0, topOffset, 0) + local endPosition = position - Vector(0, 1000, 0) + + local trace = Shared.TraceRay(startPosition, endPosition, physicsGroupMask, EntityFilterOne(entity)) + + // If we didn't hit anything, then use our existing position. This + // prevents objects from constantly moving downward if they get outside + // of the bounds of the map. + if trace.fraction ~= 1 then + return trace.endPoint + else + return position + end + +end + +function GetHoverAt(entity, position) + + local ground = GetGroundAt(entity, position, PhysicsMask.AIMovement) + local resultY = position.y + // if we have a hover height, use it to find our minimum height above ground, otherwise use zero + + local minHeightAboveGround = 0 + if entity.GetHoverHeight then + minHeightAboveGround = entity:GetHoverHeight() + end + + local heightAboveGround = resultY - ground.y + + // always snap "up", snap "down" only if not flying + if heightAboveGround <= minHeightAboveGround or not entity:GetIsFlying() then + resultY = resultY + minHeightAboveGround - heightAboveGround + end + + if resultY ~= position.y then + return Vector(position.x, resultY, position.z) + end + + return position + +end + +function GetWaypointGroupName(entity) + return ConditionalValue(entity:GetIsFlying(), kAirWaypointsGroup, kDefaultWaypointGroup) +end + function GetTriggerEntity(position, teamNumber) local triggerEntity = nil local minDist = nil - local ents = GetEntitiesForTeamWithinRange("LiveScriptActor", teamNumber, position, .5) + local ents = GetEntitiesWithMixinForTeamWithinRange("Live", teamNumber, position, .5) for index, ent in ipairs(ents) do @@ -399,7 +450,7 @@ end function GetBlockedByUmbra(entity) - if entity ~= nil and entity:isa("LiveScriptActor") then + if entity ~= nil and HasMixin(entity, "GameEffects") then if entity:GetGameEffectMask(kGameEffect.InUmbra) and (NetworkRandomInt(1, Crag.kUmbraBulletChance, "GetBlockedByUmbra") == 1) then return true @@ -1070,7 +1121,7 @@ function ResetLights() end end -// Pulled out into separate function so phantasms can use it too +// Pulled out into separate function so phantoms can use it too function SetPlayerPoseParameters(player, viewAngles, velocity, maxSpeed, maxBackwardSpeedScalar, crouchAmount) local pitch = -Math.Wrap( Math.Degrees(viewAngles.pitch), -180, 180 ) @@ -1286,7 +1337,7 @@ function GetActivationTarget(teamNumber, position) local nearestTarget = nil local nearestDist = nil - local targets = GetEntitiesForTeamWithinRange("LiveScriptActor", teamNumber, position, 2) + local targets = GetEntitiesWithMixinForTeamWithinRange("Live", teamNumber, position, 2) for index, target in ipairs(targets) do if target:GetIsVisible() and not target:isa("Infestation") then @@ -1612,7 +1663,7 @@ function CanEntityDoDamageTo(attacker, target, cheats, devMode, friendlyFire) end // Phantom damage sources can't damage players - if attacker ~= nil and HasMixin(attacker, "Phantom") and attacker:GetIsPhantom() then + if attacker ~= nil and HasMixin(attacker, "Phantom") and attacker:GetIsPhantom() and (attacker ~= target) then return false end @@ -1651,4 +1702,5 @@ function CanEntityDoDamageTo(attacker, target, cheats, devMode, friendlyFire) // Allow damage of own stuff when testing return teamsOK -end \ No newline at end of file +end + diff --git a/ns2/lua/Observatory.lua b/ns2/lua/Observatory.lua index 209ff17..584160d 100644 --- a/ns2/lua/Observatory.lua +++ b/ns2/lua/Observatory.lua @@ -6,6 +6,7 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") class 'Observatory' (Structure) @@ -18,6 +19,14 @@ Observatory.kScanSound = PrecacheAsset("sound/ns2.fev/marine/structures/observat Observatory.kDistressBeaconTime = kDistressBeaconTime Observatory.kDistressBeaconRange = kDistressBeaconRange +function Observatory:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end + function Observatory:OnInit() self:SetModel(Observatory.kModelName) diff --git a/ns2/lua/OrderSelfMixin.lua b/ns2/lua/OrderSelfMixin.lua index 1160117..eb511a0 100644 --- a/ns2/lua/OrderSelfMixin.lua +++ b/ns2/lua/OrderSelfMixin.lua @@ -62,7 +62,7 @@ function OrderSelfMixin:_FindDefendOrder(structuresNearby) local closestStructureDist = Math.infinity for i, structure in ipairs(structuresNearby) do local structureDist = (structure:GetOrigin() - self:GetOrigin()):GetLengthSquared() - local lastTimeDamageTaken, attackerId = structure:GetLastDamage() + local lastTimeDamageTaken = structure:GetTimeOfLastDamage() if lastTimeDamageTaken and lastTimeDamageTaken > 0 and ((Shared.GetTime() - lastTimeDamageTaken) < kTimeToDefendSinceTakingDamage) and (structureDist < closestStructureDist) then closestStructure = structure closestStructureDist = structureDist diff --git a/ns2/lua/OrdersMixin.lua b/ns2/lua/OrdersMixin.lua index 3b052b8..0be0a3e 100644 --- a/ns2/lua/OrdersMixin.lua +++ b/ns2/lua/OrdersMixin.lua @@ -11,6 +11,12 @@ Script.Load("lua/FunctionContracts.lua") OrdersMixin = { } OrdersMixin.type = "Orders" +OrdersMixin.expectedCallbacks = +{ + // GetExtents() is required by GetGroundAt(). + GetExtents = "Returns a Vector describing the extents of this Entity." +} + local kTimeSinceDamageDefendComplete = 10 local kDefendCompleteDistance = 5 @@ -130,6 +136,16 @@ function OrdersMixin:ClearOrders() end AddFunctionContract(OrdersMixin.ClearOrders, { Arguments = { "Entity" }, Returns = { } }) +function OrdersMixin:Reset() + self:ClearOrders() +end +AddFunctionContract(OrdersMixin.Reset, { Arguments = { "Entity" }, Returns = { } }) + +function OrdersMixin:OnKill() + self:ClearOrders() +end +AddFunctionContract(OrdersMixin.OnKill, { Arguments = { "Entity" }, Returns = { } }) + function OrdersMixin:_DestroyOrders() // Allow ents to hook destruction of current order. @@ -186,12 +202,12 @@ function OrdersMixin:_SetOrder(order, clearExisting, insertFirst) // Always snap the location of the order to the ground. local location = order:GetLocation() - if self.GetGroundAt then - location = self:GetGroundAt(location, PhysicsMask.AIMovement) + if location then + location = GetGroundAt(self, location, PhysicsMask.AIMovement) + order:SetLocation(location) end - order:SetLocation(location) - if(insertFirst) then + if insertFirst then table.insert(self.orders, 1, order:GetId()) else table.insert(self.orders, order:GetId()) @@ -294,79 +310,6 @@ function OrdersMixin:ProcessRallyOrder(originatingEntity) end AddFunctionContract(OrdersMixin.ProcessRallyOrder, { Arguments = { "Entity", "Entity" }, Returns = { } }) -// This is an "attack-move" from RTS. Attack the entity specified in our current attack order, if any. -// Otherwise, move to the location specified in the attack order and attack anything along the way. -function OrdersMixin:ProcessAttackOrder(targetSearchDistance, moveSpeed, time) - - // If we have a target, attack it - local currentOrder = self:GetCurrentOrder() - if(currentOrder ~= nil) then - - local target = Shared.GetEntity(currentOrder:GetParam()) - - if target then - - // How do you kill that which has no life? - if not HasMixin(target, "Live") or not target:GetIsAlive() then - - self:CompletedCurrentOrder() - - else - - local targetLocation = target:GetEngagementPoint() - if self:GetIsFlying() then - targetLocation = self:GetHoverAt(targetLocation) - end - - self:MoveToTarget(PhysicsMask.AIMovement, targetLocation, moveSpeed, time) - - end - - else - - // Check for a nearby target. If not found, move towards destination. - target = self:FindTarget(targetSearchDistance) - - end - - if target then - - // If we are close enough to target, attack it - local targetPosition = Vector(target:GetOrigin()) - targetPosition.y = targetPosition.y + self:GetHoverHeight() - - // Different targets can be attacked from different ranges, depending on size - local attackDistance = GetEngagementDistance(currentOrder:GetParam()) - - local distanceToTarget = (targetPosition - self:GetOrigin()):GetLength() - if (distanceToTarget <= attackDistance) and target:GetIsAlive() then - - self:MeleeAttack(target, time) - - - end - - else - - // otherwise move towards attack location and end order when we get there - local targetLocation = currentOrder:GetLocation() - if self:GetIsFlying() then - targetLocation = self:GetHoverAt(targetLocation) - end - self:MoveToTarget(PhysicsMask.AIMovement, targetLocation, moveSpeed, time) - - local distanceToTarget = (currentOrder:GetLocation() - self:GetOrigin()):GetLength() - if(distanceToTarget < self:GetMixinConstants().kMoveToDistance) then - self:CompletedCurrentOrder() - end - - end - - end - -end -AddFunctionContract(OrdersMixin.ProcessAttackOrder, { Arguments = { "Entity", "number", "number", "number" }, Returns = { } }) - function OrdersMixin:UpdateOrder() local currentOrder = self:GetCurrentOrder() @@ -420,7 +363,7 @@ function OrdersMixin:UpdateOrder() self:ClearOrders() - elseif HasMixin(orderTarget, "Live") and (Shared.GetTime() - orderTarget:GetLastDamage()) > kTimeSinceDamageDefendComplete then + elseif HasMixin(orderTarget, "Live") and (Shared.GetTime() - orderTarget:GetTimeOfLastDamage()) > kTimeSinceDamageDefendComplete then // Only complete if self is close enough to the target. if (self:GetOrigin() - orderTarget:GetOrigin()):GetLengthSquared() < (kDefendCompleteDistance * kDefendCompleteDistance) then @@ -437,4 +380,53 @@ function OrdersMixin:UpdateOrder() end end -AddFunctionContract(OrdersMixin.UpdateOrder, { Arguments = { "Entity" }, Returns = { } }) \ No newline at end of file +AddFunctionContract(OrdersMixin.UpdateOrder, { Arguments = { "Entity" }, Returns = { } }) + +// Note: This needs to be tested. +// Get target of attack order, if any. +function OrdersMixin:GetTarget() + + local target = nil + + local order = self:GetCurrentOrder() + if order ~= nil and (order:GetType() == kTechId.Attack or order:GetType() == kTechId.SetTarget) then + target = Shared.GetEntity(order:GetParam()) + end + + return target + +end + +// Note: This needs to be tested. +function OrdersMixin:PerformAction(techNode, position) + + local results = { self:OnPerformAction(techNode, position) } + + // Return true if any of the callbacks returned true. + for i, result in pairs(results) do + + if result then + return true + end + + end + + return false + +end + +/** + * Other mixins can implement this function to handle more specific actions. + * Called when tech tree action is performed on the entity. + * Return true if legal and action handled. Position passed if applicable. + */ +function OrdersMixin:OnPerformAction(techNode, position) + + if techNode:GetTechId() == kTechId.Stop then + self:ClearOrders() + return true + end + + return false + +end \ No newline at end of file diff --git a/ns2/lua/PathingMixin.lua b/ns2/lua/PathingMixin.lua index 891a708..e95253a 100644 --- a/ns2/lua/PathingMixin.lua +++ b/ns2/lua/PathingMixin.lua @@ -72,9 +72,9 @@ local function GeneratePath(src, dst) Pathing.GetPathPoints(src, dst, points) // HACKS - if (#(points) > 0) then + /* if (#(points) > 0) then table.insert( points, #(points) - 1, dst ) - end + end */ if (#(points) ~= 0 ) then SplitPathPoints( points, 0.5 ) @@ -377,4 +377,50 @@ function PathingMixin:ClearPathingFlags(flags) if (extents ~= nil) then Pathing.ClearPolyFlags(position, extents, flags) end + +end + +/** + * This is the bread and butter of PathingMixin. + */ +function PathingMixin:MoveToTarget(physicsGroupMask, location, movespeed, time) + + PROFILE("PathingMixin:MoveToTarget") + + local movement = nil + local newLocation = self:GetOrigin() + local now = Shared.GetTime() + local hasReachedLocation = false//self:IsTargetReached(location, 0.01, true) + + local direction = (location - self:GetOrigin()):GetUnit(); + if self.pathingEnabled then + if not (hasReachedLocation) then + if not self:IsPathValid(self:GetOrigin(), location) then + if not (self:BuildPath(self:GetOrigin(), location)) then + return + end + end + + if (self:GetCurrentPathPoint() ~= nil and self:GetNumPoints() >= 1) then + self:RestartPathing(now) + local point = self:GetNextPoint(time, movespeed) + if (point ~= nil) then + newLocation = point + direction = self:GetPathDirection() + SetAnglesFromVector(self, direction) + end + end + end + end + + if self:GetIsFlying() then + newLocation = GetHoverAt(self, newLocation) + end + + self:SetOrigin(newLocation) + if (self.controller and not self:GetIsFlying()) then + self:UpdateControllerFromEntity() + self:PerformMovement(Vector(0, -1000, 0), 1) + end + end \ No newline at end of file diff --git a/ns2/lua/PhantomEffigy.lua b/ns2/lua/PhantomEffigy.lua new file mode 100644 index 0000000..1412931 --- /dev/null +++ b/ns2/lua/PhantomEffigy.lua @@ -0,0 +1,71 @@ +// ======= Copyright © 2003-2011, Unknown Worlds Entertainment, Inc. All rights reserved. ======= +// +// lua\PhantomEffigy.lua +// +// Created by: Charlie Cleveland (charlie@unknownworlds.com) +// +// Additive entity that is created by Alien Commander via the Shade. Looks like an alien life +// form, which can be +used by friendly players to morph into a PhantomMixin version of that +// lifeform. Invisible to enemies, dissipates if unused after a couple minutes. +// +// ========= For more information, visit us at http://www.unknownworlds.com ===================== +Script.Load("lua/ScriptActor.lua") + +class 'PhantomEffigy' (ScriptActor) + +PhantomEffigy.kMapName = "phantom" +PhantomEffigy.kLifetime = kPhantomEffigyLifetime + +PhantomEffigy.kNetworkVars = {} + +function PhantomEffigy:OnInit() + + ScriptActor.OnInit(self) + + self.createTime = Shared.GetTime() + +end + +if Server then +function PhantomEffigy:OnUpdate(deltaTime) + + ScriptActor.OnUpdate(self, deltaTime) + + if Shared.GetTime() > (self.createTime + PhantomEffigy.kLifetime) then + + self:TriggerEffects("phantom_effigy_expire") + + DestroyEntity(self) + + end + +end +end + +function PhantomEffigy:GetCanBeUsed(player) + // Make sure effigy hasn't been destroyed this frame already. Phantoms can't use effigies of course. + return player and (player:GetTeamNumber() == self:GetTeamNumber()) and (self:GetId() ~= Entity.invalidId) and (not HasMixin(player, "Phantom") or not player:GetIsPhantom()) +end + +function PhantomEffigy:GetEffigyMorphToMapName() + if self:GetTechId() == kTechId.ShadePhantomOnos then + return Onos.kMapName + end + return Fade.kMapName +end + +function PhantomEffigy:OnUse(player, elapsedTime, useAttachPoint, usePoint) + + if Server then + + StartPhantomMode(player, self:GetEffigyMorphToMapName(), self:GetOrigin()) + + DestroyEntity(self) + + // TODO: Animate their camera towards viewpoint + + end + +end + +Shared.LinkClassToMap("PhantomEffigy", PhantomEffigy.kMapName, PhantomEffigy.kNetworkVars) diff --git a/ns2/lua/PhantomMixin.lua b/ns2/lua/PhantomMixin.lua index 0086e32..9df9387 100644 --- a/ns2/lua/PhantomMixin.lua +++ b/ns2/lua/PhantomMixin.lua @@ -2,6 +2,12 @@ // // lua\PhantomMixin.lua // +// Manages a "phantom" version of a player or structure. The alien commander can use the Shade +// to allow players to become phantom versions of other aliens. These can be killed but can't +// do damage. Phantom structures can also be created, which look and act like real versions of +// the structures, but without being functional in any way (don't contribute to tech tree, don't +// have abilities, etc.). +// // Created by: Charlie Cleveland (charlie@unknownworlds.com) // // ========= For more information, visit us at http://www.unknownworlds.com ===================== @@ -13,7 +19,9 @@ PhantomMixin.type = "Phantom" PhantomMixin.expectedCallbacks = { - GetTechId = "Used for?" + GetTechId = "Used for?", + GetId = "", + OnKill = "", } function PhantomMixin.__prepareclass(toClass) @@ -23,7 +31,7 @@ function PhantomMixin.__prepareclass(toClass) local addNetworkFields = { // 0 when not active, > 0 when object is a phantom - lifetime = "float", + phantomLifetime = "float", expired = "boolean", } @@ -34,40 +42,167 @@ function PhantomMixin.__prepareclass(toClass) end function PhantomMixin:__initmixin() - self.lifetime = 0 + self.phantomLifetime = 0 self.expired = false + self.hostPlayerId = Entity.invalidId + self.phantomPlayerId = Entity.invalidId end // Set duration after which we will expire -function PhantomMixin:SetLifetime(lifetime) +function PhantomMixin:SetPhantomLifetime(lifetime) ASSERT(type(lifetime) == "number") ASSERT(lifetime >= 0) - self.lifetime = lifetime + self.phantomLifetime = lifetime self.expired = false end +function PhantomMixin:GetPhantomLifetime() + return self.phantomLifetime +end + function PhantomMixin:GetIsExpired() return self.expired end function PhantomMixin:OnUpdate(deltaTime) - self.lifetime = math.max(self.lifetime - deltaTime, 0) + if self:GetIsPhantom() then - if not self.expired and self.lifetime == 0 then - self.expired = true + self.phantomLifetime = math.max(self.phantomLifetime - deltaTime, 0) + + if not self.expired and self.phantomLifetime == 0 then + + self:OnKill(0, nil, nil, nil, nil) + self.expired = true + + end + end end function PhantomMixin:GetIsPhantom() - return (self.lifetime > 0) + return (self.phantomLifetime > 0) +end + +function PhantomMixin:SetHostPlayer(player) + self.hostPlayerId = player:GetId() +end + +function PhantomMixin:SetPhantomPlayer(newPlayer) + self.phantomPlayerId = newPlayer:GetId() +end + +function PhantomMixin:OnKill(damage, attacker, doer, point, direction) + + if self:GetIsPhantom() then + + self:RestoreToHostPlayer() + + else + + self:KillPhantom(damage, attacker, doer, point, direction) + + end + end +function PhantomMixin:KillPhantom(damage, attacker, doer, point, direction) + // Destroy phantom also, in the same way we are destroyed + if self.phantomPlayerId ~= Entity.invalidId then + + local phantomPlayer = Shared.GetEntity(self.phantomPlayerId) + if phantomPlayer ~= nil and phantomPlayer:isa("Player") then + + // Set before calling OnKill() to avoid recursive issues when calling OnKill() again + self.phantomPlayerId = Entity.invalidId + + phantomPlayer:OnKill(damage, attacker, doer, point, direction) + + return true + + end + + end + + return false + +end +function PhantomMixin:RestoreToHostPlayer() + if self:GetIsPhantom() and (self.hostPlayerId ~= Entity.invalidId) then + + local hostPlayer = Shared.GetEntity(self.hostPlayerId) + if hostPlayer ~= nil and hostPlayer:isa("Player") then + + local playerId = self:GetId() + local killedPlayer = Shared.GetEntity(playerId) + + ASSERT(killedPlayer ~= nil) + ASSERT(killedPlayer:isa("Player")) + + // Player now controls original again + local owner = Server.GetOwner(killedPlayer) + ASSERT(owner ~= nil) + hostPlayer:SetControllingPlayer(owner) + + // Notify others of the change + killedPlayer:SendEntityChanged(hostPlayer:GetId()) + + // No longer in phantom mode + self.phantomLifetime = 0 + self.hostPlayerId = Entity.invalidId + + return true + + else + Print("PhantomMixin:OnKill(): Couldn't find host player (%s)", ToString(self.hostPlayerId)) + end + end + + return false + +end + +function PhantomMixin:GetPhantom() + if self.phantomPlayerId ~= Entity.invalidId then + return Shared.GetEntity(self.phantomPlayerId) + end + return nil +end + +function PhantomMixin:GetHost() + if self.hostPlayerId ~= Entity.invalidId then + return Shared.GetEntity(self.hostPlayerId) + end + return nil +end + +function StartPhantomMode(player, mapName, origin) + + // Transform them into the alien type represented by this effigy + local owner = Server.GetOwner(player) + local newPlayer = CreateEntity(mapName, origin, player:GetTeamNumber()) + newPlayer:SetControllingPlayer(owner) + newPlayer:SetName(player:GetName()) + + // Notify others of the change + player:SendEntityChanged(newPlayer:GetId()) + + // Set their PhantomMixin() active + ASSERT(HasMixin(newPlayer, "Phantom")) + newPlayer:SetPhantomLifetime(kPhantomLifetime) + player:SetPhantomPlayer(newPlayer) + newPlayer:SetHostPlayer(player) + + player:SetScoreboardChanged(true) + newPlayer:SetScoreboardChanged(true) + + newPlayer:TriggerEffects("phantom_effigy_start") +end \ No newline at end of file diff --git a/ns2/lua/PhaseGate.lua b/ns2/lua/PhaseGate.lua index 99fefa4..0f0f8ba 100644 --- a/ns2/lua/PhaseGate.lua +++ b/ns2/lua/PhaseGate.lua @@ -6,6 +6,7 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") // Transform angles, view angles and velocity from srcCoords to destCoords (when going through phase gate) local function TransformPlayerCoordsForPhaseGate(player, srcCoords, destCoords) @@ -40,6 +41,14 @@ PhaseGate.networkVars = destLocationId = "entityid", } +function PhaseGate:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end + function PhaseGate:OnInit() Structure.OnInit(self) diff --git a/ns2/lua/PhysicsGroups.lua b/ns2/lua/PhysicsGroups.lua index aaa0654..ddbc7ed 100644 --- a/ns2/lua/PhysicsGroups.lua +++ b/ns2/lua/PhysicsGroups.lua @@ -102,4 +102,10 @@ PhysicsMask = enum OnlyInfestation = CreateGroupsAllowedMask(PhysicsGroup.InfestationGroup) } - +PhysicsType = enum + { + 'None', // No physics representation. + 'Dynamic', // Bones are driven by physics simulation (client-side only) + 'DynamicServer', // Bones are driven by physics simulation (synced with server) + 'Kinematic' // Physics model is updated by animation + } \ No newline at end of file diff --git a/ns2/lua/Player.lua b/ns2/lua/Player.lua index 6aef65d..73fea1b 100644 --- a/ns2/lua/Player.lua +++ b/ns2/lua/Player.lua @@ -17,8 +17,13 @@ Script.Load("lua/TooltipMixin.lua") Script.Load("lua/WeaponOwnerMixin.lua") Script.Load("lua/DoorMixin.lua") Script.Load("lua/mixins/ControllerMixin.lua") +Script.Load("lua/ScoringMixin.lua") +Script.Load("lua/RagdollMixin.lua") +Script.Load("lua/UpgradableMixin.lua") +Script.Load("lua/PointGiverMixin.lua") Script.Load("lua/GameEffectsMixin.lua") Script.Load("lua/FlinchMixin.lua") +Script.Load("lua/SelectableMixin.lua") Script.Load("lua/TargetMixin.lua") Script.Load("lua/LOSMixin.lua") @@ -227,6 +232,7 @@ Player.networkVars = } PrepareClassForMixin(Player, ControllerMixin) +PrepareClassForMixin(Player, UpgradableMixin) PrepareClassForMixin(Player, GameEffectsMixin) PrepareClassForMixin(Player, FlinchMixin) @@ -235,8 +241,13 @@ function Player:OnCreate() LiveScriptActor.OnCreate(self) InitMixin(self, ControllerMixin) + InitMixin(self, ScoringMixin, { kMaxScore = kMaxScore }) + InitMixin(self, RagdollMixin) + InitMixin(self, UpgradableMixin) InitMixin(self, GameEffectsMixin) InitMixin(self, FlinchMixin) + InitMixin(self, PointGiverMixin) + InitMixin(self, SelectableMixin) if Server then InitMixin(self, TargetMixin) InitMixin(self, LOSMixin) @@ -277,7 +288,6 @@ function Player:OnCreate() self.timeLastSayingsAction = 0 self.reticleTarget = false self.timeTargetHit = 0 - self.score = 0 self.kills = 0 self.deaths = 0 @@ -983,7 +993,7 @@ function Player:GetIsPlaying() return self.gameStarted and (self:GetTeamNumber() == kTeam1Index or self:GetTeamNumber() == kTeam2Index) end -function Player:GetCanTakeDamage() +function Player:GetCanTakeDamageOverride() local teamNumber = self:GetTeamNumber() return (teamNumber == kTeam1Index or teamNumber == kTeam2Index) end @@ -1121,9 +1131,6 @@ function Player:GetMoveDirection(moveVelocity) end -function Player:UpdateEnergy(input) -end - function Player:EndUse(deltaTime) if not self:GetIsUsing() then return @@ -2339,10 +2346,6 @@ function Player:GetCanBeUsed(player) return false end -function Player:GetScore() - return self.score -end - function Player:GetScoreboardChanged() return self.scoreboardChanged end @@ -2459,7 +2462,7 @@ function Player:GetPlayerStatusDesc() return "" end -function Player:GetCanDoDamage() +function Player:GetCanGiveDamageOverride() return true end diff --git a/ns2/lua/Player_Server.lua b/ns2/lua/Player_Server.lua index 9109168..51407c6 100644 --- a/ns2/lua/Player_Server.lua +++ b/ns2/lua/Player_Server.lua @@ -152,20 +152,20 @@ end function Player:OnTakeDamage(damage, attacker, doer, point) - LiveScriptActor.OnTakeDamage(self, damage, attacker, doer, point) - if self:GetTeamType() == kAlienTeamType then self:GetTeam():TriggerAlert(kTechId.AlienAlertLifeformUnderAttack, self) end // Play damage indicator for player if point ~= nil then + local damageOrigin = doer:GetOrigin() local doerParent = doer:GetParent() if doerParent then damageOrigin = doerParent:GetOrigin() end Server.SendNetworkMessage(self, "TakeDamageIndicator", BuildTakeDamageIndicatorMessage(damageOrigin, damage), true) + end end @@ -199,22 +199,16 @@ end */ function Player:OnKill(damage, killer, doer, point, direction) + // Determine the killer's player name. local killerName = nil + if killer ~= nil and not killer:isa("Player") then - local pointOwner = killer - // If the pointOwner is not a player, award it's points to it's owner. - if pointOwner ~= nil and not pointOwner:isa("Player") then - pointOwner = pointOwner:GetOwner() - end - if(pointOwner and pointOwner:isa("Player") and pointOwner ~= self and pointOwner:GetTeamNumber() == GetEnemyTeamNumber(self:GetTeamNumber())) then - - killerName = pointOwner:GetName() - pointOwner:AddKill() - - local resAwarded = pointOwner:AwardResForKill(self) - pointOwner:AddScore(self:GetPointValue(), resAwarded) + local realKiller = killer:GetOwner() + if realKiller and realKiller:isa("Player") then + killerName = realKiller:GetName() + end - end + end // Save death to server log if(killer == self) then @@ -238,14 +232,6 @@ function Player:OnKill(damage, killer, doer, point, direction) self:AddDeaths() - // Don't allow us to do anything - self:SetIsAlive(false) - - self:ResetUpgrades() - - // On fire, in umbra, etc. - self:ClearGameEffects() - // Fade out screen self.timeOfDeath = Shared.GetTime() @@ -256,7 +242,7 @@ function Player:OnKill(damage, killer, doer, point, direction) self:RemoveChildren() // Create a rag doll - self:SetPhysicsType(Actor.PhysicsType.Dynamic) + self:SetPhysicsType(PhysicsType.Dynamic) self:SetPhysicsGroup(PhysicsGroup.RagdollGroup) // Set next think to 0 to disable @@ -350,11 +336,15 @@ function Player:OnUpdate(deltaTime) if ((self.timeOfDeath ~= nil) and (time - self.timeOfDeath > kFadeToBlackTime)) then - // Destroy the existing player and create a spectator in their place. - local spectator = self:Replace(self:GetDeathMapName()) + // Destroy the existing player and create a spectator in their place (but only if it has an owner, ie not a body left behind by Phantom use) + local owner = Server.GetOwner(self) + if owner then - // Queue up the spectator for respawn. - spectator:GetTeam():PutPlayerInRespawnQueue(spectator, Shared.GetTime()) + // Queue up the spectator for respawn. + local spectator = self:Replace(self:GetDeathMapName()) + spectator:GetTeam():PutPlayerInRespawnQueue(spectator, Shared.GetTime()) + + end end @@ -404,8 +394,13 @@ end function Player:CopyPlayerDataFrom(player) - LiveScriptActor.CopyDataFrom(self, player) - + // This is stuff from the former LiveScriptActor. + self.gameEffectsFlags = player.gameEffectsFlags + table.copy(player.gameEffects, self.gameEffects) + self.timeOfLastDamage = player.timeOfLastDamage + self.furyLevel = player.furyLevel + self.activityEnd = player.activityEnd + // ScriptActor and Actor fields self:SetAngles(player:GetAngles()) self:SetOrigin(Vector(player:GetOrigin())) @@ -549,7 +544,7 @@ function Player:Replace(mapName, newTeamNumber, preserveChildren) player:RemoveChildren() - local childEntities = GetChildEntities(self) + local childEntities = GetChildEntities(self, "Weapon") for index, entity in ipairs(childEntities) do entity:SetParent(player) @@ -744,7 +739,7 @@ function Player:RemoveChildren() self.activeWeaponIndex = 0 // Loop through all children and delete them. - local childEntities = GetChildEntities(self, "Actor") + local childEntities = GetChildEntities(self) for index, entity in ipairs(childEntities) do entity:SetParent(nil) DestroyEntity(entity) @@ -775,22 +770,6 @@ function Player:GetViewModelBlendTime() return .1 end -function Player:GetScore() - return self.score -end - -function Player:AddScore(points, res) - - // Tell client to display cool effect - if(points ~= nil and points ~= 0) then - local displayRes = ConditionalValue(type(res) == "number", res, 0) - Server.SendCommand(self, string.format("points %s %s", tostring(points), tostring(displayRes))) - self.score = Clamp(self.score + points, 0, kMaxScore) - self:SetScoreboardChanged(true) - end - -end - function Player:GetKills() return self.kills end @@ -902,7 +881,7 @@ function Player:UpdateOrderWaypoint() if(currentOrder ~= nil) then local targetLoc = Vector(currentOrder:GetLocation()) - self.nextOrderWaypoint = Server.GetNextWaypoint(PhysicsMask.AIMovement, self, self:GetWaypointGroupName(), targetLoc) + self.nextOrderWaypoint = Server.GetNextWaypoint(PhysicsMask.AIMovement, self, GetWaypointGroupName(self), targetLoc) self.finalWaypoint = Vector(targetLoc) self.nextOrderWaypointActive = true self.waypointType = currentOrder:GetType() diff --git a/ns2/lua/PlayingTeam.lua b/ns2/lua/PlayingTeam.lua index b978ef7..127a030 100644 --- a/ns2/lua/PlayingTeam.lua +++ b/ns2/lua/PlayingTeam.lua @@ -911,7 +911,8 @@ function PlayingTeam:UpdateGameEffects(timePassed) if self.timeSinceLastGameEffectUpdate >= PlayingTeam.kUpdateGameEffectsInterval then - // Friendly entities that alien structures can affect + // Friendly entities that this team's structures can affect. Any entity on this team with + // the GameEffects Mixin. local teamEntities = GetEntitiesWithMixinForTeam("GameEffects", self:GetTeamNumber()) local enemyPlayers = GetEntitiesForTeam("Player", GetEnemyTeamNumber(self:GetTeamNumber())) @@ -924,19 +925,6 @@ function PlayingTeam:UpdateGameEffects(timePassed) end function PlayingTeam:UpdateTeamSpecificGameEffects(teamEntities, enemyPlayers) - - local catchFireEntities = {} - - for index, entity in ipairs(teamEntities) do - if HasMixin(entity, "Fire") then - entity:UpdateFire(PlayingTeam.kUpdateGameEffectsInterval) - end - end - - for index, catchFireEntity in ipairs(catchFireEntities) do - catchFireEntity:SetGameEffectMask(kGameEffect.OnFire, true) - end - end function PlayingTeam:VoteToEjectCommander(votingPlayer, targetCommander) diff --git a/ns2/lua/PointGiverMixin.lua b/ns2/lua/PointGiverMixin.lua new file mode 100644 index 0000000..590c604 --- /dev/null +++ b/ns2/lua/PointGiverMixin.lua @@ -0,0 +1,58 @@ +// ======= Copyright © 2003-2011, Unknown Worlds Entertainment, Inc. All rights reserved. ======= +// +// lua\PointGiverMixin.lua +// +// Created by: Brian Cronin (brianc@unknownworlds.com) +// +// ========= For more information, visit us at http://www.unknownworlds.com ===================== + +Script.Load("lua/FunctionContracts.lua") + +/** + * PointGiverMixin handles awarding points on kills and other events. + */ +PointGiverMixin = { } +PointGiverMixin.type = "PointGiver" + +local kDefaultPointValue = 10 + +PointGiverMixin.expectedCallbacks = +{ + GetTeamNumber = "Returns the team number this PointGiver is on.", + GetTechId = "Returns the tech Id of this PointGiver." +} + +function PointGiverMixin:__initmixin() +end + +function PointGiverMixin:GetPointValue() + return LookupTechData(self:GetTechId(), kTechDataPointValue, kDefaultPointValue) +end + +/** + * If a AwardResForKill() function is defined on the attacker then it will be called + * passing in self to check how many resources should be awarded for killing self. + */ +function PointGiverMixin:OnKill(damage, attacker, doer, point, direction) + + // Give points to killer. + local pointOwner = attacker + + // If the pointOwner is not a player, award it's points to it's owner. + if pointOwner ~= nil and not HasMixin(pointOwner, "Scoring") then + pointOwner = pointOwner:GetOwner() + end + + // Points not awarded for entities on the same team. + if pointOwner ~= nil and HasMixin(pointOwner, "Scoring") and pointOwner:GetTeamNumber() ~= self:GetTeamNumber() then + + pointOwner:AddKill() + local resAwarded = 0 + if pointOwner.AwardResForKill then + resAwarded = pointOwner:AwardResForKill(self) + end + pointOwner:AddScore(self:GetPointValue(), resAwarded) + + end + +end \ No newline at end of file diff --git a/ns2/lua/PowerPack.lua b/ns2/lua/PowerPack.lua index 6a6b8ba..e6061e2 100644 --- a/ns2/lua/PowerPack.lua +++ b/ns2/lua/PowerPack.lua @@ -8,6 +8,7 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") class 'PowerPack' (Structure) @@ -21,6 +22,14 @@ if Server then Script.Load("lua/PowerPack_Server.lua") end +function PowerPack:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end + function PowerPack:OnInit() self:SetModel(PowerPack.kModelName) diff --git a/ns2/lua/PowerPack_Server.lua b/ns2/lua/PowerPack_Server.lua index dfa7865..72da1f4 100644 --- a/ns2/lua/PowerPack_Server.lua +++ b/ns2/lua/PowerPack_Server.lua @@ -26,13 +26,6 @@ function PowerPack:OnDestroy() end -function PowerPack:SafeDestroy() - self:SetIsAlive(false) - self:SetNextThink(-1) - - Structure.SafeDestroy(self) -end - function PowerPack:UpdateNearbyPowerState() // Trigger event to update power for nearby structures diff --git a/ns2/lua/PowerPoint.lua b/ns2/lua/PowerPoint.lua index 54ea48a..29e8869 100644 --- a/ns2/lua/PowerPoint.lua +++ b/ns2/lua/PowerPoint.lua @@ -104,7 +104,7 @@ function PowerPoint:Reset() end -function PowerPoint:GetCanTakeDamage() +function PowerPoint:GetCanTakeDamageOverride() return self.powered end diff --git a/ns2/lua/PowerPoint_Server.lua b/ns2/lua/PowerPoint_Server.lua index dfdb8ca..f69ab96 100644 --- a/ns2/lua/PowerPoint_Server.lua +++ b/ns2/lua/PowerPoint_Server.lua @@ -42,15 +42,6 @@ function PowerPoint:OnKill(damage, attacker, doer, point, direction) end -// Power should never be destroyed or ragdoll or they'll be destroyed for good -function PowerPoint:SafeDestroy() - self:SetIsAlive(false) - self:SetNextThink(-1) -end - -function PowerPoint:SetRagdoll(deathTime) -end - function PowerPoint:OnLoad() Structure.OnLoad(self) @@ -269,6 +260,6 @@ function PowerPoint:UpdatePoweredStructures() end -function PowerPoint:GetSendDeathMessage() +function PowerPoint:GetSendDeathMessageOverride() return self.powered -end +end \ No newline at end of file diff --git a/ns2/lua/PropDynamic.lua b/ns2/lua/PropDynamic.lua index 1255626..7bc33eb 100644 --- a/ns2/lua/PropDynamic.lua +++ b/ns2/lua/PropDynamic.lua @@ -30,10 +30,10 @@ if (Server) then end if self.dynamic then - self:SetPhysicsType(Actor.PhysicsType.Dynamic) + self:SetPhysicsType(PhysicsType.Dynamic) self:SetPhysicsGroup(PhysicsGroup.RagdollGroup) else - self:SetPhysicsType(Actor.PhysicsType.Kinematic) + self:SetPhysicsType(PhysicsType.Kinematic) end // Don't collide when commanding if not full alpha diff --git a/ns2/lua/PrototypeLab.lua b/ns2/lua/PrototypeLab.lua index 2ff07f6..d3fd999 100644 --- a/ns2/lua/PrototypeLab.lua +++ b/ns2/lua/PrototypeLab.lua @@ -6,6 +6,7 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") class 'PrototypeLab' (Structure) @@ -13,6 +14,14 @@ PrototypeLab.kMapName = "prototypelab" PrototypeLab.kModelName = PrecacheAsset("models/marine/prototype_module/prototype_module.model") +function PrototypeLab:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end + function PrototypeLab:OnInit() self:SetModel(PrototypeLab.kModelName) diff --git a/ns2/lua/Ragdoll.lua b/ns2/lua/Ragdoll.lua index c81d4e7..36bf2b4 100644 --- a/ns2/lua/Ragdoll.lua +++ b/ns2/lua/Ragdoll.lua @@ -17,7 +17,7 @@ function Ragdoll:OnInit() ScriptActor.OnInit(self) - self:SetPhysicsType(Actor.PhysicsType.Dynamic) + self:SetPhysicsType(PhysicsType.Dynamic) self:SetPhysicsGroup(PhysicsGroup.RagdollGroup) if(Server) then diff --git a/ns2/lua/RagdollMixin.lua b/ns2/lua/RagdollMixin.lua new file mode 100644 index 0000000..faffd60 --- /dev/null +++ b/ns2/lua/RagdollMixin.lua @@ -0,0 +1,172 @@ +// ======= Copyright © 2003-2011, Unknown Worlds Entertainment, Inc. All rights reserved. ======= +// +// lua\RagdollMixin.lua +// +// Created by: Brian Cronin (brianc@unknownworlds.com) +// +// ========= For more information, visit us at http://www.unknownworlds.com ===================== + +Script.Load("lua/FunctionContracts.lua") + +local function GetDamageImpulse(damage, doer, point) + + if damage and doer and point then + return GetNormalizedVector(doer:GetOrigin() - point) * (damage / 40) * .01 + end + return nil + +end + +RagdollMixin = { } +RagdollMixin.type = "Ragdoll" + +RagdollMixin.expectedMixins = +{ + Live = "Needed for SetIsAlive()." +} + +RagdollMixin.expectedCallbacks = +{ + SetPhysicsType = "Sets the physics to the passed in type.", + GetPhysicsType = "Returns the physics type, dynamic, kinematic, etc.", + SetPhysicsGroup = "Sets the physics group to the passed in value.", + GetPhysicsGroup = "", + GetPhysicsModel = "Returns the physics model.", + SetAnimation = "", + TriggerEffects = "" +} + +function RagdollMixin:__initmixin() +end + +function RagdollMixin:OnTakeDamage(damage, attacker, doer, point) + + // Apply directed impulse to physically simulated objects, according to amount of damage. + if self:GetPhysicsModel() ~= nil and self:GetPhysicsType() == PhysicsType.Dynamic then + + local damageImpulse = GetDamageImpulse(damage, doer, point) + if damageImpulse then + self:GetPhysicsModel():AddImpulse(point, damageImpulse) + end + + end + +end +AddFunctionContract(RagdollMixin.OnTakeDamage, { Arguments = { "Entity", "number", "Entity", { "Entity", "nil" }, "Vector" }, Returns = { } }) + +function RagdollMixin:OnKill(damage, attacker, doer, point, direction) + + self.justKilled = true + if point then + self.deathImpulse = GetDamageImpulse(damage, doer, point) + self.deathPoint = Vector(point) + end + +end +AddFunctionContract(RagdollMixin.OnKill, { Arguments = { "Entity", "number", "Entity", { "Entity", "nil" }, "Vector" }, Returns = { } }) + +function RagdollMixin:SetRagdoll(deathTime) + + if self:GetPhysicsGroup() ~= PhysicsGroup.RagdollGroup then + + self:SetPhysicsType(PhysicsType.Dynamic) + + self:SetPhysicsGroup(PhysicsGroup.RagdollGroup) + + // Apply landing blow death impulse to ragdoll (but only if we didn't play death animation). + if self.deathImpulse and self.deathPoint and self:GetPhysicsModel() and self:GetPhysicsType() == PhysicsType.Dynamic then + + self:GetPhysicsModel():AddImpulse(self.deathPoint, self.deathImpulse) + self.deathImpulse = nil + self.deathPoint = nil + + end + + if deathTime then + self.timeToDestroy = deathTime + end + + end + +end +AddFunctionContract(RagdollMixin.SetRagdoll, { Arguments = { "Entity", { "number", "nil" } }, Returns = { } }) + +function RagdollMixin:OnUpdate(deltaTime) + + // Process outside of OnProcessMove() because animations can't be set there. + if Server then + self:_UpdateJustKilled() + self:_UpdateTimeToDestroy(deltaTime) + end + +end +AddFunctionContract(RagdollMixin.OnUpdate, { Arguments = { "Entity", "number" }, Returns = { } }) + +function RagdollMixin:_UpdateJustKilled() + + if self.justKilled then + + // Clear current animation so we know if it was set in TriggerEffects + self:SetAnimation("", true) + + self:TriggerEffects("death") + + // Destroy immediately if death animation or ragdoll wasn't triggered (used queued because we're in OnProcessMove) + local anim = self:GetAnimation() + if (self:GetPhysicsGroup() == PhysicsGroup.RagdollGroup) or (anim ~= nil and anim ~= "") then + + if self.timeToDestroy == nil then + // Set default time to destroy so it's impossible to have things lying around. + self.timeToDestroy = 4 + end + + else + self:SafeDestroy() + end + + self.justKilled = nil + + end + +end +AddFunctionContract(RagdollMixin._UpdateJustKilled, { Arguments = { "Entity" }, Returns = { } }) + +function RagdollMixin:_UpdateTimeToDestroy(deltaTime) + + if self.timeToDestroy then + + self.timeToDestroy = self.timeToDestroy - deltaTime + + if self.timeToDestroy <= 0 then + + self:SafeDestroy() + self.timeToDestroy = nil + + end + + end + +end +AddFunctionContract(RagdollMixin._UpdateTimeToDestroy, { Arguments = { "Entity" }, Returns = { } }) + +function RagdollMixin:SafeDestroy() + + // Note: This should be moved somewhere else soon. + if self.GetIsOnFire and self:GetIsOnFire() then + self:TriggerEffects("fire_stop") + end + + if self:GetIsMapEntity() then + + self:SetIsAlive(false) + self:SetIsVisible(false) + self:SetPhysicsType(PhysicsType.None) + + else + + DestroyEntity(self) + + end + +end +AddFunctionContract(RagdollMixin.SafeDestroy, { Arguments = { "Entity" }, Returns = { } }) \ No newline at end of file diff --git a/ns2/lua/ResourcePoint.lua b/ns2/lua/ResourcePoint.lua index d2a570b..2a477ba 100644 --- a/ns2/lua/ResourcePoint.lua +++ b/ns2/lua/ResourcePoint.lua @@ -33,7 +33,7 @@ function ResourcePoint:OnInit() self:SetPhysicsGroup(PhysicsGroup.AttachClassGroup) // Make the nozzle kinematic so that the player will collide with it. - self:SetPhysicsType(Actor.PhysicsType.Kinematic) + self:SetPhysicsType(PhysicsType.Kinematic) self:SetTechId(kTechId.ResourcePoint) diff --git a/ns2/lua/ResourceTower.lua b/ns2/lua/ResourceTower.lua index bc1d027..af0cd51 100644 --- a/ns2/lua/ResourceTower.lua +++ b/ns2/lua/ResourceTower.lua @@ -8,6 +8,7 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") class 'ResourceTower' (Structure) @@ -23,7 +24,7 @@ ResourceTower.kMaxUpgradeLevel = 3 // they find. Same as in NS1. ResourceTower.kBuildDelay = 4 -local networkVars = +ResourceTower.networkVars = { upgradeLevel = string.format("integer (0 to %d)", ResourceTower.kMaxUpgradeLevel) } @@ -32,6 +33,14 @@ if (Server) then Script.Load("lua/ResourceTower_Server.lua") end +function ResourceTower:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end + function ResourceTower:OnInit() Structure.OnInit(self) @@ -56,18 +65,4 @@ function ResourceTower:GiveResourcesToTeam(player) end -/* -function ResourceTower:GetDescription() - - local description = Structure.GetDescription(self) - - // Add upgrade level - local upgradeLevel = self:GetUpgradeLevel() - description = string.format("%s - +%d of %d", description, self:GetUpgradeLevel(), ResourceTower.kMaxUpgradeLevel) - - return description - -end -*/ - -Shared.LinkClassToMap("ResourceTower", ResourceTower.kMapName, networkVars) +Shared.LinkClassToMap("ResourceTower", ResourceTower.kMapName, ResourceTower.networkVars) diff --git a/ns2/lua/RoboticsFactory.lua b/ns2/lua/RoboticsFactory.lua index b916903..e970c52 100644 --- a/ns2/lua/RoboticsFactory.lua +++ b/ns2/lua/RoboticsFactory.lua @@ -6,6 +6,7 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") class 'RoboticsFactory' (Structure) @@ -26,13 +27,21 @@ local networkVars = currentBuiltId = "entityid" } +function RoboticsFactory:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end + function RoboticsFactory:OnInit() self:SetModel(RoboticsFactory.kModelName) Structure.OnInit(self) - self:SetPhysicsType(Actor.PhysicsType.Kinematic) + self:SetPhysicsType(PhysicsType.Kinematic) self.currentArcId = Entity.invalidId diff --git a/ns2/lua/ScoringMixin.lua b/ns2/lua/ScoringMixin.lua new file mode 100644 index 0000000..a26d8b4 --- /dev/null +++ b/ns2/lua/ScoringMixin.lua @@ -0,0 +1,49 @@ +// ======= Copyright © 2003-2011, Unknown Worlds Entertainment, Inc. All rights reserved. ======= +// +// lua\ScoringMixin.lua +// +// Created by: Brian Cronin (brianc@unknownworlds.com) +// +// ========= For more information, visit us at http://www.unknownworlds.com ===================== + +Script.Load("lua/FunctionContracts.lua") + +/** + * ScoringMixin keeps track of a score. It provides function to allow changing the score. + */ +ScoringMixin = { } +ScoringMixin.type = "Scoring" + +ScoringMixin.expectedCallbacks = +{ + SetScoreboardChanged = "Called to notify the entity that the score has changed and should be updated on the client's scoreboard." +} + +function ScoringMixin:__initmixin() + + self.score = 0 + +end + +function ScoringMixin:GetScore() + return self.score +end +AddFunctionContract(ScoringMixin.GetScore, { Arguments = { "Entity" }, Returns = { "number" } }) + +function ScoringMixin:AddScore(points, res) + + // Should only be called on the Server. + assert(Server and Client == nil) + + // Tell client to display cool effect. + if points ~= nil and points ~= 0 then + + local displayRes = ConditionalValue(type(res) == "number", res, 0) + Server.SendCommand(self, string.format("points %s %s", tostring(points), tostring(displayRes))) + self.score = Clamp(self.score + points, 0, self:GetMixinConstants().kMaxScore or 100) + self:SetScoreboardChanged(true) + + end + +end +AddFunctionContract(ScoringMixin.AddScore, { Arguments = { "Entity", "number", { "number", "nil" } }, Returns = { } }) \ No newline at end of file diff --git a/ns2/lua/SelectableMixin.lua b/ns2/lua/SelectableMixin.lua new file mode 100644 index 0000000..1dbf475 --- /dev/null +++ b/ns2/lua/SelectableMixin.lua @@ -0,0 +1,39 @@ +// ======= Copyright © 2003-2011, Unknown Worlds Entertainment, Inc. All rights reserved. ======= +// +// lua\SelectableMixin.lua +// +// Created by: Brian Cronin (brianc@unknownworlds.com) +// +// ========= For more information, visit us at http://www.unknownworlds.com ===================== + +Script.Load("lua/FunctionContracts.lua") + +/** + * SelectableMixin marks entities as selectable to a commander. + */ +SelectableMixin = { } +SelectableMixin.type = "Selectable" + +SelectableMixin.optionalCallbacks = +{ + OnGetIsSelectable = "Passes in a table with a Selectable field that can be set to true or false." +} + +function SelectableMixin:__initmixin() +end + +function SelectableMixin:GetIsSelectable() + + if self.OnGetIsSelectable then + + // Assume selectable by default. + local selectableTable = { Selectable = true } + self:OnGetIsSelectable(selectableTable) + return selectableTable.Selectable + + end + + return true + +end +AddFunctionContract(SelectableMixin.GetIsSelectable, { Arguments = { "Entity" }, Returns = { "boolean" } }) \ No newline at end of file diff --git a/ns2/lua/Sentry.lua b/ns2/lua/Sentry.lua index c6806cf..ccfb54c 100644 --- a/ns2/lua/Sentry.lua +++ b/ns2/lua/Sentry.lua @@ -6,6 +6,7 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") class 'Sentry' (Structure) @@ -96,6 +97,8 @@ function Sentry:OnCreate() Structure.OnCreate(self) + InitMixin(self, RagdollMixin) + self.desiredYawDegrees = 0 self.desiredPitchDegrees = 0 self.barrelYawDegrees = 0 @@ -340,7 +343,7 @@ function Sentry:UpdatePoseParameters(deltaTime) end -function Sentry:GetCanDoDamage() +function Sentry:GetCanGiveDamageOverride() return true end diff --git a/ns2/lua/Shade.lua b/ns2/lua/Shade.lua index ae3e63b..4b1995a 100644 --- a/ns2/lua/Shade.lua +++ b/ns2/lua/Shade.lua @@ -15,12 +15,13 @@ // team to get a stealth hive built. Allow players to stay cloaked for awhile, until they attack // (even if they move out of range - great for getting by sentries). // -// Phantasm (Targeted) - Allow Commander to create fake Fade, Onos, Hive (and possibly +// Phantom (Targeted) - Allow Commander to create fake Fade, Onos, Hive (and possibly // ammo/medpacks). They can be pathed around and used to create tactical distractions or divert // forces elsewhere. // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") class 'Shade' (Structure) @@ -34,6 +35,13 @@ Shade.kCloakRadius = 15 // when cloak is triggered, we cloak everything around us at this interval Shade.kActiveThinkInterval = 3 +function Shade:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end function Shade:GetIsAlienStructure() return true @@ -45,7 +53,7 @@ function Shade:GetTechButtons(techId) if techId == kTechId.RootMenu then - techButtons = { kTechId.UpgradesMenu, kTechId.ShadeDisorient, kTechId.ShadeCloak, kTechId.ShadePhantasmMenu } + techButtons = { kTechId.UpgradesMenu, kTechId.ShadeDisorient, kTechId.ShadeCloak, kTechId.ShadePhantomMenu } // Allow structure to be upgraded to mature version local upgradeIndex = table.maxn(techButtons) + 1 @@ -59,9 +67,9 @@ function Shade:GetTechButtons(techId) techButtons = {kTechId.CamouflageTech, kTechId.FeintTech, kTechId.None} techButtons[kAlienBackButtonIndex] = kTechId.RootMenu - elseif techId == kTechId.ShadePhantasmMenu then + elseif techId == kTechId.ShadePhantomMenu then - techButtons = {kTechId.ShadePhantasmFade, kTechId.ShadePhantasmOnos, kTechId.ShadePhantasmHive} + techButtons = {kTechId.ShadePhantomFade, kTechId.ShadePhantomOnos} techButtons[kAlienBackButtonIndex] = kTechId.RootMenu end @@ -79,7 +87,7 @@ function Shade:OnResearchComplete(structure, researchId) // Transform into mature shade if structure and (structure:GetId() == self:GetId()) and (researchId == kTechId.UpgradeShade) then - success = self:Upgrade(kTechId.MatureShade) + success = self:UpgradeToTechId(kTechId.MatureShade) end @@ -147,6 +155,23 @@ function Shade:GetActivationTechAllowed(techId) return true end +function CreatePhantom(techId, position, normal, commander) + + // Create phantom effigy nearby + local phantomEffigy = CreateEntityForCommander(kTechId.PhantomEffigy, position, commander) + phantomEffigy:SetTechId(techId) + + // Set model + local modelName = LookupTechData(techId, kTechDataModel) + phantomEffigy:SetModel(modelName) + + // Don't let enemy team see this, but allow ready room and friendly commander/team-mates + phantomEffigy:SetExcludeRelevancyMask( ConditionalValue(commander:GetTeamNumber() == 1, kRelevantToTeam2Unit, kRelevantToTeam1Unit) ) + + return true + +end + function Shade:PerformActivation(techId, position, normal, commander) local success = false @@ -155,6 +180,10 @@ function Shade:PerformActivation(techId, position, normal, commander) success = self:TriggerCloak() + elseif (techId == kTechId.ShadePhantomFade) or (techId == kTechId.ShadePhantomOnos) then + + success = CreatePhantom(techId, position, normal, commander) + end return success diff --git a/ns2/lua/Shared.lua b/ns2/lua/Shared.lua index c043004..0f98a47 100644 --- a/ns2/lua/Shared.lua +++ b/ns2/lua/Shared.lua @@ -93,8 +93,7 @@ Script.Load("lua/Drifter.lua") Script.Load("lua/Egg.lua") Script.Load("lua/Embryo.lua") Script.Load("lua/Cocoon.lua") -Script.Load("lua/Phantasm.lua") -Script.Load("lua/OnosPhantasm.lua") +Script.Load("lua/PhantomEffigy.lua") // Base players Script.Load("lua/ReadyRoomPlayer.lua") diff --git a/ns2/lua/Shift.lua b/ns2/lua/Shift.lua index 26b0be9..d6dba6f 100644 --- a/ns2/lua/Shift.lua +++ b/ns2/lua/Shift.lua @@ -14,6 +14,7 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Structure.lua") +Script.Load("lua/RagdollMixin.lua") class 'Shift' (Structure) @@ -31,6 +32,14 @@ Shift.kEnergizeEffect = PrecacheAsset("cinematics/alien/shift/energize.cinematic Shift.kEnergizeSmallTargetEffect = PrecacheAsset("cinematics/alien/shift/energize_small.cinematic") Shift.kEnergizeLargeTargetEffect = PrecacheAsset("cinematics/alien/shift/energize_large.cinematic") +function Shift:OnCreate() + + Structure.OnCreate(self) + + InitMixin(self, RagdollMixin) + +end + function Shift:GetIsAlienStructure() return true end @@ -70,7 +79,7 @@ function Shift:OnResearchComplete(structure, researchId) // Transform into mature shift if structure and (structure:GetId() == self:GetId()) and (researchId == kTechId.UpgradeShift) then - success = self:Upgrade(kTechId.MatureShift) + success = self:UpgradeToTechId(kTechId.MatureShift) end diff --git a/ns2/lua/Structure.lua b/ns2/lua/Structure.lua index 3c4f787..47e3f3c 100644 --- a/ns2/lua/Structure.lua +++ b/ns2/lua/Structure.lua @@ -9,7 +9,10 @@ // ========= For more information, visit us at http://www.unknownworlds.com ===================== Script.Load("lua/Balance.lua") Script.Load("lua/LiveScriptActor.lua") +Script.Load("lua/UpgradableMixin.lua") +Script.Load("lua/PointGiverMixin.lua") Script.Load("lua/GameEffectsMixin.lua") +Script.Load("lua/SelectableMixin.lua") Script.Load("lua/FlinchMixin.lua") Script.Load("lua/CloakableMixin.lua") Script.Load("lua/TargetMixin.lua") @@ -68,6 +71,7 @@ Structure.networkVars = } PrepareClassForMixin(Structure, EnergyMixin) +PrepareClassForMixin(Structure, UpgradableMixin) PrepareClassForMixin(Structure, GameEffectsMixin) PrepareClassForMixin(Structure, FlinchMixin) PrepareClassForMixin(Structure, CloakableMixin) @@ -76,8 +80,11 @@ function Structure:OnCreate() LiveScriptActor.OnCreate(self) + InitMixin(self, UpgradableMixin) InitMixin(self, GameEffectsMixin) InitMixin(self, FlinchMixin) + InitMixin(self, PointGiverMixin) + InitMixin(self, SelectableMixin) InitMixin(self, CloakableMixin) InitMixin(self, PathingMixin) @@ -91,7 +98,7 @@ function Structure:OnCreate() self:SetUpdates(true) // Make the structure kinematic so that the player will collide with it. - self:SetPhysicsType(Actor.PhysicsType.Kinematic) + self:SetPhysicsType(PhysicsType.Kinematic) self.effectsActive = false @@ -244,8 +251,9 @@ function Structure:GetTechAllowed(techId, techNode, player) end function Structure:GetStatusDescription() - - if (not self:GetIsBuilt() ) then + if (self:GetRecycleActive()) then + return Locale.ResolveString("RECYCLING") .. "...", self:GetResearchProgress() + elseif (not self:GetIsBuilt() ) then return Locale.ResolveString("CONSTRUCTING") .. "...", self:GetBuiltFraction() @@ -354,4 +362,8 @@ function Structure:OverrideVisionRadius() return LOSMixin.kStructureMinLOSDistance end +function Structure:GetRecycleActive() + return self.researchingId == kTechId.Recycle +end + Shared.LinkClassToMap("Structure", Structure.kMapName, Structure.networkVars) diff --git a/ns2/lua/Structure_Server.lua b/ns2/lua/Structure_Server.lua index 91356ff..da1a06b 100644 --- a/ns2/lua/Structure_Server.lua +++ b/ns2/lua/Structure_Server.lua @@ -68,8 +68,9 @@ function Structure:OnUse(player, elapsedTime, useAttachPoint, usePoint) end function Structure:UpdateResearch(timePassed) - - if (self:GetIsBuilt() and (self.researchingId ~= kTechId.None)) then + local shouldUpdate = (self:GetIsBuilt() or self:GetRecycleActive()) + + if (shouldUpdate and (self.researchingId ~= kTechId.None)) then local timePassed = Shared.GetTime() - self.timeResearchStarted @@ -100,33 +101,27 @@ function Structure:UpdateRecycle(timePassed) // TODO: end -function Structure:Upgrade(newTechId) +function Structure:OnPreUpgradeToTechId(newTechId) - if self:GetTechId() ~= newTechId then - - // Preserve health and armor scalars but potentially change maxHealth and maxArmor - local energyScalar = self.energy / self.maxEnergy - - self.maxEnergy = LookupTechData(newTechId, kTechDataMaxEnergy, self.maxEnergy) - - self.energy = energyScalar * self.maxEnergy - - return LiveScriptActor.Upgrade(self, newTechId) - - end + // Preserve health and armor scalars but potentially change maxHealth and maxArmor. + local energyScalar = self.energy / self.maxEnergy - return false + self.maxEnergy = LookupTechData(newTechId, kTechDataMaxEnergy, self.maxEnergy) + + self.energy = energyScalar * self.maxEnergy end function Structure:UpdateStructure(timePassed) - if self:GetIsBuilt() then + local shouldUpdate = (self:GetIsBuilt() or self:GetRecycleActive()) - self:UpdateResearch(timePassed) - + if shouldUpdate then + self:UpdateResearch(timePassed) + end + + if self:GetIsBuilt() then self:UpdateEnergy(timePassed) - end self:UpdateRecycle(timePassed) @@ -182,8 +177,6 @@ end // Play hurt or wound effects function Structure:OnTakeDamage(damage, attacker, doer, point) - LiveScriptActor.OnTakeDamage(self, damage, attacker, doer, point) - local team = self:GetTeam() if team.TriggerAlert then team:TriggerAlert(self:GetDamagedAlertId(), self) diff --git a/ns2/lua/TargetCache.lua b/ns2/lua/TargetCache.lua index 9966d84..4c09208 100644 --- a/ns2/lua/TargetCache.lua +++ b/ns2/lua/TargetCache.lua @@ -90,7 +90,7 @@ end // Selects targets based on if they can hurt us // function HarmfulPrioritizer() - return function(target) return target:GetCanDoDamage() end + return function(target) return target:GetCanGiveDamage() end end // diff --git a/ns2/lua/TechData.lua b/ns2/lua/TechData.lua index df7ae16..c5985b1 100644 --- a/ns2/lua/TechData.lua +++ b/ns2/lua/TechData.lua @@ -325,6 +325,7 @@ function BuildTechData() { [kTechDataId] = kTechId.Cyst, [kTechDataMapName] = Cyst.kMapName, [kTechDataDisplayName] = "CYST", [kTechDataTooltipInfo] = "CYST_TOOLTIP", [kTechDataCostKey] = kCystCost, [kTechDataBuildTime] = kCystBuildTime, [kTechDataMaxHealth] = kCystHealth, [kTechDataMaxArmor] = kCystArmor, [kTechDataModel] = Cyst.kModelName, [kVisualRange] = Cyst.kInfestRadius, [kTechDataRequiresInfestation] = false, [kTechDataPointValue] = kCystPointValue, [kTechDataGrows] = false, [kTechDataBuildRequiresMethod]=GetCystParentAvailable, [kTechDataGhostGuidesMethod]=GetCystGhostGuides, /* [kStructureBuildNearClass] = {"Hive", "Cyst"}, [kStructureAttachId] = {kTechId.Hive, kTechId.Cyst}, [kStructureAttachRange] = kHiveCystParentRange */} , { [kTechDataId] = kTechId.MiniCyst, [kTechDataMapName] = MiniCyst.kMapName, [kTechDataDisplayName] = "MINI_CYST", [kTechDataCostKey] = kMiniCystCost, [kTechDataBuildTime] = kMiniCystBuildTime, [kTechDataMaxHealth] = kMiniCystHealth, [kTechDataMaxArmor] = kMiniCystArmor, [kTechDataModel] = MiniCyst.kModelName, [kVisualRange] = MiniCyst.kInfestRadius, [kTechDataRequiresInfestation] = false, [kTechDataPointValue] = kMiniCystPointValue, [kTechDataGrows] = false}, + // Alien structure abilities and their energy costs { [kTechDataId] = kTechId.CragHeal, [kTechDataDisplayName] = "HEAL", [kTechDataHotkey] = Move.H, [kTechDataCostKey] = kCragHealCost, [kTechDataTooltipInfo] = "CRAG_HEAL_TOOLTIP"}, { [kTechDataId] = kTechId.CragUmbra, [kTechDataDisplayName] = "UMBRA", [kTechDataHotkey] = Move.M, [kTechDataCostKey] = kCragUmbraCost, [kVisualRange] = Crag.kHealRadius, [kTechDataTooltipInfo] = "CRAG_UMBRA_TOOLTIP"}, @@ -339,10 +340,10 @@ function BuildTechData() { [kTechDataId] = kTechId.ShadeDisorient, [kTechDataDisplayName] = "DISORIENT", [kTechDataHotkey] = Move.D, [kTechDataTooltipInfo] = "SHADE_DISORIENT_TOOLTIP"}, { [kTechDataId] = kTechId.ShadeCloak, [kTechDataDisplayName] = "CLOAK", [kTechDataHotkey] = Move.C, [kTechDataCostKey] = kShadeCloakCost }, - { [kTechDataId] = kTechId.ShadePhantasmMenu, [kTechDataDisplayName] = "PHANTASM", [kTechDataHotkey] = Move.P }, - //{ [kTechDataId] = kTechId.ShadePhantasmFade, [kTechDataDisplayName] = "PHANTASM FADE", [kTechDataModel] = Fade.kModelName, [kTechDataMapName] = FadePhantasm.kMapName, [kTechDataHotkey] = Move.F, [kTechDataCostKey] = kShadePhantasmFadeEnergyCost }, - // { [kTechDataId] = kTechId.ShadePhantasmOnos, [kTechDataDisplayName] = "PHANTASM ONOS", [kTechDataModel] = Onos.kModelName, [kTechDataMapName] = OnosPhantasm.kMapName, [kTechDataHotkey] = Move.O, [kTechDataCostKey] = kShadePhantasmOnosEnergyCost }, - //{ [kTechDataId] = kTechId.ShadePhantasmHive, [kTechDataDisplayName] = "PHANTASM HIVE", [kTechDataModel] = Hive.kModelName, [kTechDataMapName] = HivePhantasm.kMapName, [kTechDataHotkey] = Move.H, [kTechDataCostKey] = kShadePhantasmHiveEnergyCost, [kStructureAttachClass] = "TechPoint", }, + { [kTechDataId] = kTechId.ShadePhantomMenu, [kTechDataDisplayName] = "PHANTOM", [kTechDataHotkey] = Move.P }, + { [kTechDataId] = kTechId.ShadePhantomFade, [kTechDataDisplayName] = "PHANTOM_FADE", [kTechDataModel] = Fade.kModelName, [kTechDataMapName] = PhantomEffigy.kMapName, [kTechDataHotkey] = Move.F, [kTechDataCostKey] = kShadePhantomFadeEnergyCost }, + { [kTechDataId] = kTechId.ShadePhantomOnos, [kTechDataDisplayName] = "PHANTOM_ONOS", [kTechDataModel] = Onos.kModelName, [kTechDataMapName] = PhantomEffigy.kMapName, [kTechDataHotkey] = Move.O, [kTechDataCostKey] = kShadePhantomOnosEnergyCost }, + //{ [kTechDataId] = kTechId.ShadePhantasmHive, [kTechDataDisplayName] = "PHANTOM_HIVE", [kTechDataModel] = Hive.kModelName, [kTechDataMapName] = HivePhantasm.kMapName, [kTechDataHotkey] = Move.H, [kTechDataCostKey] = kShadePhantasmHiveEnergyCost, [kStructureAttachClass] = "TechPoint", }, { [kTechDataId] = kTechId.WhipUnroot, [kTechDataDisplayName] = "UNROOT_WHIP", [kTechDataTooltipInfo] = "UNROOT_WHIP_TOOLTIP"}, { [kTechDataId] = kTechId.WhipRoot, [kTechDataDisplayName] = "ROOT_WHIP", [kTechDataTooltipInfo] = "ROOT_WHIP_TOOLTIP"}, diff --git a/ns2/lua/TechPoint.lua b/ns2/lua/TechPoint.lua index 210c941..2c4fe77 100644 --- a/ns2/lua/TechPoint.lua +++ b/ns2/lua/TechPoint.lua @@ -48,7 +48,7 @@ function TechPoint:OnInit() self:SetPhysicsGroup(PhysicsGroup.AttachClassGroup) // Make the nozzle kinematic so that the player will collide with it. - self:SetPhysicsType(Actor.PhysicsType.Kinematic) + self:SetPhysicsType(PhysicsType.Kinematic) self:SetTechId(kTechId.TechPoint) diff --git a/ns2/lua/TechPoint_Server.lua b/ns2/lua/TechPoint_Server.lua index 52ba714..0bbb10d 100644 --- a/ns2/lua/TechPoint_Server.lua +++ b/ns2/lua/TechPoint_Server.lua @@ -6,7 +6,7 @@ // // ========= For more information, visit us at http://www.unknownworlds.com ===================== -function TechPoint:GetCanTakeDamage() +function TechPoint:GetCanTakeDamageOverride() return false end diff --git a/ns2/lua/TechTreeButtons.lua b/ns2/lua/TechTreeButtons.lua index 1c8d048..de10a73 100644 --- a/ns2/lua/TechTreeButtons.lua +++ b/ns2/lua/TechTreeButtons.lua @@ -290,11 +290,10 @@ function InitTechTreeMaterialOffsets() kAlienTechIdToMaterialOffset[kTechId.MatureShade] = 66 kAlienTechIdToMaterialOffset[kTechId.ShadeCloak] = 67 kAlienTechIdToMaterialOffset[kTechId.ShadeDisorient] = 68 - kAlienTechIdToMaterialOffset[kTechId.ShadePhantasmMenu] = 69 - kAlienTechIdToMaterialOffset[kTechId.ShadePhantasmFade] = 69 - kAlienTechIdToMaterialOffset[kTechId.ShadePhantasmOnos] = 69 - kAlienTechIdToMaterialOffset[kTechId.ShadePhantasmHive] = 69 - kAlienTechIdToMaterialOffset[kTechId.PhantasmTech] = 70 + kAlienTechIdToMaterialOffset[kTechId.ShadePhantomMenu] = 69 + kAlienTechIdToMaterialOffset[kTechId.ShadePhantomFade] = 69 + kAlienTechIdToMaterialOffset[kTechId.ShadePhantomOnos] = 69 + kAlienTechIdToMaterialOffset[kTechId.PhantomTech] = 70 kAlienTechIdToMaterialOffset[kTechId.CamouflageTech] = 71 kAlienTechIdToMaterialOffset[kTechId.Camouflage] = 71 diff --git a/ns2/lua/TechTreeConstants.lua b/ns2/lua/TechTreeConstants.lua index a6f76be..9db1024 100644 --- a/ns2/lua/TechTreeConstants.lua +++ b/ns2/lua/TechTreeConstants.lua @@ -108,13 +108,13 @@ kTechId = enum({ 'Crag', 'UpgradeCrag', 'MatureCrag', 'CragHeal', 'CragUmbra', 'CragBabblers', 'Whip', 'UpgradeWhip', 'MatureWhip', 'WhipAcidStrike', 'WhipFury', 'WhipBombard', 'Shift', 'UpgradeShift', 'MatureShift', 'ShiftRecall', 'ShiftEcho', 'ShiftEnergize', - 'Shade', 'UpgradeShade', 'MatureShade', 'ShadeDisorient', 'ShadeCloak', 'ShadePhantasmMenu', 'ShadePhantasmFade', 'ShadePhantasmOnos', 'ShadePhantasmHive', + 'Shade', 'UpgradeShade', 'MatureShade', 'ShadeDisorient', 'ShadeCloak', 'ShadePhantomMenu', 'ShadePhantomFade', 'ShadePhantomOnos', // Whip movement 'WhipRoot', 'WhipUnroot', // Alien abilities and upgrades - BabblerTech - 'BabblerTech', 'EchoTech', 'PhantasmTech', + 'BabblerTech', 'EchoTech', 'PhantomTech', 'PhantomEffigy', 'Melee1Tech', 'Melee2Tech', 'Melee3Tech', 'AlienArmor1Tech', 'AlienArmor2Tech', 'AlienArmor3Tech', 'AdrenalineTech', 'BileBombTech', 'LeapTech', 'BacteriaTech', 'FeintTech', 'SapTech', 'StompTech', 'BoneShieldTech', 'CarapaceTech', 'PiercingTech', 'FrenzyTech', 'SwarmTech', 'RegenerationTech', 'CamouflageTech', diff --git a/ns2/lua/TimedCallbackMixin.lua b/ns2/lua/TimedCallbackMixin.lua index 3f35e29..1670307 100644 --- a/ns2/lua/TimedCallbackMixin.lua +++ b/ns2/lua/TimedCallbackMixin.lua @@ -24,7 +24,7 @@ function TimedCallbackMixin:AddTimedCallback(addFunction, callRate) end AddFunctionContract(TimedCallbackMixin.AddTimedCallback, { Arguments = { "Entity", "function", "number" }, Returns = { } }) -function TimedCallbackMixin:UpdateTimedCallbacks(deltaTime) +function TimedCallbackMixin:OnUpdate(deltaTime) if self.timedCallbacks then @@ -58,4 +58,4 @@ function TimedCallbackMixin:UpdateTimedCallbacks(deltaTime) end end -AddFunctionContract(TimedCallbackMixin.UpdateTimedCallbacks, { Arguments = { "Entity", "number" }, Returns = { } }) \ No newline at end of file +AddFunctionContract(TimedCallbackMixin.OnUpdate, { Arguments = { "Entity", "number" }, Returns = { } }) \ No newline at end of file diff --git a/ns2/lua/UpgradableMixin.lua b/ns2/lua/UpgradableMixin.lua new file mode 100644 index 0000000..c13d161 --- /dev/null +++ b/ns2/lua/UpgradableMixin.lua @@ -0,0 +1,183 @@ +// ======= Copyright © 2003-2011, Unknown Worlds Entertainment, Inc. All rights reserved. ======= +// +// lua\UpgradableMixin.lua +// +// Created by: Brian Cronin (brianc@unknownworlds.com) +// +// ========= For more information, visit us at http://www.unknownworlds.com ===================== + +Script.Load("lua/FunctionContracts.lua") + +/** + * UpgradableMixin handles two forms of upgrades. There are the upgrades that it owns (upgrade1 - upgrade4). + * It can also handle upgrading the entire entity to another tech Id independent of the upgrades it owns. + */ +UpgradableMixin = { } +UpgradableMixin.type = "Upgradable" + +UpgradableMixin.expectedCallbacks = +{ + SetTechId = "Sets the current tech Id of this entity.", + GetTechId = "Returns the current tech Id of this entity." +} + +UpgradableMixin.optionalCallbacks = +{ + OnPreUpgradeToTechId = "Called right before upgrading to a new tech Id.", + OnGiveUpgrade = "Called to notify that an upgrade was given with the tech Id as the single parameter." +} + +function UpgradableMixin.__prepareclass(toClass) + + ASSERT(toClass.networkVars ~= nil, "UpgradableMixin expects the class to have network fields") + + local addNetworkFields = + { + upgrade1 = "enum kTechId", + upgrade2 = "enum kTechId", + upgrade3 = "enum kTechId", + upgrade4 = "enum kTechId", + } + + for k, v in pairs(addNetworkFields) do + toClass.networkVars[k] = v + end + +end + +function UpgradableMixin:__initmixin() + + self.upgrade1 = kTechId.None + self.upgrade2 = kTechId.None + self.upgrade3 = kTechId.None + self.upgrade4 = kTechId.None + +end + +function UpgradableMixin:UpgradeToTechId(newTechId) + + if self:GetTechId() ~= newTechId then + + if self.OnPreUpgradeToTechId then + self:OnPreUpgradeToTechId(newTechId) + end + + local healthScalar = 0 + local armorScalar = 0 + local isAlive = HasMixin(self, "Live") + if isAlive then + // Preserve health and armor scalars but potentially change maxHealth and maxArmor. + healthScalar = self:GetHealthScalar() + armorScalar = self:GetArmorScalar() + end + + self:SetTechId(newTechId) + + if isAlive then + + self:SetMaxHealth(LookupTechData(newTechId, kTechDataMaxHealth, self:GetMaxHealth())) + self:SetMaxArmor(LookupTechData(newTechId, kTechDataMaxArmor, self:GetMaxArmor())) + + self:SetHealth(healthScalar * self:GetMaxHealth()) + self:SetArmor(armorScalar * self:GetMaxArmor()) + + end + + return true + + end + + return false + +end +AddFunctionContract(UpgradableMixin.UpgradeToTechId, { Arguments = { "Entity", "number" }, Returns = { "boolean" } }) + +function UpgradableMixin:GetHasUpgrade(techId) + return techId ~= kTechId.None and (techId == self.upgrade1 or techId == self.upgrade2 or techId == self.upgrade3 or techId == self.upgrade4) +end +AddFunctionContract(UpgradableMixin.GetHasUpgrade, { Arguments = { "Entity", "number" }, Returns = { "boolean" } }) + +function UpgradableMixin:GetUpgrades() + + local upgrades = { } + + if self.upgrade1 ~= kTechId.None then + table.insert(upgrades, self.upgrade1) + end + if self.upgrade2 ~= kTechId.None then + table.insert(upgrades, self.upgrade2) + end + if self.upgrade3 ~= kTechId.None then + table.insert(upgrades, self.upgrade3) + end + if self.upgrade4 ~= kTechId.None then + table.insert(upgrades, self.upgrade4) + end + + return upgrades + +end +AddFunctionContract(UpgradableMixin.GetUpgrades, { Arguments = { "Entity" }, Returns = { "table" } }) + +function UpgradableMixin:GiveUpgrade(techId) + + local upgradeGiven = false + + if not self:GetHasUpgrade(techId) then + + if self.upgrade1 == kTechId.None then + + self.upgrade1 = techId + upgradeGiven = true + + elseif self.upgrade2 == kTechId.None then + + self.upgrade2 = techId + upgradeGiven = true + + elseif self.upgrade3 == kTechId.None then + + self.upgrade3 = techId + upgradeGiven = true + + elseif self.upgrade4 == kTechId.None then + + self.upgrade4 = techId + upgradeGiven = true + + end + + assert(upgradeGiven, "Entity already has the max of four upgrades.") + + else + error("Entity already has upgrade.") + end + + if upgradeGiven and self.OnGiveUpgrade then + self:OnGiveUpgrade(techId) + end + + return upgradeGiven + +end +AddFunctionContract(UpgradableMixin.GiveUpgrade, { Arguments = { "Entity", "number" }, Returns = { "boolean" } }) + +function UpgradableMixin:Reset() + self:_ClearUpgrades() +end +AddFunctionContract(UpgradableMixin.Reset, { Arguments = { "Entity" }, Returns = { } }) + +function UpgradableMixin:OnKill() + self:_ClearUpgrades() +end +AddFunctionContract(UpgradableMixin.OnKill, { Arguments = { "Entity" }, Returns = { } }) + +function UpgradableMixin:_ClearUpgrades() + + self.upgrade1 = kTechId.None + self.upgrade2 = kTechId.None + self.upgrade3 = kTechId.None + self.upgrade4 = kTechId.None + +end +AddFunctionContract(UpgradableMixin._ClearUpgrades, { Arguments = { "Entity" }, Returns = { } }) \ No newline at end of file diff --git a/ns2/lua/Weapons/Alien/BileBomb.lua b/ns2/lua/Weapons/Alien/BileBomb.lua index 50fa469..1ee4093 100644 --- a/ns2/lua/Weapons/Alien/BileBomb.lua +++ b/ns2/lua/Weapons/Alien/BileBomb.lua @@ -53,7 +53,7 @@ function BileBomb:FireBombProjectile(player) local bomb = CreateEntity(Bomb.kMapName, startPoint, player:GetTeamNumber()) SetAnglesFromVector(bomb, viewCoords.zAxis) - bomb:SetPhysicsType(Actor.PhysicsType.Kinematic) + bomb:SetPhysicsType(PhysicsType.Kinematic) local startVelocity = viewCoords.zAxis * BileBomb.kBombSpeed bomb:SetVelocity(startVelocity) diff --git a/ns2/lua/Weapons/Alien/SpitSpray.lua b/ns2/lua/Weapons/Alien/SpitSpray.lua index 1bb0d70..f955682 100644 --- a/ns2/lua/Weapons/Alien/SpitSpray.lua +++ b/ns2/lua/Weapons/Alien/SpitSpray.lua @@ -80,7 +80,7 @@ function SpitSpray:CreateSpitProjectile(player) local spit = CreateEntity(Spit.kMapName, startPoint, player:GetTeamNumber()) SetAnglesFromVector(spit, viewCoords.zAxis) - spit:SetPhysicsType(Actor.PhysicsType.Kinematic) + spit:SetPhysicsType(PhysicsType.Kinematic) local startVelocity = viewCoords.zAxis * SpitSpray.kSpitSpeed spit:SetVelocity(startVelocity) diff --git a/ns2/lua/Weapons/Projectile_Server.lua b/ns2/lua/Weapons/Projectile_Server.lua index 00a7c1b..da8592b 100644 --- a/ns2/lua/Weapons/Projectile_Server.lua +++ b/ns2/lua/Weapons/Projectile_Server.lua @@ -46,9 +46,9 @@ function Projectile:SetPhysicsType(physicsType) if (self.physicsBody) then - if (self.physicsType == Actor.PhysicsType.Kinematic) then + if (self.physicsType == PhysicsType.Kinematic) then self.physicsBody:SetSimulationEnabled(false) - elseif (self.physicsType == Actor.PhysicsType.Dynamic) then + elseif (self.physicsType == PhysicsType.Dynamic) then self.physicsBody:SetSimulationEnabled(true) end diff --git a/ns2/lua/Weapons/Weapon_Client.lua b/ns2/lua/Weapons/Weapon_Client.lua index fad4775..1b5016c 100644 --- a/ns2/lua/Weapons/Weapon_Client.lua +++ b/ns2/lua/Weapons/Weapon_Client.lua @@ -11,12 +11,12 @@ end function Weapon:UpdateDropped() - if self:GetPhysicsType() == Actor.PhysicsType.DynamicServer and not self.dropped then + if self:GetPhysicsType() == PhysicsType.DynamicServer and not self.dropped then self:Dropped(nil) self.dropped = true - elseif self:GetPhysicsType() == Actor.PhysicsType.None then + elseif self:GetPhysicsType() == PhysicsType.None then self.dropped = false end diff --git a/ns2/lua/Weapons/Weapon_Server.lua b/ns2/lua/Weapons/Weapon_Server.lua index af0fd32..ce878d5 100644 --- a/ns2/lua/Weapons/Weapon_Server.lua +++ b/ns2/lua/Weapons/Weapon_Server.lua @@ -38,7 +38,7 @@ function Weapon:SetWeaponWorldState(state) if state then - self:SetPhysicsType(Actor.PhysicsType.DynamicServer) + self:SetPhysicsType(PhysicsType.DynamicServer) // So it doesn't affect player movement and so collide callback is called self:SetPhysicsGroup(PhysicsGroup.DroppedWeaponGroup) @@ -56,7 +56,7 @@ function Weapon:SetWeaponWorldState(state) else - self:SetPhysicsType(Actor.PhysicsType.None) + self:SetPhysicsType(PhysicsType.None) self:SetPhysicsGroup(PhysicsGroup.WeaponGroup) self:UpdatePhysicsModel() diff --git a/ns2/lua/Whip.lua b/ns2/lua/Whip.lua index 9f13ed0..4a7520e 100644 --- a/ns2/lua/Whip.lua +++ b/ns2/lua/Whip.lua @@ -12,6 +12,7 @@ Script.Load("lua/Structure.lua") Script.Load("lua/DoorMixin.lua") Script.Load("lua/InfestationMixin.lua") +Script.Load("lua/RagdollMixin.lua") class 'Whip' (Structure) @@ -54,6 +55,8 @@ function Whip:OnCreate() Structure.OnCreate(self) + InitMixin(self, RagdollMixin) + self.attackYaw = 0 self.mode = Whip.kMode.Rooted @@ -154,7 +157,7 @@ function Whip:UpdatePoseParameters(deltaTime) end -function Whip:GetCanDoDamage() +function Whip:GetCanGiveDamageOverride() return true end diff --git a/ns2/lua/Whip_Server.lua b/ns2/lua/Whip_Server.lua index baff9d0..6ab9469 100644 --- a/ns2/lua/Whip_Server.lua +++ b/ns2/lua/Whip_Server.lua @@ -371,7 +371,7 @@ function Whip:OnResearchComplete(structure, researchId) // Transform into mature whip if structure and (structure:GetId() == self:GetId()) and (researchId == kTechId.UpgradeWhip) then - success = self:Upgrade(kTechId.MatureWhip) + success = self:UpgradeToTechId(kTechId.MatureWhip) end diff --git a/ns2/lua/ns2.deproj b/ns2/lua/ns2.deproj index 68869d2..7675f78 100644 --- a/ns2/lua/ns2.deproj +++ b/ns2/lua/ns2.deproj @@ -555,12 +555,6 @@ NS2Utility.lua - - Phantasm.lua - - - OnosPhantasm.lua - Weapons\Alien\SwipeBlink.lua @@ -1002,4 +996,25 @@ PhantomMixin.lua + + SelectableMixin.lua + + + PhantomEffigy.lua + + + AttackOrderMixin.lua + + + RagdollMixin.lua + + + UpgradableMixin.lua + + + PointGiverMixin.lua + + + ScoringMixin.lua +