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
+20
View File
@@ -0,0 +1,20 @@
# Functional Turrets release artwork
Original distressed survival-poster layout, using the actual modified KI5 vehicle meshes rendered in Blender. HRSys accent: `#F16465`. No AI image generation was used. The Ferret and LAV-300 meshes originate from KI5; this artwork does not change their ownership or permissions.
- `preview.png`: 1024 x 1024 Workshop and mod preview.
- `icon.png`: 512 x 512 transparent shared mod icon.
- `preview-master.png`: 2048 x 2048 editable composition output.
- `icon-master.png`: 1024 x 1024 transparent master.
- `*-release-render.blend`: prepared scenes with geometry, procedural materials, lights and orthographic cameras. Open in Blender to change the renders.
- `*-turret-render.png`: transparent render inputs for the composition.
To rebuild the composition on Windows with Python, Pillow, NumPy, and the Impact/Courier Bold Windows fonts:
```powershell
python compose_release_art.py --output-dir .
```
The original `render_release_turrets.py` is included as process source; it expects the development `work/lav_turret_rig.blend` and `work/ferret_turret_rig.blend` inputs. The included prepared `.blend` scenes can be rendered directly without those inputs.
The deployed images are also copied into each mod's root and `42.20` directories. Update those copies and the project root `preview.png` after changing the artwork.
+22
View File
@@ -0,0 +1,22 @@
{
"style": "Original distressed survival-poster treatment; actual game-model renders",
"pink": "#F16465",
"preview_master": [
2048,
2048
],
"preview": [
1024,
1024
],
"icon_master": [
1024,
1024
],
"icon": [
512,
512
],
"icon_transparent": true,
"no_image_generation": true
}
+80
View File
@@ -0,0 +1,80 @@
"""Compose the release art from native Blender renders and original typography."""
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageChops, ImageOps
import numpy as np, math, json, argparse
work=Path(__file__).parent
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output-dir',type=Path,default=work.parent/'outputs/FunctionalTurrets-release-art')
out=parser.parse_args().output_dir
W=2048;coral=(241,100,101);cream=(232,226,209);rng=np.random.default_rng(271026)
impact=Path(r'C:\Windows\Fonts\impact.ttf');mono=Path(r'C:\Windows\Fonts\courbd.ttf')
font=lambda n:ImageFont.truetype(str(impact),n)
small=lambda n:ImageFont.truetype(str(mono),n)
def fit(text,maxwidth,maxsize,fontpath=impact):
for n in range(maxsize,10,-1):
f=ImageFont.truetype(str(fontpath),n)
if f.getlength(text)<=maxwidth:return f
def cutout(path):
im=Image.open(path).convert('RGBA');return im.crop(im.getchannel('A').getbbox())
def arrows(layer,box,color,width):
d=ImageDraw.Draw(layer);cx=(box[0]+box[2])/2;cy=(box[1]+box[3])/2;rx=(box[2]-box[0])/2;ry=(box[3]-box[1])/2
for start,end in [(12,155),(195,338)]:
d.arc(box,start,end,fill=color,width=width)
a=math.radians(end);x=cx+rx*math.cos(a);y=cy+ry*math.sin(a)
tangent=np.array([-rx*math.sin(a),ry*math.cos(a)]);tangent=tangent/np.linalg.norm(tangent)
side=np.array([-tangent[1],tangent[0]])
tip=np.array([x,y])+tangent*width*1.6;base=np.array([x,y])-tangent*width*1.9
d.polygon([tuple(tip),tuple(base+side*width*1.55),tuple(base-side*width*1.55)],fill=color)
def distressed_text(canvas,position,text,typeface,color):
mask=Image.new('L',canvas.size);d=ImageDraw.Draw(mask);d.text(position,text,font=typeface,fill=255,anchor='lt')
a=np.asarray(mask).copy();noise=rng.random(a.shape)
a[(noise<.025)&(a>0)]=30
# Thin uneven scratches cut through the lettering without sacrificing shape.
scratches=Image.new('L',canvas.size);sd=ImageDraw.Draw(scratches)
for _ in range(40):
x=int(rng.integers(70,W-70));y=int(rng.integers(170,650));sd.line((x,y,x+int(rng.integers(5,100)),y+int(rng.integers(-3,4))),fill=150,width=int(rng.integers(1,3)))
mask=ImageChops.subtract(Image.fromarray(a),scratches)
canvas.alpha_composite(Image.composite(Image.new('RGBA',canvas.size,(*color,255)),Image.new('RGBA',canvas.size),mask))
# Weathered dark olive paper, with a restrained warm vignette.
yy,xx=np.mgrid[0:W,0:W];rad=((xx/W-.5)**2+(yy/W-.5)**2)
grain=rng.normal(0,2.3,(W,W));coarse=Image.fromarray(rng.integers(0,255,(64,64),dtype=np.uint8)).resize((W,W),Image.Resampling.BICUBIC)
tone=(np.asarray(coarse).astype(float)-127)/35
background=np.stack([22+grain+tone-10*rad,27+grain+tone-12*rad,24+grain+tone-11*rad],axis=2)
poster=Image.fromarray(np.clip(background,0,255).astype('uint8')).convert('RGBA')
texture=Image.new('RGBA',poster.size);td=ImageDraw.Draw(texture)
for x in range(82,W,128):td.line((x,725,x,1770),fill=(190,190,160,9),width=1)
for y in range(770,1790,128):td.line((85,y,1960,y),fill=(190,190,160,9),width=1)
for _ in range(180):
x,y=rng.integers(15,W-15,2);td.line((int(x),int(y),int(x+rng.integers(-2,3)),int(y+rng.integers(2,55))),fill=(150,145,120,int(rng.integers(5,20))),width=1)
td.rectangle((36,36,2011,2011),outline=(213,209,185,40),width=2)
poster.alpha_composite(texture)
distressed_text(poster,(88,175),'FUNCTIONAL',fit('FUNCTIONAL',1872,262),cream)
distressed_text(poster,(82,384),'TURRETS',fit('TURRETS',1884,377),cream)
ferret=cutout(out/'ferret-turret-render.png');lav=cutout(out/'lav-turret-render.png')
ferret=ImageOps.contain(ferret,(840,890),Image.Resampling.LANCZOS)
lav=ImageOps.contain(lav,(1260,1020),Image.Resampling.LANCZOS)
def place(im,x,y):
shadow=Image.new('RGBA',poster.size);a=im.getchannel('A').filter(ImageFilter.GaussianBlur(9))
shade=Image.new('RGBA',im.size,(0,0,0,0));shade.putalpha(a.point(lambda p:int(p*.65)))
shadow.alpha_composite(shade,(x+14,y+24));poster.alpha_composite(shadow);poster.alpha_composite(im,(x,y))
place(ferret,82,822)
place(lav,1945-lav.width,775)
poster.convert('RGB').save(out/'preview-master.png')
poster.convert('RGB').resize((1024,1024),Image.Resampling.LANCZOS).save(out/'preview.png')
# Shared icon: transparent model silhouette and rotation arrows, no tiny text.
icon=Image.new('RGBA',(1024,1024));ring=Image.new('RGBA',icon.size)
arrows(ring,(90,637,930,960),(*coral,240),24);icon.alpha_composite(ring)
model=ImageOps.contain(cutout(out/'lav-turret-render.png'),(880,865),Image.Resampling.LANCZOS)
a=model.getchannel('A');outline=ImageChops.subtract(a.filter(ImageFilter.MaxFilter(9)),a)
outline_layer=Image.new('RGBA',model.size,(*cream,255));outline_layer.putalpha(outline.point(lambda p:int(p*.78)))
x=(1024-model.width)//2;y=32
icon.alpha_composite(outline_layer,(x,y));icon.alpha_composite(model,(x,y))
icon.save(out/'icon-master.png');icon.resize((512,512),Image.Resampling.LANCZOS).save(out/'icon.png')
icon.resize((128,128),Image.Resampling.LANCZOS).save(out/'icon-128.png')
poster.convert('RGB').resize((256,256),Image.Resampling.LANCZOS).save(out/'preview-256.png')
(out/'artwork.json').write_text(json.dumps({'style':'Original distressed survival-poster treatment; actual game-model renders','pink':'#F16465','preview_master':[2048,2048],'preview':[1024,1024],'icon_master':[1024,1024],'icon':[512,512],'icon_transparent':True,'no_image_generation':True},indent=2))
print('Composed release preview and transparent icon:',out)
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

