This hard coded state based logic and animation is starting to seem like the wrong idea.
How exactly should you handle the logic of weapons?
For example, going from Idle to Moving, Firing, Reloading, Zooming (iron sights), etc.
Right now I use a large combination of weapon:SetState, weapon:SetAnimation, Get forms of those, etc. Then the weapon's update loop just plays the current animation (which defaults to Idle if nothing is set).
This is the DRAFT DRAFT! pre-pre-pre-alpha version of wepaon.lua and the first weapon inheriting it gold_xix. Just note that the functionality you see in the videos and such was implemented using if statements and hackish stuff in the render loop of the fps script. I am porting over to the actual class-based weapon setup, and so far only have states and animation working, with the functionality already more powerful, as you can see from the code:
weapon.lua
dofile("scripts/utilities.lua")
-- Defaults
-- States
WEAPON_IDLE = 0
WEAPON_TAKE_OUT = 1
WEAPON_PUT_AWAY = 2
WEAPON_FIRE = 3
WEAPON_RELOAD = 4
WEAPON_ZOOM_IDLE = 5
WEAPON_ZOOM_FIRE = 6
WEAPON_TO_ZOOM = 7
WEAPON_TO_NORMAL = 8
WEAPON_MOVING = 9
WEAPON_ZOOM_MOVING = 10
-- Globals
WEAPON_SCALE = 0.6 -- Global scale shared by all weapons. Gun specific scaling is multiplied by this factor. It allows guns of different scales to appear similarly sized.
-- Variables
firstweapon=nil
lastweapon=nil
-- Weapons Table
weapons={}
function CreateWeapon( weapon_table )
local weapon = {}
-- Sub Tables
weapon.Animations = { } -- Table containing all animations
weapon.Sounds = { } -- Table containing all sounds
weapon.Effects = { } -- Table containing all effects (emitter, muzzle flash, brass, etc.)
weapon.Attachments = { } -- Table containing attachments (ironsights, scopes, etc.)
-- Properties
weapon.AnimationBlending = 0.5
weapon.SwayScale = 0 -- X Axis Movement
weapon.BobScale = 0 -- Y Axis Movement
weapon.Zoomed = false
weapon.Moving = false
weapon.AutoReload = false -- Gun will automatically go to reload state if you try to fire with an empty clip
-- Sound
weapon.Volume = 1.0
weapon.Pitch = 1.0
-- Animation
weapon.AnimationStartTime = AppTime()
weapon.CurrentAnimation = ""
weapon.CurrentAnimationFrame = 0
weapon.Animating = false
-- Primary
weapon.Primary = { }
weapon.Primary.ClipSize = 1 -- Number of rounds in the clip
weapon.Primary.Clip = 1 -- Current number of rounds in the clip
weapon.Primary.Ammo = 1 -- Current total ammo (not including current clip)
weapon.Primary.Damage = 0 -- Damage done per round
weapon.Primary.ShotCount = 0 -- Bullets ejected per round
weapon.Primary.Cone = 0 -- Spread cone (X,Y)
weapon.Primary.Delay = 0 -- Time between current shot and next shot
weapon.Primary.NextFire = AppTime() -- Time next shot can be fired
-- Secondary
weapon.Secondary = { }
weapon.Secondary.ClipSize = 0
weapon.Secondary.Clip = 0
weapon.Secondary.Ammo = 0
weapon.Secondary.Damage = 0
weapon.Secondary.ShotCount = 0
weapon.Secondary.Cone = 0
weapon.Secondary.Delay = 0
weapon.Secondary.NextFire = AppTime()
-- Finite State Logic
weapon.State = 0 -- Weapons's current logic state
-- World/View Models
weapon.WorldModel = nil -- World View Model
weapon.ViewModel = LoadMesh(weapon_table.ViewModel) -- First Person View Model
weapon.ViewModel:SetParent(fw.main.camera,0)
local vwep_offset, vwep_scale = weapon_table.Offset, weapon_table.Scale
weapon.ViewModel:SetPosition(Vec3(-0.01*WEAPON_SCALE,-0.005*WEAPON_SCALE,0.05*WEAPON_SCALE),0)
weapon.ViewModel:SetScale(Vec3(0.04*WEAPON_SCALE,0.04*WEAPON_SCALE,0.04*WEAPON_SCALE))
weapon.ViewModel:SetShadowMode(0,1)
AppLog("We loaded and positioned the view model...")
-- Future AABB Random Culling Fix...
--vwep.localaabb.x0=-3
--vwep.localaabb.x1=3
--vwep.localaabb.y0=-3
--vwep.localaabb.y1=3
--vwep.localaabb.z0=-3
--vwep.localaabb.z1=3
--vwep:UpdateAABB()
-- Positioning / Offsets
weapon.Offset = weapon.ViewModel.position:Copy()
local gundisplayposition = weapon.ViewModel:GetPosition()
local positionentity = FindChild(weapon.ViewModel,"FIRESPOT") or weapon.ViewModel -- Entity used for firespot location
local displayposition = EntityPosition(positionentity)
-- Muzzleflash
weapon.Effects.MuzzleFlash = CreatePointLight(3)
weapon.Effects.MuzzleFlash:SetParent( weapon.ViewModel )
local muzzleflash_color = weapon_table.muzzleflash_color or Vec4(1,0.6,0.0,1.0)
weapon.Effects.MuzzleFlash:SetColor(muzzleflash_color)
weapon.Effects.MuzzleFlash:SetPosition( displayposition )
weapon.Effects.MuzzleFlash:SetShadowMode(0)
weapon.Effects.MuzzleFlash:Hide()
function weapon:Update()
--[[-- Idle State
if (self:IsIdle()) then
if (self:IsZoomed()) then
self:LoopAnimation("WEAPON_ZOOM_IDLE")
else
if (self.IsMoving()) then
self:LoopAnimation("WEAPON_MOVE")
else
self:LoopAnimation("WEAPON_IDLE")
end
end
-- Fire State
elseif (self:IsFiring()) then
-- Zoom Fire State
if (self:IsZoomed()) then
self:SetAnimating(true)
self:PlayAnimation("WEAPON_ZOOM_FIRE")
-- Normal Fire State
else
self:SetAnimating(true)
self:PlayAnimation("WEAPON_FIRE")
end
if (not self:IsAnimating()) then
self:SetState(WEAPON_IDLE)
end
-- No State? (Idle)
else
self:LoopAnimation("WEAPON_IDLE")
end]]--
-- State Correction
if (self:IsFiring() and not self.Animating) then
self:SetState(WEAPON_IDLE)
self:SetAnimation("WEAPON_IDLE")
end
self:PlayAnimation(self.CurrentAnimation)
self.ViewModel:SetPosition(self.Offset:Copy())
self:UpdateSounds()
end
function weapon:Think()
-- This can be used for AI, etc. Implement it if you want
end
-- Animation
function weapon:AddAnimation(name,startframe,endframe,speedmodifier,oneshot)
self.Animations[name] = {Name = name,
StartFrame = startframe,
EndFrame = endframe,
Length = (endframe-startframe),
Modifier = speedmodifier,
OneShot = oneshot or false,
Running = false}
end
--[[function weapon:LoopAnimation(animation)
local time = ((AppTime() - self.AnimationStartTime) / 100.0)
local currentanim = self.Animations[animation]
self.CurrentAnimationFrame = ((time * currentanim.Modifier * HOST_TIMESCALE) % currentanim.Length) + currentanim.StartFrame
if (self.CurrentAnimationFrame < currentanim.StartFrame) then self.CurrentAnimationFrame = currentanim.StartFrame end
if (self.CurrentAnimationFrame > currentanim.EndFrame) then self.CurrentAnimationFrame = currentanim.EndFrame end
self.ViewModel:Animate(self.CurrentAnimationFrame, self.AnimationBlending, 0, 1)
end]]
function weapon:SetAnimation(animation)
self.AnimationStartTime = AppTime()
self.CurrentAnimation = animation
self.Animating = true
end
function weapon:PlayAnimation(animation)
if (not self.Animating) then return end
if (animation == nil or animation == "") then return end
local time = (AppTime() - self.AnimationStartTime) / 100.0
local currentanim = self.Animations[animation]
currentanim.Running = true
if (not self.Animating) then
currentanim.Running = false
end
self.CurrentAnimationFrame = (time * currentanim.Modifier * HOST_TIMESCALE) % currentanim.Length + currentanim.StartFrame
if (self.CurrentAnimationFrame < currentanim.StartFrame) then self.CurrentAnimationFrame = currentanim.StartFrame end
if (self.CurrentAnimationFrame > currentanim.EndFrame) then self.CurrentAnimationFrame = currentanim.EndFrame end
self.ViewModel:Animate(self.CurrentAnimationFrame, self.AnimationBlending, 0, 1)
if ((self.CurrentAnimationFrame > currentanim.EndFrame-0.5 and currentanim.OneShot) or currentanim.Running == false) then
self.Animating = false
currentanim.Running = false
end
end
-- Sound
function weapon:AddSound(name,soundfile,frame)
local sound = LoadSound(soundfile)
local sound_table = {Name = name,
Frame = frame,
Sound = sound,
Source = CreateSource(sound)}
local Weapon = self
function sound_table:Update()
self.Source:SetVolume(Weapon.Volume)
self.Source:SetPitch(Weapon.Pitch)
end
table.insert(self.Sounds,sound_table)
end
function weapon:UpdateSounds()
for k,v in pairs(self.Sounds) do
if (v.Frame == self.CurrentFrame) then
v.Source:Play()
v:Update()
end
end
end
-- States
function weapon:GetState()
return self.State
end
function weapon:SetState(state)
self.State = state
end
function weapon:IsIdle()
return self.State == WEAPON_IDLE or self.State == WEAPON_ZOOM_IDLE
end
function weapon:IsFiring()
return self.State == WEAPON_FIRE
end
function weapon:IsReloading()
return self.State == WEAPON_RELOAD
end
function weapon:IsZoomed()
return self.State == WEAPON_ZOOM_IDLE or self.State == WEAPON_ZOOM_FIRE or self.Zoomed
end
function weapon:IsEmpty()
return self:IsPrimaryClipEmpty()
end
function weapon:IsAnimating()
return self.Animating
end
function weapon:IsMoving()
return ((KeyDown(KEY_W) - KeyDown(KEY_S)) ~= 0) or ((KeyDown(KEY_A) - KeyDown(KEY_D)) ~= 0)
end
function weapon:SetAnimating(anim)
self.Animating = anim
end
-- Weapon Functionality
-- Reload
function weapon:Reload()
if (not self:IsIdle()) then return end
self:SetState(WEAPON_RELOAD)
end
-- Primary Fire Mode
function weapon:GetPrimaryClip()
return self.Primary.Clip
end
function weapon:GetPrimaryClipSize()
return self.Primary.ClipSize
end
function weapon:GetPrimaryAmmo()
return self.Primary.Ammo
end
function weapon:IsPrimaryClipEmpty()
return self.Primary.Clip <= 0
end
function weapon:CanPrimaryFire()
return (AppTime() >= self:GetNextPrimaryFire()) and (self:IsIdle() or self.IsMoving()) and (not self:IsPrimaryClipEmpty())
end
function weapon:SetNextPrimaryFire(time)
self.Primary.NextFire = time
end
function weapon:GetNextPrimaryFire()
return self.Primary.NextFire
end
function weapon:TakePrimaryAmmo(num)
self.Primary.Clip = self.Primary.Clip - num
end
function weapon:PrimaryFire()
if (not self:CanPrimaryFire()) then return end -- We can not perform a primary fire
self:SetNextPrimaryFire(AppTime() + self.Primary.Delay)
self:SetNextSecondaryFire(AppTime() + self.Secondary.Delay)
self:SetState(WEAPON_FIRE)
self:SetAnimation("WEAPON_FIRE")
end
-- Secondary Fire Mode
function weapon:GetSecondaryClip()
return self.Secondary.Clip
end
function weapon:GetSecondaryClipSize()
return self.Secondary.ClipSize
end
function weapon:GetSecondaryAmmo()
return self.Secondary.Ammo
end
function weapon:IsSecondaryClipEmpty()
return self.Secondary.Clip <= 0
end
function weapon:CanSecondaryFire()
return (AppTime() >= self:GetNextSecondaryFire()) and (self:IsIdle()) and (not self:IsSecondaryClipEmpty())
end
function weapon:SetNextSecondaryFire(time)
self.Secondary.NextFire = time
end
function weapon:GetNextSecondaryFire()
return self.Secondary.NextFire
end
function weapon:TakeSecondaryAmmo(num)
self.Secondary.Clip = self.Secondary.Clip - num
end
function weapon:SecondaryFire()
if (not self:CanSecondaryFire()) then return end -- We can not perform a secondary fire
self:SetNextPrimaryFire(AppTime() + self.Primary.Delay)
self:SetNextSecondaryFire(AppTime() + self.Secondary.Delay)
end
-- Weapons Table Hierarchy
if lastweapon==nil then
firstweapon=weapon
lastweapon=weapon
else
lastweapon.next=weapon
weapon.prev=lastweapon
end
weapons[weapon]=weapon
function weapon:Free()
weapons[self]=nil
end
-- All weapons created from CreateWeapon inherit the base functionality implemented in this file
weapon.Base = weapon
return weapon
end
gold xix:
dofile("scripts/classes/weapon.lua")
GoldXIX = { }
GoldXIX_WeaponTable = {
ViewModel = "abstract::HUD.gmf",
Offset = Vec3(-0.01,-0.005,0.05),
Scale = Vec3(0.04,0.04,0.04)
}
GoldXIX = CreateWeapon(GoldXIX_WeaponTable)
-- Animations
GoldXIX:AddAnimation("WEAPON_IDLE", 277.0, 302.0, 1.0)
GoldXIX:AddAnimation("WEAPON_ZOOM_IDLE", 212.0, 238.0, 1.0)
GoldXIX:AddAnimation("WEAPON_MOVE", 304.0, 326.0, 2.0)
GoldXIX:AddAnimation("WEAPON_FIRE", 62.0, 74.0, 3.0, true)
GoldXIX:AddAnimation("WEAPON_ZOOM_FIRE", 262.0, 274.0, 3.0, true)
GoldXIX:SetAnimation("WEAPON_IDLE")
GoldXIX:SetState(WEAPON_IDLE)