Intial Commit

This commit is contained in:
2026-10-27 17:27:44 -04:00
parent 1fa6e14bde
commit 5743c34a21
49 changed files with 1454 additions and 0 deletions
@@ -0,0 +1,123 @@
require "KLT_LAV"
require "TimedActions/KLT_ReloadLAV"
require "Vehicles/Papa_Chad_Tank_Cannon"
local F = KLTLAV
local characterVariables = {
"M2_idle", "M2_360Anim", "M2_045Anim", "M2_090Anim", "M2_135Anim",
"M2_180Anim", "M2_225Anim", "M2_270Anim", "M2_315Anim",
}
function F.installClient()
if F.clientInstalled then return end
if not weaponConfig or not weaponConfig.M41_76mm
or not chooseWeaponPC or not tankAimSystemPC then
print("[KI5LAVTurret] MTK turret API unavailable; controls not installed.")
return
end
local profile = {}
for key, value in pairs(weaponConfig.M41_76mm) do profile[key] = value end
profile.requiredPart = F.weaponPart
profile.requiredSeat = "SeatGunner" -- MTK's label for index 1, not a renamed seat.
profile.requiredItem = F.ammoType
profile.requiredItemHE = F.heAmmoType
profile.coax = false
profile.sound = "HRSys_LAV300_90mm_Fire"
profile.soundEnd = "HRSys_LAV300_90mm_End"
profile.rotationTime = 10
profile.shotsPerMinute = 12
profile.startHeight = 0.7
profile.penetrationMM = 150
profile.soundTurret = "Turret_Electre"
profile.soundTurretCooldown = 0.25
weaponConfig[F.weaponPart] = profile
F.profile = profile
-- Keep AP damage unchanged. HE uses precisely the tank profile's explosion
-- radius, flash radius and damage, with MTK's existing explosion code.
local heProfile = {}
for key, value in pairs(profile) do heProfile[key] = value end
heProfile.penetrationMM = weaponConfig.M41_76mm.penetrationMM
F.heProfile = heProfile
local originalChoose = chooseWeaponPC
chooseWeaponPC = function(vehicle)
if F.isLAV(vehicle) then
if not F.inputBlocked and F.weaponReady(vehicle) and not F.pendingReload[getPlayer()] then
local part = vehicle:getPartById(F.weaponPart)
local container = part and part:getItemContainer()
if container and container:containsType(F.heAmmoType) then return heProfile end
return profile
end
return nil
end
return originalChoose(vehicle)
end
local originalAim = tankAimSystemPC
Events.OnPlayerUpdate.Remove(originalAim)
tankAimSystemPC = function(player)
local vehicle = player and player:getVehicle()
if not F.isLAV(vehicle) then return originalAim(player) end
local gunner = F.isGunner(vehicle, player) and player == getPlayer() and not player:isDead()
local overInventory = UIManager and UIManager.isMouseOverInventory
and UIManager.isMouseOverInventory()
-- Still call MTK so its existing projectiles continue to advance. A nil
-- weapon prevents input/state changes from driver or non-local updates.
F.inputBlocked = not gunner or overInventory or getGameSpeed() == 0
if F.inputBlocked then
originalAim(player)
F.inputBlocked = false
return
end
local oldDirection = player:getDirectionAngle()
local oldSneaking = player:isSneaking()
local oldVariables = {}
for _, name in ipairs(characterVariables) do oldVariables[name] = player:getVariableString(name) end
local aiming = isMouseButtonDown(1) and player:getCharacterActions():isEmpty()
if aiming then
-- ToWorld also uses the installed Viewpoint mouse bridge when active.
-- Scaled mouse coordinates are required by the base-game conversion.
local x, y = ISCoordConversion.ToWorld(getMouseXScaled(), getMouseYScaled(), player:getZ() + profile.startHeight)
if x and y then
local dx, dy = x - player:getX(), y - player:getY()
if dx * dx + dy * dy > 0.0001 then
player:setDirectionAngle(math.deg(math.atan2(dy, dx)))
end
end
end
local weapon = vehicle:getPartById(F.weaponPart)
local container = weapon and weapon:getItemContainer()
local round = aiming and isMouseButtonDown(0) and container and (container:FindAndReturn(F.heAmmoType) or container:FindAndReturn(F.ammoType))
originalAim(player)
if round and isClient() and not container:contains(round) then
sendRemoveItemFromContainer(container, round)
end
-- Keep DAMN's existing seated pose instead of MTK's exposed M2 gunner pose.
player:setDirectionAngle(oldDirection)
player:setSneaking(oldSneaking)
for _, name in ipairs(characterVariables) do
if oldVariables[name] and oldVariables[name] ~= "" then
player:setVariable(name, oldVariables[name])
else
player:clearVariable(name)
end
end
if aiming and turretAngles[vehicle:getId()] and not isClient() then
local hull = calculateVehicleRotation(vehicle:getAngleX(), vehicle:getAngleY(), vehicle:getAngleZ())
local relative = (hull - turretAngles[vehicle:getId()] + 360) % 360
F.savePose(vehicle, F.poseForAngle(relative))
end
end
Events.OnPlayerUpdate.Add(tankAimSystemPC)
F.clientInstalled = true
print("[KI5LAVTurret] Gunner controls registered with Military Tool Kit.")
end
Events.OnGameBoot.Add(F.installClient)
Events.OnGameStart.Add(F.installClient)
@@ -0,0 +1,85 @@
require "TimedActions/ISBaseTimedAction"
require "KLT_LAV"
KLT_ReloadLAV = ISBaseTimedAction:derive("KLT_ReloadLAV")
local L = KLTLAV
function KLT_ReloadLAV:isValid()
if isClient() or not L.isGunner(self.vehicle, self.character) or not L.weaponReady(self.vehicle) then return false end
local gun = self.vehicle:getPartById(L.weaponPart)
local storage = self.vehicle:getPartById(L.storagePart)
return gun == self.gun and storage == self.storage and storage:getInventoryItem() ~= nil
and storage:getCondition() > 0 and gun:getItemContainer() == self.breech
and storage:getItemContainer() == self.source and self.breech:isEmpty()
and self.source:contains(self.round) and L.isAmmoType(self.round:getFullType())
end
function KLT_ReloadLAV:start()
self.character:getEmitter():playSound("Turret_Reload_Open")
end
function KLT_ReloadLAV:clearPending()
if L.pendingReload[self.character] == self then L.pendingReload[self.character] = nil end
end
function KLT_ReloadLAV:stop()
self:clearPending()
ISBaseTimedAction.stop(self)
end
function KLT_ReloadLAV:forceCancel()
self:clearPending()
end
function KLT_ReloadLAV:perform()
-- Validate again: leaving the seat, losing the round or loading another
-- shell during the action must never create or move ammunition.
if self:isValid() then
self.source:Remove(self.round)
self.breech:AddItem(self.round)
self.character:getEmitter():playSound("Turret_Reload_Close")
end
self:clearPending()
ISBaseTimedAction.perform(self)
end
function KLT_ReloadLAV:new(character, vehicle, gun, storage, round)
local o = ISBaseTimedAction.new(self, character)
o.vehicle = vehicle
o.gun = gun
o.storage = storage
o.source = storage:getItemContainer()
o.breech = gun:getItemContainer()
o.round = round
o.maxTime = 300 -- about five seconds before character modifiers
o.stopOnWalk = true
o.stopOnRun = true
o.stopOnAim = false -- pendingReload blocks firing while the player holds aim
return o
end
function L.queueReload(character)
local vehicle = character and character:getVehicle()
if not L.isGunner(vehicle, character) or not L.weaponReady(vehicle) or L.pendingReload[character] then return false end
if isClient() then character:Say("LAV reload prototype currently supports single-player."); return false end
local gun = vehicle:getPartById(L.weaponPart)
local storage = vehicle:getPartById(L.storagePart)
local source = storage and storage:getItemContainer()
local breech = gun and gun:getItemContainer()
if not source or not breech or not breech:isEmpty() then return false end
-- Match MTK tank reload priority: HE first when both types are stored.
local round = source:FindAndReturn(L.heAmmoType) or source:FindAndReturn(L.ammoType)
if not round then character:Say("No 90 mm AP or HE shells in LAV ammunition storage."); return false end
local action = KLT_ReloadLAV:new(character, vehicle, gun, storage, round)
if not action:isValid() then return false end
L.pendingReload[character] = action
ISTimedActionQueue.add(action)
return true
end
Events.OnKeyPressed.Add(function(key)
if key == Keyboard.KEY_R and getGameSpeed() > 0
and not (UIManager and UIManager.isMouseOverInventory and UIManager.isMouseOverInventory()) then
L.queueReload(getSpecificPlayer(0))
end
end)