+59
View File
@@ -0,0 +1,59 @@
"""Release-art model renders. Uses existing meshes, no image generation."""
import bpy, bmesh, json, math
from pathlib import Path
from mathutils import Vector
work=Path(__file__).parent
out=work.parent/'outputs/FunctionalTurrets-release-art'
out.mkdir(parents=True,exist_ok=True)
def linear(x):return x/12.92 if x<=0.04045 else ((x+0.055)/1.055)**2.4
def rgba(hex):return tuple(linear(int(hex[i:i+2],16)/255) for i in (0,2,4))+(1,)
for kind in ['lav','ferret']:
bpy.ops.wm.open_mainfile(filepath=str(work/('lav_turret_rig.blend' if kind=='lav' else 'ferret_turret_rig.blend')))
rig=bpy.data.objects['KLT_LAVSkeleton' if kind=='lav' else 'KFT_FerretSkeleton']
rig.animation_data_clear()
for bone in rig.pose.bones:bone.matrix_basis.identity()
upper=bpy.data.objects['KLT_LAVTurret' if kind=='lav' else 'KFT_FerretTurret']
keep=({'KLT_LAVTurret','KLT_LAVHatch','KLT_LAVViewports','KLT_LAVRadio','KLT_LAVInterior'} if kind=='lav' else {'KFT_FerretTurret','KFT_FerretHatch'})
for obj in list(bpy.context.scene.objects):
if obj.type=='MESH' and obj.name not in keep:bpy.data.objects.remove(obj,do_unlink=True)
group=upper.vertex_groups['body_bone'].index
fixed={v.index for v in upper.data.vertices if any(g.group==group and g.weight>.99 for g in v.groups)}
faces={p.index for p in upper.data.polygons if all(i in fixed for i in p.vertices)}
assert len(faces)==(52 if kind=='lav' else 16)
bm=bmesh.new();bm.from_mesh(upper.data);bm.faces.ensure_lookup_table()
bmesh.ops.delete(bm,geom=[f for f in bm.faces if f.index in faces],context='FACES');bm.to_mesh(upper.data);bm.free()
mat=bpy.data.materials.new('HRSys distressed coral');mat.use_nodes=True
nodes=mat.node_tree.nodes;links=mat.node_tree.links;bsdf=nodes.get('Principled BSDF')
noise=nodes.new('ShaderNodeTexNoise');noise.inputs['Scale'].default_value=18;noise.inputs['Detail'].default_value=3.5;noise.inputs['Roughness'].default_value=.75
coords=nodes.new('ShaderNodeTexCoord');links.new(coords.outputs['Generated'],noise.inputs['Vector'])
ramp=nodes.new('ShaderNodeValToRGB');ramp.color_ramp.elements[0].position=.22;ramp.color_ramp.elements[0].color=rgba('3e3330')
ramp.color_ramp.elements[1].position=.46;ramp.color_ramp.elements[1].color=rgba('e46060')
links.new(noise.outputs['Fac'],ramp.inputs[0]);links.new(ramp.outputs['Color'],bsdf.inputs['Base Color'])
bsdf.inputs['Roughness'].default_value=.84;bsdf.inputs['Metallic'].default_value=.08
bump=nodes.new('ShaderNodeBump');bump.inputs['Strength'].default_value=.10;bump.inputs['Distance'].default_value=.025;links.new(noise.outputs['Fac'],bump.inputs['Height']);links.new(bump.outputs['Normal'],bsdf.inputs['Normal'])
for obj in bpy.context.scene.objects:
if obj.type=='MESH':obj.data.materials.clear();obj.data.materials.append(mat)
scene=bpy.context.scene;scene.render.engine='CYCLES';scene.cycles.samples=96;scene.cycles.use_denoising=True
scene.render.film_transparent=True;scene.render.image_settings.file_format='PNG';scene.render.image_settings.color_mode='RGBA';scene.render.image_settings.color_depth='8'
scene.view_settings.view_transform='Standard';scene.view_settings.look='None';scene.view_settings.exposure=-.15
scene.world.use_nodes=True;scene.world.node_tree.nodes['Background'].inputs[0].default_value=(.72,.77,.72,1);scene.world.node_tree.nodes['Background'].inputs[1].default_value=.32
scene.render.resolution_x=1800;scene.render.resolution_y=1800;scene.render.resolution_percentage=100
bpy.context.view_layer.update()
points=[o.matrix_world@Vector(c) for o in scene.objects if o.type=='MESH' for c in o.bound_box]
low=Vector(tuple(min(v[a] for v in points) for a in range(3)));high=Vector(tuple(max(v[a] for v in points) for a in range(3)));centre=(low+high)/2
size=max(high-low)
direction=Vector((.70,1,.67)) if kind=='lav' else Vector((-.8,1,.70))
bpy.ops.object.camera_add(location=centre+direction.normalized()*size*2.5)
cam=bpy.context.object;cam.rotation_euler=(centre-cam.location).to_track_quat('-Z','Y').to_euler();cam.data.type='ORTHO';scene.camera=cam
rot=cam.rotation_euler.to_matrix();projected=[rot.transposed()@(v-centre) for v in points]
span=[max(p[a] for p in projected)-min(p[a] for p in projected) for a in range(2)]
midpoint=Vector(tuple((max(p[a] for p in projected)+min(p[a] for p in projected))/2 for a in range(2))+(0,))
cam.location+=rot@midpoint;cam.data.ortho_scale=max(span)*1.08
def light(name,position,energy,scale):
data=bpy.data.lights.new(name,'AREA');data.energy=energy*size*size;data.size=scale*size
obj=bpy.data.objects.new(name,data);scene.collection.objects.link(obj);obj.location=centre+Vector(position)*size;obj.rotation_euler=(centre-obj.location).to_track_quat('-Z','Y').to_euler()
light('Warm key',(-.6,.8,1.3),10,.65);light('Soft fill',(.8,.3,.5),3,.8);light('Rim',(.5,-1,1.1),13,.5)
scene.render.filepath=str(out/f'{kind}-turret-render.png');bpy.ops.render.render(write_still=True)
bpy.ops.wm.save_as_mainfile(filepath=str(out/f'{kind}-release-render.blend'))
print('RELEASE_RENDER',kind,out)
Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

@@ -0,0 +1,108 @@
require "KFT_Ferret"
require "Vehicles/Papa_Chad_Tank_Cannon"
local F = KFTFerret
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.M1919A4_Coax
or not chooseWeaponPC or not tankAimSystemPC then
print("[KI5FerretTurret] MTK turret API unavailable; controls not installed.")
return
end
local profile = {}
for key, value in pairs(weaponConfig.M1919A4_Coax) 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 = nil
profile.coax = false
profile.sound = "HRSys_Ferret_Browning_Fire"
profile.soundEnd = "HRSys_Ferret_Browning_End"
profile.rotationTime = 12
profile.soundTurret = "Turret_Manual"
profile.soundTurretCooldown = 0.25
weaponConfig[F.weaponPart] = profile
F.profile = profile
local originalChoose = chooseWeaponPC
chooseWeaponPC = function(vehicle)
if F.isFerret(vehicle) then
if not F.inputBlocked and F.weaponReady(vehicle) then 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.isFerret(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.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("[KI5FerretTurret] Gunner controls registered with Military Tool Kit.")
end
Events.OnGameBoot.Add(F.installClient)
Events.OnGameStart.Add(F.installClient)
@@ -0,0 +1,27 @@
require "KFT_Ferret"
local F = KFTFerret
local function persistTurret(module, command, player, args)
if module ~= "turret_sync" or command ~= "syncAnim" or not args then return end
if type(args.vehicleID) ~= "number" or type(args.animID) ~= "string" then return end
local value = tonumber(args.animID)
if not value or value % 2 ~= 0 or value < 2 or value > 360 then return end
local vehicle = getVehicleById(args.vehicleID)
if F.isGunner(vehicle, player) then F.savePose(vehicle, string.format("%03d", value)) end
end
Events.OnClientCommand.Add(persistTurret)
-- MTK supplies the ammunition items and unpacking recipes. Give them a modest
-- source in military ammunition stores even when the M41 is not enabled.
require "Items/ProceduralDistributions"
local supplies = ProceduralDistributions.list.ArmyStorageAmmunition
if supplies and supplies.items then
local alreadyPresent = false
for index = 1, #supplies.items, 2 do
if supplies.items[index] == "Base.762x63mmCarton" then alreadyPresent = true end
end
if not alreadyPresent then
table.insert(supplies.items, "Base.762x63mmCarton")
table.insert(supplies.items, 3)
end
end
@@ -0,0 +1,92 @@
-- Local compatibility adapter. Original Workshop files remain dependencies.
KFTFerret = KFTFerret or {}
local F = KFTFerret
F.scriptName = "Base.62daimlerFerretMk2"
F.weaponPart = "KFT_FerretMG"
F.ammoType = "Base.762x63mm"
F.version = 1
function F.isFerret(vehicle)
return vehicle ~= nil and vehicle:getScriptName() == F.scriptName
end
function F.isGunner(vehicle, character)
return F.isFerret(vehicle) and character ~= nil
and character:getVehicle() == vehicle and vehicle:getSeat(character) == 1
end
function F.AmmoAccess(vehicle, part, character)
return F.isGunner(vehicle, character)
end
function F.InitTurret(vehicle, part)
local data = part:getModData()
local vehicleData = vehicle:getModData()
local pose = data.kftPose or vehicleData.turretAnimID or "002"
vehicleData.turretAnimID = pose
part:setModelVisible("Default", true)
vehicle:playPartAnim(part, pose)
local gloveBox = vehicle:getPartById("GloveBox")
if gloveBox then gloveBox:setModelVisible("interiorTop", false) end
end
function F.InitGloveBox(vehicle, part)
part:setModelVisible("interiorTop", false)
end
function F.InitWeapon(vehicle, part)
local data = part:getModData()
if not data.kftInitialized then
-- Called once for a newly added part, including an existing-save migration.
-- A removed or destroyed gun is never recreated on subsequent loads.
if not part:getInventoryItem() then
VehicleUtils.createPartInventoryItem(part)
part:setCondition(100)
end
data.kftInitialized = F.version
end
end
function F.weaponReady(vehicle)
if not F.isFerret(vehicle) then return false end
local part = vehicle:getPartById(F.weaponPart)
return part ~= nil and part:getInventoryItem() ~= nil and part:getCondition() > 0
end
function F.poseForAngle(relativeAngle)
local pose = math.floor(math.floor((relativeAngle % 360) + 0.5) / 2) * 2 + 2
if pose > 360 then pose = 2 end
return string.format("%03d", pose)
end
function F.savePose(vehicle, pose)
local turret = vehicle:getPartById("Turrent")
if not turret then return end
vehicle:getModData().turretAnimID = pose
local data = turret:getModData()
if data.kftPose ~= pose then
data.kftPose = pose
if isServer() then vehicle:transmitPartModData(turret) end
end
end
function F.patchScript()
local script = getScriptManager():getVehicle(F.scriptName)
if not script then
print("[KI5FerretTurret] Ferret Mk2 script unavailable; adapter not applied.")
return false
end
if script:getPartById(F.weaponPart) then return true end
if not script:getPartById("GloveBox") or not script:getPartById("FER62HatchTurret") then
print("[KI5FerretTurret] Unexpected Ferret layout; adapter not applied.")
return false
end
-- Load merges only the named parts. Do NOT call Loaded(): in B42.21 it
-- multiplies chassis dimensions and suspension by modelScale a second time.
script:Load("62daimlerFerretMk2", require "KFT_FerretPatch")
print("[KI5FerretTurret] Ferret Mk2 turret parts applied (v0.1.1).")
return true
end
Events.OnGameBoot.Add(F.patchScript)
Events.OnInitGlobalModData.Add(F.patchScript)
@@ -0,0 +1,214 @@
return [=[
{
part GloveBox
{
lua { create = Vehicles.Create.Default, init = KFTFerret.InitGloveBox, }
}
part Turrent
{
category = nodisplay,
area = SeatFrontRight,
model Default { file = KFT_FerretTurret, scale = 1.0, offset = 0 0 0, rotate = 0 0 0, }
lua { create = KFTFerret.InitTurret, init = KFTFerret.InitTurret, }
anim 002 { anim = KFT_FerretYaw_002, animate = FALSE, }
anim 004 { anim = KFT_FerretYaw_004, animate = FALSE, }
anim 006 { anim = KFT_FerretYaw_006, animate = FALSE, }
anim 008 { anim = KFT_FerretYaw_008, animate = FALSE, }
anim 010 { anim = KFT_FerretYaw_010, animate = FALSE, }
anim 012 { anim = KFT_FerretYaw_012, animate = FALSE, }
anim 014 { anim = KFT_FerretYaw_014, animate = FALSE, }
anim 016 { anim = KFT_FerretYaw_016, animate = FALSE, }
anim 018 { anim = KFT_FerretYaw_018, animate = FALSE, }
anim 020 { anim = KFT_FerretYaw_020, animate = FALSE, }
anim 022 { anim = KFT_FerretYaw_022, animate = FALSE, }
anim 024 { anim = KFT_FerretYaw_024, animate = FALSE, }
anim 026 { anim = KFT_FerretYaw_026, animate = FALSE, }
anim 028 { anim = KFT_FerretYaw_028, animate = FALSE, }
anim 030 { anim = KFT_FerretYaw_030, animate = FALSE, }
anim 032 { anim = KFT_FerretYaw_032, animate = FALSE, }
anim 034 { anim = KFT_FerretYaw_034, animate = FALSE, }
anim 036 { anim = KFT_FerretYaw_036, animate = FALSE, }
anim 038 { anim = KFT_FerretYaw_038, animate = FALSE, }
anim 040 { anim = KFT_FerretYaw_040, animate = FALSE, }
anim 042 { anim = KFT_FerretYaw_042, animate = FALSE, }
anim 044 { anim = KFT_FerretYaw_044, animate = FALSE, }
anim 046 { anim = KFT_FerretYaw_046, animate = FALSE, }
anim 048 { anim = KFT_FerretYaw_048, animate = FALSE, }
anim 050 { anim = KFT_FerretYaw_050, animate = FALSE, }
anim 052 { anim = KFT_FerretYaw_052, animate = FALSE, }
anim 054 { anim = KFT_FerretYaw_054, animate = FALSE, }
anim 056 { anim = KFT_FerretYaw_056, animate = FALSE, }
anim 058 { anim = KFT_FerretYaw_058, animate = FALSE, }
anim 060 { anim = KFT_FerretYaw_060, animate = FALSE, }
anim 062 { anim = KFT_FerretYaw_062, animate = FALSE, }
anim 064 { anim = KFT_FerretYaw_064, animate = FALSE, }
anim 066 { anim = KFT_FerretYaw_066, animate = FALSE, }
anim 068 { anim = KFT_FerretYaw_068, animate = FALSE, }
anim 070 { anim = KFT_FerretYaw_070, animate = FALSE, }
anim 072 { anim = KFT_FerretYaw_072, animate = FALSE, }
anim 074 { anim = KFT_FerretYaw_074, animate = FALSE, }
anim 076 { anim = KFT_FerretYaw_076, animate = FALSE, }
anim 078 { anim = KFT_FerretYaw_078, animate = FALSE, }
anim 080 { anim = KFT_FerretYaw_080, animate = FALSE, }
anim 082 { anim = KFT_FerretYaw_082, animate = FALSE, }
anim 084 { anim = KFT_FerretYaw_084, animate = FALSE, }
anim 086 { anim = KFT_FerretYaw_086, animate = FALSE, }
anim 088 { anim = KFT_FerretYaw_088, animate = FALSE, }
anim 090 { anim = KFT_FerretYaw_090, animate = FALSE, }
anim 092 { anim = KFT_FerretYaw_092, animate = FALSE, }
anim 094 { anim = KFT_FerretYaw_094, animate = FALSE, }
anim 096 { anim = KFT_FerretYaw_096, animate = FALSE, }
anim 098 { anim = KFT_FerretYaw_098, animate = FALSE, }
anim 100 { anim = KFT_FerretYaw_100, animate = FALSE, }
anim 102 { anim = KFT_FerretYaw_102, animate = FALSE, }
anim 104 { anim = KFT_FerretYaw_104, animate = FALSE, }
anim 106 { anim = KFT_FerretYaw_106, animate = FALSE, }
anim 108 { anim = KFT_FerretYaw_108, animate = FALSE, }
anim 110 { anim = KFT_FerretYaw_110, animate = FALSE, }
anim 112 { anim = KFT_FerretYaw_112, animate = FALSE, }
anim 114 { anim = KFT_FerretYaw_114, animate = FALSE, }
anim 116 { anim = KFT_FerretYaw_116, animate = FALSE, }
anim 118 { anim = KFT_FerretYaw_118, animate = FALSE, }
anim 120 { anim = KFT_FerretYaw_120, animate = FALSE, }
anim 122 { anim = KFT_FerretYaw_122, animate = FALSE, }
anim 124 { anim = KFT_FerretYaw_124, animate = FALSE, }
anim 126 { anim = KFT_FerretYaw_126, animate = FALSE, }
anim 128 { anim = KFT_FerretYaw_128, animate = FALSE, }
anim 130 { anim = KFT_FerretYaw_130, animate = FALSE, }
anim 132 { anim = KFT_FerretYaw_132, animate = FALSE, }
anim 134 { anim = KFT_FerretYaw_134, animate = FALSE, }
anim 136 { anim = KFT_FerretYaw_136, animate = FALSE, }
anim 138 { anim = KFT_FerretYaw_138, animate = FALSE, }
anim 140 { anim = KFT_FerretYaw_140, animate = FALSE, }
anim 142 { anim = KFT_FerretYaw_142, animate = FALSE, }
anim 144 { anim = KFT_FerretYaw_144, animate = FALSE, }
anim 146 { anim = KFT_FerretYaw_146, animate = FALSE, }
anim 148 { anim = KFT_FerretYaw_148, animate = FALSE, }
anim 150 { anim = KFT_FerretYaw_150, animate = FALSE, }
anim 152 { anim = KFT_FerretYaw_152, animate = FALSE, }
anim 154 { anim = KFT_FerretYaw_154, animate = FALSE, }
anim 156 { anim = KFT_FerretYaw_156, animate = FALSE, }
anim 158 { anim = KFT_FerretYaw_158, animate = FALSE, }
anim 160 { anim = KFT_FerretYaw_160, animate = FALSE, }
anim 162 { anim = KFT_FerretYaw_162, animate = FALSE, }
anim 164 { anim = KFT_FerretYaw_164, animate = FALSE, }
anim 166 { anim = KFT_FerretYaw_166, animate = FALSE, }
anim 168 { anim = KFT_FerretYaw_168, animate = FALSE, }
anim 170 { anim = KFT_FerretYaw_170, animate = FALSE, }
anim 172 { anim = KFT_FerretYaw_172, animate = FALSE, }
anim 174 { anim = KFT_FerretYaw_174, animate = FALSE, }
anim 176 { anim = KFT_FerretYaw_176, animate = FALSE, }
anim 178 { anim = KFT_FerretYaw_178, animate = FALSE, }
anim 180 { anim = KFT_FerretYaw_180, animate = FALSE, }
anim 182 { anim = KFT_FerretYaw_182, animate = FALSE, }
anim 184 { anim = KFT_FerretYaw_184, animate = FALSE, }
anim 186 { anim = KFT_FerretYaw_186, animate = FALSE, }
anim 188 { anim = KFT_FerretYaw_188, animate = FALSE, }
anim 190 { anim = KFT_FerretYaw_190, animate = FALSE, }
anim 192 { anim = KFT_FerretYaw_192, animate = FALSE, }
anim 194 { anim = KFT_FerretYaw_194, animate = FALSE, }
anim 196 { anim = KFT_FerretYaw_196, animate = FALSE, }
anim 198 { anim = KFT_FerretYaw_198, animate = FALSE, }
anim 200 { anim = KFT_FerretYaw_200, animate = FALSE, }
anim 202 { anim = KFT_FerretYaw_202, animate = FALSE, }
anim 204 { anim = KFT_FerretYaw_204, animate = FALSE, }
anim 206 { anim = KFT_FerretYaw_206, animate = FALSE, }
anim 208 { anim = KFT_FerretYaw_208, animate = FALSE, }
anim 210 { anim = KFT_FerretYaw_210, animate = FALSE, }
anim 212 { anim = KFT_FerretYaw_212, animate = FALSE, }
anim 214 { anim = KFT_FerretYaw_214, animate = FALSE, }
anim 216 { anim = KFT_FerretYaw_216, animate = FALSE, }
anim 218 { anim = KFT_FerretYaw_218, animate = FALSE, }
anim 220 { anim = KFT_FerretYaw_220, animate = FALSE, }
anim 222 { anim = KFT_FerretYaw_222, animate = FALSE, }
anim 224 { anim = KFT_FerretYaw_224, animate = FALSE, }
anim 226 { anim = KFT_FerretYaw_226, animate = FALSE, }
anim 228 { anim = KFT_FerretYaw_228, animate = FALSE, }
anim 230 { anim = KFT_FerretYaw_230, animate = FALSE, }
anim 232 { anim = KFT_FerretYaw_232, animate = FALSE, }
anim 234 { anim = KFT_FerretYaw_234, animate = FALSE, }
anim 236 { anim = KFT_FerretYaw_236, animate = FALSE, }
anim 238 { anim = KFT_FerretYaw_238, animate = FALSE, }
anim 240 { anim = KFT_FerretYaw_240, animate = FALSE, }
anim 242 { anim = KFT_FerretYaw_242, animate = FALSE, }
anim 244 { anim = KFT_FerretYaw_244, animate = FALSE, }
anim 246 { anim = KFT_FerretYaw_246, animate = FALSE, }
anim 248 { anim = KFT_FerretYaw_248, animate = FALSE, }
anim 250 { anim = KFT_FerretYaw_250, animate = FALSE, }
anim 252 { anim = KFT_FerretYaw_252, animate = FALSE, }
anim 254 { anim = KFT_FerretYaw_254, animate = FALSE, }
anim 256 { anim = KFT_FerretYaw_256, animate = FALSE, }
anim 258 { anim = KFT_FerretYaw_258, animate = FALSE, }
anim 260 { anim = KFT_FerretYaw_260, animate = FALSE, }
anim 262 { anim = KFT_FerretYaw_262, animate = FALSE, }
anim 264 { anim = KFT_FerretYaw_264, animate = FALSE, }
anim 266 { anim = KFT_FerretYaw_266, animate = FALSE, }
anim 268 { anim = KFT_FerretYaw_268, animate = FALSE, }
anim 270 { anim = KFT_FerretYaw_270, animate = FALSE, }
anim 272 { anim = KFT_FerretYaw_272, animate = FALSE, }
anim 274 { anim = KFT_FerretYaw_274, animate = FALSE, }
anim 276 { anim = KFT_FerretYaw_276, animate = FALSE, }
anim 278 { anim = KFT_FerretYaw_278, animate = FALSE, }
anim 280 { anim = KFT_FerretYaw_280, animate = FALSE, }
anim 282 { anim = KFT_FerretYaw_282, animate = FALSE, }
anim 284 { anim = KFT_FerretYaw_284, animate = FALSE, }
anim 286 { anim = KFT_FerretYaw_286, animate = FALSE, }
anim 288 { anim = KFT_FerretYaw_288, animate = FALSE, }
anim 290 { anim = KFT_FerretYaw_290, animate = FALSE, }
anim 292 { anim = KFT_FerretYaw_292, animate = FALSE, }
anim 294 { anim = KFT_FerretYaw_294, animate = FALSE, }
anim 296 { anim = KFT_FerretYaw_296, animate = FALSE, }
anim 298 { anim = KFT_FerretYaw_298, animate = FALSE, }
anim 300 { anim = KFT_FerretYaw_300, animate = FALSE, }
anim 302 { anim = KFT_FerretYaw_302, animate = FALSE, }
anim 304 { anim = KFT_FerretYaw_304, animate = FALSE, }
anim 306 { anim = KFT_FerretYaw_306, animate = FALSE, }
anim 308 { anim = KFT_FerretYaw_308, animate = FALSE, }
anim 310 { anim = KFT_FerretYaw_310, animate = FALSE, }
anim 312 { anim = KFT_FerretYaw_312, animate = FALSE, }
anim 314 { anim = KFT_FerretYaw_314, animate = FALSE, }
anim 316 { anim = KFT_FerretYaw_316, animate = FALSE, }
anim 318 { anim = KFT_FerretYaw_318, animate = FALSE, }
anim 320 { anim = KFT_FerretYaw_320, animate = FALSE, }
anim 322 { anim = KFT_FerretYaw_322, animate = FALSE, }
anim 324 { anim = KFT_FerretYaw_324, animate = FALSE, }
anim 326 { anim = KFT_FerretYaw_326, animate = FALSE, }
anim 328 { anim = KFT_FerretYaw_328, animate = FALSE, }
anim 330 { anim = KFT_FerretYaw_330, animate = FALSE, }
anim 332 { anim = KFT_FerretYaw_332, animate = FALSE, }
anim 334 { anim = KFT_FerretYaw_334, animate = FALSE, }
anim 336 { anim = KFT_FerretYaw_336, animate = FALSE, }
anim 338 { anim = KFT_FerretYaw_338, animate = FALSE, }
anim 340 { anim = KFT_FerretYaw_340, animate = FALSE, }
anim 342 { anim = KFT_FerretYaw_342, animate = FALSE, }
anim 344 { anim = KFT_FerretYaw_344, animate = FALSE, }
anim 346 { anim = KFT_FerretYaw_346, animate = FALSE, }
anim 348 { anim = KFT_FerretYaw_348, animate = FALSE, }
anim 350 { anim = KFT_FerretYaw_350, animate = FALSE, }
anim 352 { anim = KFT_FerretYaw_352, animate = FALSE, }
anim 354 { anim = KFT_FerretYaw_354, animate = FALSE, }
anim 356 { anim = KFT_FerretYaw_356, animate = FALSE, }
anim 358 { anim = KFT_FerretYaw_358, animate = FALSE, }
anim 360 { anim = KFT_FerretYaw_360, animate = FALSE, }
}
part FER62HatchTurret
{
parent = Turrent,
model HATCHtop { file = KFT_FerretHatch, scale = 1.0, offset = 0 0 0, rotate = 0 0 0, }
anim Open { anim = KFT_FerretHatch, reverse = FALSE, animate = TRUE, rate = 1.0, }
anim Close { anim = KFT_FerretHatch, reverse = TRUE, animate = TRUE, rate = 1.5, }
anim Closed { anim = KFT_FerretHatch, reverse = FALSE, animate = FALSE, }
anim Opened { anim = KFT_FerretHatch, reverse = TRUE, animate = FALSE, }
}
part KFT_FerretMG
{
itemType = Base.M1919A4_Coax2,
category = Turrent,
area = SeatFrontRight,
mechanicArea = SeatFrontRight,
parent = Turrent,
container { capacity = 10, conditionAffectsCapacity = false, test = KFTFerret.AmmoAccess, }
lua { create = KFTFerret.InitWeapon, init = KFTFerret.InitWeapon, }
}
}
]=]
@@ -0,0 +1,33 @@
module Base
{
sound HRSys_Ferret_Browning_Fire
{
category = Vehicle,
master = Primary,
is3D = true,
loop = false,
maxInstancesPerEmitter = 8,
clip
{
file = media/sound/HRSys_Ferret_Browning_Fire.wav,
volume = 1.0,
distanceMin = 3,
distanceMax = 60,
}
}
sound HRSys_Ferret_Browning_End
{
category = Vehicle,
master = Primary,
is3D = true,
loop = false,
maxInstancesPerEmitter = 8,
clip
{
file = media/sound/HRSys_Ferret_Browning_End.wav,
volume = 1.0,
distanceMin = 3,
distanceMax = 60,
}
}
}
@@ -0,0 +1,20 @@
module Base
{
model KFT_FerretTurret
{
mesh = vehicles/KFT_FerretTurret|KFT_FerretTurret,
shader = damn_vehicle_noreflect_shader,
scale = 0.1,
static = FALSE,
boneWeight = body_bone 1.0,
boneWeight = KFT_FerretYaw 1.0,
}
model KFT_FerretHatch
{
mesh = vehicles/KFT_FerretTurret|KFT_FerretHatch,
shader = damn_vehicle_noreflect_shader,
scale = 0.1,
static = FALSE,
boneWeight = door_turret_bone 1.0,
}
}
@@ -0,0 +1,10 @@
name=HRSys Functional Turrets - Ferret
id=hrsys_functionalturrets_ferret
description=Adds a functioning turret and gunner-operated .30-calibre Browning machine gun to KI5's '62 Daimler Ferret Mk2.
author=Riggs0
category=vehicle
require=\damnlib,\62daimlerFerret,\Military_Tool_Kit
versionMin=42.20.0
modversion=0.1.4
icon=icon.png
poster=preview.png
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

@@ -0,0 +1,4 @@
{
"IGUI_VehiclePartKFT_FerretMG": "Ferret Browning (.30-06)",
"IGUI_VehiclePartTurrent": "Turret"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

@@ -0,0 +1,10 @@
name=HRSys Functional Turrets - Ferret
id=hrsys_functionalturrets_ferret
description=Adds a functioning turret and gunner-operated .30-calibre Browning machine gun to KI5's '62 Daimler Ferret Mk2.
author=Riggs0
category=vehicle
require=\damnlib,\62daimlerFerret,\Military_Tool_Kit
versionMin=42.20.0
modversion=0.1.4
icon=icon.png
poster=preview.png
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

@@ -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)
@@ -0,0 +1,19 @@
require "KLT_LAV"
local L = KLTLAV
Events.OnClientCommand.Add(function(module, command, player, args)
if module ~= "turret_sync" or command ~= "syncAnim" or not args then return end
if type(args.vehicleID) ~= "number" or type(args.animID) ~= "string" then return end
local value = tonumber(args.animID)
if not value or value % 2 ~= 0 or value < 2 or value > 360 then return end
local vehicle = getVehicleById(args.vehicleID)
if L.isGunner(vehicle, player) then L.savePose(vehicle, string.format("%03d", value)) end
end)
require "Items/ProceduralDistributions"
local supplies = ProceduralDistributions.list.ArmyStorageAmmunition
if supplies and supplies.items then
for _, ammoType in ipairs({L.ammoType, L.heAmmoType}) do
local present = false
for i = 1, #supplies.items, 2 do if supplies.items[i] == ammoType then present = true end end
if not present then table.insert(supplies.items, ammoType); table.insert(supplies.items, 3) end
end
end
@@ -0,0 +1,113 @@
KLTLAV = KLTLAV or {}
local L = KLTLAV
L.scriptName = "Base.84gageV300fsv"
L.weaponPart = "KLT_LAV90"
L.storagePart = "KLT_LAVAmmo"
L.ammoType = "KLT.Shell90AP"
L.heAmmoType = "KLT.Shell90HE"
L.pendingReload = L.pendingReload or {}
function L.isAmmoType(fullType)
return fullType == L.ammoType or fullType == L.heAmmoType
end
function L.isLAV(vehicle)
return vehicle ~= nil and vehicle:getScriptName() == L.scriptName
end
function L.isGunner(vehicle, character)
return L.isLAV(vehicle) and character ~= nil and not character:isDead()
and character:getVehicle() == vehicle and vehicle:getSeat(character) == 1
end
function L.AmmoAccess(vehicle, part, character)
return L.isGunner(vehicle, character)
end
function L.BreechAccess(vehicle, part, character)
-- The reload action alone inserts one round. Storage remains accessible.
return false
end
function L.InitGloveBox(vehicle, part)
part:setModelVisible("BaseV300FSV", false)
part:setModelVisible("InteriorV3002", false)
end
function L.InitTurret(vehicle, part)
local pose = part:getModData().kltPose or vehicle:getModData().turretAnimID or "002"
vehicle:getModData().turretAnimID = pose
part:setModelVisible("Default", true)
vehicle:playPartAnim(part, pose)
local glove = vehicle:getPartById("GloveBox")
if glove then L.InitGloveBox(vehicle, glove) end
end
function L.InitInterior(vehicle, part)
-- One animated model per part. B42 updates each ModelInfo against the
-- first ModelInfo for its part; two independent players on Turrent would
-- clear the owner's track pointer and leak its frozen pose tracks.
part:setModelVisible("Default", true)
end
function L.CreateGunnerDoor(vehicle, part)
Vehicles.Create.Door(vehicle, part)
part:setModelVisible("KLT_RigLink", true)
end
function L.InitGunnerDoor(vehicle, part)
Vehicles.Init.Door(vehicle, part)
part:setModelVisible("KLT_RigLink", true)
end
local function initializePart(vehicle, part)
local data = part:getModData()
if not data.kltInitialized then
if not part:getInventoryItem() then
VehicleUtils.createPartInventoryItem(part)
part:setCondition(100)
end
data.kltInitialized = 1
end
end
L.InitWeapon = initializePart
L.InitStorage = initializePart
function L.weaponReady(vehicle)
if not L.isLAV(vehicle) then return false end
local part = vehicle:getPartById(L.weaponPart)
return part ~= nil and part:getInventoryItem() ~= nil and part:getCondition() > 0
end
function L.poseForAngle(angle)
local pose = math.floor(math.floor((angle % 360) + 0.5) / 2) * 2 + 2
if pose > 360 then pose = 2 end
return string.format("%03d", pose)
end
function L.savePose(vehicle, pose)
local part = vehicle:getPartById("Turrent")
if not part then return end
vehicle:getModData().turretAnimID = pose
if part:getModData().kltPose ~= pose then
part:getModData().kltPose = pose
if isServer() then vehicle:transmitPartModData(part) end
end
end
function L.patchScript()
local script = getScriptManager():getVehicle(L.scriptName)
if not script then print("[KI5LAVTurret] LAV FSV script unavailable."); return false end
if script:getPartById(L.weaponPart) then return true end
if not script:getPartById("WindowGunnerHatch") or not script:getPartById("GloveBox")
or not script:getPartById("DoorRearLeft") or not script:getPartById("V300fsvViewports") then
print("[KI5LAVTurret] Unexpected LAV FSV layout; adapter not applied.")
return false
end
-- Merge only the named parts. Loaded() would rescale finalized physics.
script:Load("84gageV300fsv", require "KLT_LAVPatch")
print("[KI5LAVTurret] LAV FSV parts applied (v0.1.2).")
return true
end
Events.OnGameBoot.Add(L.patchScript)
Events.OnInitGlobalModData.Add(L.patchScript)
@@ -0,0 +1,240 @@
return [=[
{
part GloveBox { lua { create = Vehicles.Create.Default, init = KLTLAV.InitGloveBox, } }
part Turrent
{
category = nodisplay,
area = SeatRearLeft,
model Default { file = KLT_LAVTurret, scale = 1.0, offset = 0 0 0, rotate = 0 0 0, }
lua { create = KLTLAV.InitTurret, init = KLTLAV.InitTurret, }
anim 002 { anim = KLT_LAVYaw_002, animate = FALSE, }
anim 004 { anim = KLT_LAVYaw_004, animate = FALSE, }
anim 006 { anim = KLT_LAVYaw_006, animate = FALSE, }
anim 008 { anim = KLT_LAVYaw_008, animate = FALSE, }
anim 010 { anim = KLT_LAVYaw_010, animate = FALSE, }
anim 012 { anim = KLT_LAVYaw_012, animate = FALSE, }
anim 014 { anim = KLT_LAVYaw_014, animate = FALSE, }
anim 016 { anim = KLT_LAVYaw_016, animate = FALSE, }
anim 018 { anim = KLT_LAVYaw_018, animate = FALSE, }
anim 020 { anim = KLT_LAVYaw_020, animate = FALSE, }
anim 022 { anim = KLT_LAVYaw_022, animate = FALSE, }
anim 024 { anim = KLT_LAVYaw_024, animate = FALSE, }
anim 026 { anim = KLT_LAVYaw_026, animate = FALSE, }
anim 028 { anim = KLT_LAVYaw_028, animate = FALSE, }
anim 030 { anim = KLT_LAVYaw_030, animate = FALSE, }
anim 032 { anim = KLT_LAVYaw_032, animate = FALSE, }
anim 034 { anim = KLT_LAVYaw_034, animate = FALSE, }
anim 036 { anim = KLT_LAVYaw_036, animate = FALSE, }
anim 038 { anim = KLT_LAVYaw_038, animate = FALSE, }
anim 040 { anim = KLT_LAVYaw_040, animate = FALSE, }
anim 042 { anim = KLT_LAVYaw_042, animate = FALSE, }
anim 044 { anim = KLT_LAVYaw_044, animate = FALSE, }
anim 046 { anim = KLT_LAVYaw_046, animate = FALSE, }
anim 048 { anim = KLT_LAVYaw_048, animate = FALSE, }
anim 050 { anim = KLT_LAVYaw_050, animate = FALSE, }
anim 052 { anim = KLT_LAVYaw_052, animate = FALSE, }
anim 054 { anim = KLT_LAVYaw_054, animate = FALSE, }
anim 056 { anim = KLT_LAVYaw_056, animate = FALSE, }
anim 058 { anim = KLT_LAVYaw_058, animate = FALSE, }
anim 060 { anim = KLT_LAVYaw_060, animate = FALSE, }
anim 062 { anim = KLT_LAVYaw_062, animate = FALSE, }
anim 064 { anim = KLT_LAVYaw_064, animate = FALSE, }
anim 066 { anim = KLT_LAVYaw_066, animate = FALSE, }
anim 068 { anim = KLT_LAVYaw_068, animate = FALSE, }
anim 070 { anim = KLT_LAVYaw_070, animate = FALSE, }
anim 072 { anim = KLT_LAVYaw_072, animate = FALSE, }
anim 074 { anim = KLT_LAVYaw_074, animate = FALSE, }
anim 076 { anim = KLT_LAVYaw_076, animate = FALSE, }
anim 078 { anim = KLT_LAVYaw_078, animate = FALSE, }
anim 080 { anim = KLT_LAVYaw_080, animate = FALSE, }
anim 082 { anim = KLT_LAVYaw_082, animate = FALSE, }
anim 084 { anim = KLT_LAVYaw_084, animate = FALSE, }
anim 086 { anim = KLT_LAVYaw_086, animate = FALSE, }
anim 088 { anim = KLT_LAVYaw_088, animate = FALSE, }
anim 090 { anim = KLT_LAVYaw_090, animate = FALSE, }
anim 092 { anim = KLT_LAVYaw_092, animate = FALSE, }
anim 094 { anim = KLT_LAVYaw_094, animate = FALSE, }
anim 096 { anim = KLT_LAVYaw_096, animate = FALSE, }
anim 098 { anim = KLT_LAVYaw_098, animate = FALSE, }
anim 100 { anim = KLT_LAVYaw_100, animate = FALSE, }
anim 102 { anim = KLT_LAVYaw_102, animate = FALSE, }
anim 104 { anim = KLT_LAVYaw_104, animate = FALSE, }
anim 106 { anim = KLT_LAVYaw_106, animate = FALSE, }
anim 108 { anim = KLT_LAVYaw_108, animate = FALSE, }
anim 110 { anim = KLT_LAVYaw_110, animate = FALSE, }
anim 112 { anim = KLT_LAVYaw_112, animate = FALSE, }
anim 114 { anim = KLT_LAVYaw_114, animate = FALSE, }
anim 116 { anim = KLT_LAVYaw_116, animate = FALSE, }
anim 118 { anim = KLT_LAVYaw_118, animate = FALSE, }
anim 120 { anim = KLT_LAVYaw_120, animate = FALSE, }
anim 122 { anim = KLT_LAVYaw_122, animate = FALSE, }
anim 124 { anim = KLT_LAVYaw_124, animate = FALSE, }
anim 126 { anim = KLT_LAVYaw_126, animate = FALSE, }
anim 128 { anim = KLT_LAVYaw_128, animate = FALSE, }
anim 130 { anim = KLT_LAVYaw_130, animate = FALSE, }
anim 132 { anim = KLT_LAVYaw_132, animate = FALSE, }
anim 134 { anim = KLT_LAVYaw_134, animate = FALSE, }
anim 136 { anim = KLT_LAVYaw_136, animate = FALSE, }
anim 138 { anim = KLT_LAVYaw_138, animate = FALSE, }
anim 140 { anim = KLT_LAVYaw_140, animate = FALSE, }
anim 142 { anim = KLT_LAVYaw_142, animate = FALSE, }
anim 144 { anim = KLT_LAVYaw_144, animate = FALSE, }
anim 146 { anim = KLT_LAVYaw_146, animate = FALSE, }
anim 148 { anim = KLT_LAVYaw_148, animate = FALSE, }
anim 150 { anim = KLT_LAVYaw_150, animate = FALSE, }
anim 152 { anim = KLT_LAVYaw_152, animate = FALSE, }
anim 154 { anim = KLT_LAVYaw_154, animate = FALSE, }
anim 156 { anim = KLT_LAVYaw_156, animate = FALSE, }
anim 158 { anim = KLT_LAVYaw_158, animate = FALSE, }
anim 160 { anim = KLT_LAVYaw_160, animate = FALSE, }
anim 162 { anim = KLT_LAVYaw_162, animate = FALSE, }
anim 164 { anim = KLT_LAVYaw_164, animate = FALSE, }
anim 166 { anim = KLT_LAVYaw_166, animate = FALSE, }
anim 168 { anim = KLT_LAVYaw_168, animate = FALSE, }
anim 170 { anim = KLT_LAVYaw_170, animate = FALSE, }
anim 172 { anim = KLT_LAVYaw_172, animate = FALSE, }
anim 174 { anim = KLT_LAVYaw_174, animate = FALSE, }
anim 176 { anim = KLT_LAVYaw_176, animate = FALSE, }
anim 178 { anim = KLT_LAVYaw_178, animate = FALSE, }
anim 180 { anim = KLT_LAVYaw_180, animate = FALSE, }
anim 182 { anim = KLT_LAVYaw_182, animate = FALSE, }
anim 184 { anim = KLT_LAVYaw_184, animate = FALSE, }
anim 186 { anim = KLT_LAVYaw_186, animate = FALSE, }
anim 188 { anim = KLT_LAVYaw_188, animate = FALSE, }
anim 190 { anim = KLT_LAVYaw_190, animate = FALSE, }
anim 192 { anim = KLT_LAVYaw_192, animate = FALSE, }
anim 194 { anim = KLT_LAVYaw_194, animate = FALSE, }
anim 196 { anim = KLT_LAVYaw_196, animate = FALSE, }
anim 198 { anim = KLT_LAVYaw_198, animate = FALSE, }
anim 200 { anim = KLT_LAVYaw_200, animate = FALSE, }
anim 202 { anim = KLT_LAVYaw_202, animate = FALSE, }
anim 204 { anim = KLT_LAVYaw_204, animate = FALSE, }
anim 206 { anim = KLT_LAVYaw_206, animate = FALSE, }
anim 208 { anim = KLT_LAVYaw_208, animate = FALSE, }
anim 210 { anim = KLT_LAVYaw_210, animate = FALSE, }
anim 212 { anim = KLT_LAVYaw_212, animate = FALSE, }
anim 214 { anim = KLT_LAVYaw_214, animate = FALSE, }
anim 216 { anim = KLT_LAVYaw_216, animate = FALSE, }
anim 218 { anim = KLT_LAVYaw_218, animate = FALSE, }
anim 220 { anim = KLT_LAVYaw_220, animate = FALSE, }
anim 222 { anim = KLT_LAVYaw_222, animate = FALSE, }
anim 224 { anim = KLT_LAVYaw_224, animate = FALSE, }
anim 226 { anim = KLT_LAVYaw_226, animate = FALSE, }
anim 228 { anim = KLT_LAVYaw_228, animate = FALSE, }
anim 230 { anim = KLT_LAVYaw_230, animate = FALSE, }
anim 232 { anim = KLT_LAVYaw_232, animate = FALSE, }
anim 234 { anim = KLT_LAVYaw_234, animate = FALSE, }
anim 236 { anim = KLT_LAVYaw_236, animate = FALSE, }
anim 238 { anim = KLT_LAVYaw_238, animate = FALSE, }
anim 240 { anim = KLT_LAVYaw_240, animate = FALSE, }
anim 242 { anim = KLT_LAVYaw_242, animate = FALSE, }
anim 244 { anim = KLT_LAVYaw_244, animate = FALSE, }
anim 246 { anim = KLT_LAVYaw_246, animate = FALSE, }
anim 248 { anim = KLT_LAVYaw_248, animate = FALSE, }
anim 250 { anim = KLT_LAVYaw_250, animate = FALSE, }
anim 252 { anim = KLT_LAVYaw_252, animate = FALSE, }
anim 254 { anim = KLT_LAVYaw_254, animate = FALSE, }
anim 256 { anim = KLT_LAVYaw_256, animate = FALSE, }
anim 258 { anim = KLT_LAVYaw_258, animate = FALSE, }
anim 260 { anim = KLT_LAVYaw_260, animate = FALSE, }
anim 262 { anim = KLT_LAVYaw_262, animate = FALSE, }
anim 264 { anim = KLT_LAVYaw_264, animate = FALSE, }
anim 266 { anim = KLT_LAVYaw_266, animate = FALSE, }
anim 268 { anim = KLT_LAVYaw_268, animate = FALSE, }
anim 270 { anim = KLT_LAVYaw_270, animate = FALSE, }
anim 272 { anim = KLT_LAVYaw_272, animate = FALSE, }
anim 274 { anim = KLT_LAVYaw_274, animate = FALSE, }
anim 276 { anim = KLT_LAVYaw_276, animate = FALSE, }
anim 278 { anim = KLT_LAVYaw_278, animate = FALSE, }
anim 280 { anim = KLT_LAVYaw_280, animate = FALSE, }
anim 282 { anim = KLT_LAVYaw_282, animate = FALSE, }
anim 284 { anim = KLT_LAVYaw_284, animate = FALSE, }
anim 286 { anim = KLT_LAVYaw_286, animate = FALSE, }
anim 288 { anim = KLT_LAVYaw_288, animate = FALSE, }
anim 290 { anim = KLT_LAVYaw_290, animate = FALSE, }
anim 292 { anim = KLT_LAVYaw_292, animate = FALSE, }
anim 294 { anim = KLT_LAVYaw_294, animate = FALSE, }
anim 296 { anim = KLT_LAVYaw_296, animate = FALSE, }
anim 298 { anim = KLT_LAVYaw_298, animate = FALSE, }
anim 300 { anim = KLT_LAVYaw_300, animate = FALSE, }
anim 302 { anim = KLT_LAVYaw_302, animate = FALSE, }
anim 304 { anim = KLT_LAVYaw_304, animate = FALSE, }
anim 306 { anim = KLT_LAVYaw_306, animate = FALSE, }
anim 308 { anim = KLT_LAVYaw_308, animate = FALSE, }
anim 310 { anim = KLT_LAVYaw_310, animate = FALSE, }
anim 312 { anim = KLT_LAVYaw_312, animate = FALSE, }
anim 314 { anim = KLT_LAVYaw_314, animate = FALSE, }
anim 316 { anim = KLT_LAVYaw_316, animate = FALSE, }
anim 318 { anim = KLT_LAVYaw_318, animate = FALSE, }
anim 320 { anim = KLT_LAVYaw_320, animate = FALSE, }
anim 322 { anim = KLT_LAVYaw_322, animate = FALSE, }
anim 324 { anim = KLT_LAVYaw_324, animate = FALSE, }
anim 326 { anim = KLT_LAVYaw_326, animate = FALSE, }
anim 328 { anim = KLT_LAVYaw_328, animate = FALSE, }
anim 330 { anim = KLT_LAVYaw_330, animate = FALSE, }
anim 332 { anim = KLT_LAVYaw_332, animate = FALSE, }
anim 334 { anim = KLT_LAVYaw_334, animate = FALSE, }
anim 336 { anim = KLT_LAVYaw_336, animate = FALSE, }
anim 338 { anim = KLT_LAVYaw_338, animate = FALSE, }
anim 340 { anim = KLT_LAVYaw_340, animate = FALSE, }
anim 342 { anim = KLT_LAVYaw_342, animate = FALSE, }
anim 344 { anim = KLT_LAVYaw_344, animate = FALSE, }
anim 346 { anim = KLT_LAVYaw_346, animate = FALSE, }
anim 348 { anim = KLT_LAVYaw_348, animate = FALSE, }
anim 350 { anim = KLT_LAVYaw_350, animate = FALSE, }
anim 352 { anim = KLT_LAVYaw_352, animate = FALSE, }
anim 354 { anim = KLT_LAVYaw_354, animate = FALSE, }
anim 356 { anim = KLT_LAVYaw_356, animate = FALSE, }
anim 358 { anim = KLT_LAVYaw_358, animate = FALSE, }
anim 360 { anim = KLT_LAVYaw_360, animate = FALSE, }
}
part KLT_LAVInterior
{
category = nodisplay,
parent = Turrent,
model Default { file = KLT_LAVInterior, scale = 1.0, offset = 0 0 0, rotate = 0 0 0, }
lua { create = KLTLAV.InitInterior, init = KLTLAV.InitInterior, }
}
part DoorRearLeft
{
parent = Turrent,
model KLT_RigLink { file = KLT_LAVRigLink, scale = 1.0, offset = 0 0 0, rotate = 0 0 0, }
lua { create = KLTLAV.CreateGunnerDoor, init = KLTLAV.InitGunnerDoor, }
}
part WindowGunnerHatch
{
model windowRL668 { file = KLT_LAVHatch, scale = 1.0, offset = 0 0 0, rotate = 0 0 0, }
anim ClosedToOpen { anim = KLT_LAVHatch, animate = FALSE, }
}
part V300fsvViewports
{
parent = Turrent,
model fsvViewports { file = KLT_LAVViewports, scale = 1.0, offset = 0 0 0, rotate = 0 0 0, }
}
part Radio
{
parent = Turrent,
model sfvadio { file = KLT_LAVRadio, scale = 1.0, offset = 0 0 0, rotate = 0 0 0, }
}
part KLT_LAV90
{
itemType = KLT.LAV90Gun,
specificItem = false,
category = Turrent,
area = SeatRearLeft,
mechanicArea = SeatRearLeft,
parent = Turrent,
container { capacity = 8, conditionAffectsCapacity = false, test = KLTLAV.BreechAccess, }
lua { create = KLTLAV.InitWeapon, init = KLTLAV.InitWeapon, }
}
part KLT_LAVAmmo
{
itemType = KLT.LAV90Rack,
specificItem = false,
category = Turrent,
area = SeatRearLeft,
container { capacity = 160, conditionAffectsCapacity = false, test = KLTLAV.AmmoAccess, }
lua { create = KLTLAV.InitStorage, init = KLTLAV.InitStorage, }
}
}
]=]
@@ -0,0 +1,33 @@
module Base
{
sound HRSys_LAV300_90mm_Fire
{
category = Vehicle,
master = Primary,
is3D = true,
loop = false,
maxInstancesPerEmitter = 8,
clip
{
file = media/sound/HRSys_LAV300_90mm_Fire.wav,
volume = 1.0,
distanceMin = 5,
distanceMax = 100,
}
}
sound HRSys_LAV300_90mm_End
{
category = Vehicle,
master = Primary,
is3D = true,
loop = false,
maxInstancesPerEmitter = 8,
clip
{
file = media/sound/HRSys_LAV300_90mm_End.wav,
volume = 1.0,
distanceMin = 5,
distanceMax = 100,
}
}
}
@@ -0,0 +1,51 @@
module KLT
{
item LAV90Gun
{
DisplayCategory = VehicleMaintenance,
DisplayName = LAV 90 mm cannon,
ItemType = base:normal,
Weight = 30.0,
Icon = M41_76mm,
VehicleType = 2,
ConditionMax = 100,
MechanicsItem = TRUE,
MaxCapacity = 8,
ConditionAffectsCapacity = false,
}
item LAV90Rack
{
DisplayCategory = VehicleMaintenance,
DisplayName = LAV 90 mm ammunition rack,
ItemType = base:normal,
Weight = 15.0,
Icon = CarTrunk,
VehicleType = 2,
ConditionMax = 100,
MechanicsItem = TRUE,
MaxCapacity = 160,
ConditionAffectsCapacity = false,
}
item Shell90AP
{
DisplayCategory = Ammo,
DisplayName = LAV 90 mm AP shell,
ItemType = base:normal,
Weight = 8.0,
Icon = 76mmAmmoAP,
Count = 1,
WorldStaticModel = 76mm_Item,
Tags = base:ammo,
}
item Shell90HE
{
DisplayCategory = Ammo,
DisplayName = LAV 90 mm HE shell,
ItemType = base:normal,
Weight = 8.0,
Icon = 76mmAmmoHE,
Count = 1,
WorldStaticModel = 76mmHE_Item,
Tags = base:ammo,
}
}
@@ -0,0 +1,54 @@
module Base
{
model KLT_LAVTurret
{
mesh = vehicles/KLT_LAVTurret|KLT_LAVTurret,
shader = damn_vehicle_noreflect_shader,
scale = 0.1,
static = FALSE,
boneWeight = body_bone 1.0,
boneWeight = KLT_LAVYaw 1.0,
}
model KLT_LAVInterior
{
mesh = vehicles/KLT_LAVTurret|KLT_LAVInterior,
shader = damn_wheel_shader,
scale = 0.1,
static = FALSE,
texture = Vehicles/Vehicles_84gageV300_Interior,
boneWeight = KLT_LAVYaw 1.0,
}
model KLT_LAVHatch
{
mesh = vehicles/KLT_LAVTurret|KLT_LAVHatch,
shader = damn_vehicle_noreflect_shader,
scale = 0.1,
static = FALSE,
boneWeight = hatch_gunner_bone 1.0,
}
model KLT_LAVViewports
{
mesh = vehicles/KLT_LAVTurret|KLT_LAVViewports,
shader = damn_vehicle_shader,
scale = 0.1,
static = FALSE,
boneWeight = KLT_LAVYaw 1.0,
}
model KLT_LAVRadio
{
mesh = vehicles/KLT_LAVTurret|KLT_LAVRadio,
shader = damn_wheel_shader,
scale = 0.1,
static = FALSE,
texture = Vehicles/Vehicles_84gageV300_Interior,
boneWeight = KLT_LAVYaw 1.0,
}
model KLT_LAVRigLink
{
mesh = vehicles/KLT_LAVTurret|KLT_LAVRigLink,
shader = damn_vehicle_noreflect_shader,
scale = 0.1,
static = FALSE,
boneWeight = body_bone 1.0,
}
}
@@ -0,0 +1,10 @@
name=HRSys Functional Turrets - LAV-300
id=hrsys_functionalturrets_lav300
description=Adds a functioning turret and gunner-operated 90 mm cannon with AP and HE ammunition to KI5's '84 Cadillac Gage LAV-300 FSV.
author=Riggs0
category=vehicle
require=\damnlib,\84gageV300,\Military_Tool_Kit
versionMin=42.20.0
modversion=0.1.4
icon=icon.png
poster=preview.png
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

@@ -0,0 +1,5 @@
{
"IGUI_VehiclePartKLT_LAV90": "LAV 90 mm cannon (breech)",
"IGUI_VehiclePartKLT_LAVAmmo": "LAV 90 mm ammunition storage",
"IGUI_VehiclePartTurrent": "Turret"
}
@@ -0,0 +1,6 @@
{
"ItemName_KLT.LAV90Gun": "LAV 90 mm cannon",
"ItemName_KLT.LAV90Rack": "LAV 90 mm ammunition rack",
"ItemName_KLT.Shell90AP": "LAV 90 mm AP shell",
"ItemName_KLT.Shell90HE": "LAV 90 mm HE shell"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

@@ -0,0 +1,10 @@
name=HRSys Functional Turrets - LAV-300
id=hrsys_functionalturrets_lav300
description=Adds a functioning turret and gunner-operated 90 mm cannon with AP and HE ammunition to KI5's '84 Cadillac Gage LAV-300 FSV.
author=Riggs0
category=vehicle
require=\damnlib,\84gageV300,\Military_Tool_Kit
versionMin=42.20.0
modversion=0.1.4
icon=icon.png
poster=preview.png
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

+6
View File
@@ -0,0 +1,6 @@
version=1
id=3828342866
title=FunctionalTurrets
description=WIP WIP
tags=Build 42;Vehicles
visibility=friendsOnly