diff --git a/Art/Release/README.md b/Art/Release/README.md new file mode 100644 index 0000000..8a001d1 --- /dev/null +++ b/Art/Release/README.md @@ -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. diff --git a/Art/Release/artwork.json b/Art/Release/artwork.json new file mode 100644 index 0000000..36947fe --- /dev/null +++ b/Art/Release/artwork.json @@ -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 +} \ No newline at end of file diff --git a/Art/Release/compose_release_art.py b/Art/Release/compose_release_art.py new file mode 100644 index 0000000..35ac286 --- /dev/null +++ b/Art/Release/compose_release_art.py @@ -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) diff --git a/Art/Release/ferret-release-render.blend b/Art/Release/ferret-release-render.blend new file mode 100644 index 0000000..3b974ed Binary files /dev/null and b/Art/Release/ferret-release-render.blend differ diff --git a/Art/Release/ferret-turret-render.png b/Art/Release/ferret-turret-render.png new file mode 100644 index 0000000..dbc2e6b Binary files /dev/null and b/Art/Release/ferret-turret-render.png differ diff --git a/Art/Release/icon-master.png b/Art/Release/icon-master.png new file mode 100644 index 0000000..ea0ca9e Binary files /dev/null and b/Art/Release/icon-master.png differ diff --git a/Art/Release/icon.png b/Art/Release/icon.png new file mode 100644 index 0000000..af65a58 Binary files /dev/null and b/Art/Release/icon.png differ diff --git a/Art/Release/lav-release-render.blend b/Art/Release/lav-release-render.blend new file mode 100644 index 0000000..5e7d9bf Binary files /dev/null and b/Art/Release/lav-release-render.blend differ diff --git a/Art/Release/lav-turret-render.png b/Art/Release/lav-turret-render.png new file mode 100644 index 0000000..9d39558 Binary files /dev/null and b/Art/Release/lav-turret-render.png differ diff --git a/Art/Release/preview-master.png b/Art/Release/preview-master.png new file mode 100644 index 0000000..245ff94 Binary files /dev/null and b/Art/Release/preview-master.png differ diff --git a/Art/Release/preview.png b/Art/Release/preview.png new file mode 100644 index 0000000..9a9be03 Binary files /dev/null and b/Art/Release/preview.png differ diff --git a/Art/Release/render_release_turrets.py b/Art/Release/render_release_turrets.py new file mode 100644 index 0000000..e141310 --- /dev/null +++ b/Art/Release/render_release_turrets.py @@ -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) diff --git a/Contents/mods/FunctionalTurretsFerret/42.20/icon.png b/Contents/mods/FunctionalTurretsFerret/42.20/icon.png new file mode 100644 index 0000000..af65a58 Binary files /dev/null and b/Contents/mods/FunctionalTurretsFerret/42.20/icon.png differ diff --git a/Contents/mods/FunctionalTurretsFerret/42.20/media/lua/client/KFT_FerretClient.lua b/Contents/mods/FunctionalTurretsFerret/42.20/media/lua/client/KFT_FerretClient.lua new file mode 100644 index 0000000..41dbfe0 --- /dev/null +++ b/Contents/mods/FunctionalTurretsFerret/42.20/media/lua/client/KFT_FerretClient.lua @@ -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) diff --git a/Contents/mods/FunctionalTurretsFerret/42.20/media/lua/server/KFT_FerretServer.lua b/Contents/mods/FunctionalTurretsFerret/42.20/media/lua/server/KFT_FerretServer.lua new file mode 100644 index 0000000..1f766aa --- /dev/null +++ b/Contents/mods/FunctionalTurretsFerret/42.20/media/lua/server/KFT_FerretServer.lua @@ -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 diff --git a/Contents/mods/FunctionalTurretsFerret/42.20/media/lua/shared/KFT_Ferret.lua b/Contents/mods/FunctionalTurretsFerret/42.20/media/lua/shared/KFT_Ferret.lua new file mode 100644 index 0000000..3ab7679 --- /dev/null +++ b/Contents/mods/FunctionalTurretsFerret/42.20/media/lua/shared/KFT_Ferret.lua @@ -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) diff --git a/Contents/mods/FunctionalTurretsFerret/42.20/media/lua/shared/KFT_FerretPatch.lua b/Contents/mods/FunctionalTurretsFerret/42.20/media/lua/shared/KFT_FerretPatch.lua new file mode 100644 index 0000000..366b1dd --- /dev/null +++ b/Contents/mods/FunctionalTurretsFerret/42.20/media/lua/shared/KFT_FerretPatch.lua @@ -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, } + } +} +]=] diff --git a/Contents/mods/FunctionalTurretsFerret/42.20/media/scripts/HRSys_Ferret_Browning_Sounds.txt b/Contents/mods/FunctionalTurretsFerret/42.20/media/scripts/HRSys_Ferret_Browning_Sounds.txt new file mode 100644 index 0000000..abf0923 --- /dev/null +++ b/Contents/mods/FunctionalTurretsFerret/42.20/media/scripts/HRSys_Ferret_Browning_Sounds.txt @@ -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, + } + } +} diff --git a/Contents/mods/FunctionalTurretsFerret/42.20/media/scripts/KFT_FerretModels.txt b/Contents/mods/FunctionalTurretsFerret/42.20/media/scripts/KFT_FerretModels.txt new file mode 100644 index 0000000..82d4da5 --- /dev/null +++ b/Contents/mods/FunctionalTurretsFerret/42.20/media/scripts/KFT_FerretModels.txt @@ -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, + } +} diff --git a/Contents/mods/FunctionalTurretsFerret/42.20/mod.info b/Contents/mods/FunctionalTurretsFerret/42.20/mod.info new file mode 100644 index 0000000..406e8ab --- /dev/null +++ b/Contents/mods/FunctionalTurretsFerret/42.20/mod.info @@ -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 diff --git a/Contents/mods/FunctionalTurretsFerret/42.20/preview.png b/Contents/mods/FunctionalTurretsFerret/42.20/preview.png new file mode 100644 index 0000000..9a9be03 Binary files /dev/null and b/Contents/mods/FunctionalTurretsFerret/42.20/preview.png differ diff --git a/Contents/mods/FunctionalTurretsFerret/common/media/lua/shared/Translate/EN/IG_UI.json b/Contents/mods/FunctionalTurretsFerret/common/media/lua/shared/Translate/EN/IG_UI.json new file mode 100644 index 0000000..ba12043 --- /dev/null +++ b/Contents/mods/FunctionalTurretsFerret/common/media/lua/shared/Translate/EN/IG_UI.json @@ -0,0 +1,4 @@ +{ + "IGUI_VehiclePartKFT_FerretMG": "Ferret Browning (.30-06)", + "IGUI_VehiclePartTurrent": "Turret" +} \ No newline at end of file diff --git a/Contents/mods/FunctionalTurretsFerret/common/media/models_X/vehicles/KFT_FerretTurret.fbx b/Contents/mods/FunctionalTurretsFerret/common/media/models_X/vehicles/KFT_FerretTurret.fbx new file mode 100644 index 0000000..75f1787 Binary files /dev/null and b/Contents/mods/FunctionalTurretsFerret/common/media/models_X/vehicles/KFT_FerretTurret.fbx differ diff --git a/Contents/mods/FunctionalTurretsFerret/common/media/sound/HRSys_Ferret_Browning_End.wav b/Contents/mods/FunctionalTurretsFerret/common/media/sound/HRSys_Ferret_Browning_End.wav new file mode 100644 index 0000000..2c39bbb Binary files /dev/null and b/Contents/mods/FunctionalTurretsFerret/common/media/sound/HRSys_Ferret_Browning_End.wav differ diff --git a/Contents/mods/FunctionalTurretsFerret/common/media/sound/HRSys_Ferret_Browning_Fire.wav b/Contents/mods/FunctionalTurretsFerret/common/media/sound/HRSys_Ferret_Browning_Fire.wav new file mode 100644 index 0000000..504ae02 Binary files /dev/null and b/Contents/mods/FunctionalTurretsFerret/common/media/sound/HRSys_Ferret_Browning_Fire.wav differ diff --git a/Contents/mods/FunctionalTurretsFerret/icon.png b/Contents/mods/FunctionalTurretsFerret/icon.png new file mode 100644 index 0000000..af65a58 Binary files /dev/null and b/Contents/mods/FunctionalTurretsFerret/icon.png differ diff --git a/Contents/mods/FunctionalTurretsFerret/mod.info b/Contents/mods/FunctionalTurretsFerret/mod.info new file mode 100644 index 0000000..406e8ab --- /dev/null +++ b/Contents/mods/FunctionalTurretsFerret/mod.info @@ -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 diff --git a/Contents/mods/FunctionalTurretsFerret/preview.png b/Contents/mods/FunctionalTurretsFerret/preview.png new file mode 100644 index 0000000..9a9be03 Binary files /dev/null and b/Contents/mods/FunctionalTurretsFerret/preview.png differ diff --git a/Contents/mods/FunctionalTurretsLAV300/42.20/icon.png b/Contents/mods/FunctionalTurretsLAV300/42.20/icon.png new file mode 100644 index 0000000..af65a58 Binary files /dev/null and b/Contents/mods/FunctionalTurretsLAV300/42.20/icon.png differ diff --git a/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/client/KLT_LAVClient.lua b/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/client/KLT_LAVClient.lua new file mode 100644 index 0000000..deee367 --- /dev/null +++ b/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/client/KLT_LAVClient.lua @@ -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) diff --git a/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/client/TimedActions/KLT_ReloadLAV.lua b/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/client/TimedActions/KLT_ReloadLAV.lua new file mode 100644 index 0000000..fc07a65 --- /dev/null +++ b/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/client/TimedActions/KLT_ReloadLAV.lua @@ -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) diff --git a/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/server/KLT_LAVServer.lua b/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/server/KLT_LAVServer.lua new file mode 100644 index 0000000..85ed52d --- /dev/null +++ b/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/server/KLT_LAVServer.lua @@ -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 diff --git a/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/shared/KLT_LAV.lua b/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/shared/KLT_LAV.lua new file mode 100644 index 0000000..827c42f --- /dev/null +++ b/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/shared/KLT_LAV.lua @@ -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) diff --git a/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/shared/KLT_LAVPatch.lua b/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/shared/KLT_LAVPatch.lua new file mode 100644 index 0000000..1deb97e --- /dev/null +++ b/Contents/mods/FunctionalTurretsLAV300/42.20/media/lua/shared/KLT_LAVPatch.lua @@ -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, } + } +} +]=] diff --git a/Contents/mods/FunctionalTurretsLAV300/42.20/media/scripts/HRSys_LAV300_90mm_Sounds.txt b/Contents/mods/FunctionalTurretsLAV300/42.20/media/scripts/HRSys_LAV300_90mm_Sounds.txt new file mode 100644 index 0000000..fb20450 --- /dev/null +++ b/Contents/mods/FunctionalTurretsLAV300/42.20/media/scripts/HRSys_LAV300_90mm_Sounds.txt @@ -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, + } + } +} diff --git a/Contents/mods/FunctionalTurretsLAV300/42.20/media/scripts/KLT_LAVItems.txt b/Contents/mods/FunctionalTurretsLAV300/42.20/media/scripts/KLT_LAVItems.txt new file mode 100644 index 0000000..f61c6c9 --- /dev/null +++ b/Contents/mods/FunctionalTurretsLAV300/42.20/media/scripts/KLT_LAVItems.txt @@ -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, + } +} diff --git a/Contents/mods/FunctionalTurretsLAV300/42.20/media/scripts/KLT_LAVModels.txt b/Contents/mods/FunctionalTurretsLAV300/42.20/media/scripts/KLT_LAVModels.txt new file mode 100644 index 0000000..1fe0de3 --- /dev/null +++ b/Contents/mods/FunctionalTurretsLAV300/42.20/media/scripts/KLT_LAVModels.txt @@ -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, + } +} diff --git a/Contents/mods/FunctionalTurretsLAV300/42.20/mod.info b/Contents/mods/FunctionalTurretsLAV300/42.20/mod.info new file mode 100644 index 0000000..6f54322 --- /dev/null +++ b/Contents/mods/FunctionalTurretsLAV300/42.20/mod.info @@ -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 diff --git a/Contents/mods/FunctionalTurretsLAV300/42.20/preview.png b/Contents/mods/FunctionalTurretsLAV300/42.20/preview.png new file mode 100644 index 0000000..9a9be03 Binary files /dev/null and b/Contents/mods/FunctionalTurretsLAV300/42.20/preview.png differ diff --git a/Contents/mods/FunctionalTurretsLAV300/common/media/lua/shared/Translate/EN/IG_UI.json b/Contents/mods/FunctionalTurretsLAV300/common/media/lua/shared/Translate/EN/IG_UI.json new file mode 100644 index 0000000..1c40115 --- /dev/null +++ b/Contents/mods/FunctionalTurretsLAV300/common/media/lua/shared/Translate/EN/IG_UI.json @@ -0,0 +1,5 @@ +{ + "IGUI_VehiclePartKLT_LAV90": "LAV 90 mm cannon (breech)", + "IGUI_VehiclePartKLT_LAVAmmo": "LAV 90 mm ammunition storage", + "IGUI_VehiclePartTurrent": "Turret" +} \ No newline at end of file diff --git a/Contents/mods/FunctionalTurretsLAV300/common/media/lua/shared/Translate/EN/ItemName.json b/Contents/mods/FunctionalTurretsLAV300/common/media/lua/shared/Translate/EN/ItemName.json new file mode 100644 index 0000000..74a0d58 --- /dev/null +++ b/Contents/mods/FunctionalTurretsLAV300/common/media/lua/shared/Translate/EN/ItemName.json @@ -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" +} \ No newline at end of file diff --git a/Contents/mods/FunctionalTurretsLAV300/common/media/models_X/vehicles/KLT_LAVTurret.fbx b/Contents/mods/FunctionalTurretsLAV300/common/media/models_X/vehicles/KLT_LAVTurret.fbx new file mode 100644 index 0000000..c6af803 Binary files /dev/null and b/Contents/mods/FunctionalTurretsLAV300/common/media/models_X/vehicles/KLT_LAVTurret.fbx differ diff --git a/Contents/mods/FunctionalTurretsLAV300/common/media/sound/HRSys_LAV300_90mm_End.wav b/Contents/mods/FunctionalTurretsLAV300/common/media/sound/HRSys_LAV300_90mm_End.wav new file mode 100644 index 0000000..dc7c89e Binary files /dev/null and b/Contents/mods/FunctionalTurretsLAV300/common/media/sound/HRSys_LAV300_90mm_End.wav differ diff --git a/Contents/mods/FunctionalTurretsLAV300/common/media/sound/HRSys_LAV300_90mm_Fire.wav b/Contents/mods/FunctionalTurretsLAV300/common/media/sound/HRSys_LAV300_90mm_Fire.wav new file mode 100644 index 0000000..2451959 Binary files /dev/null and b/Contents/mods/FunctionalTurretsLAV300/common/media/sound/HRSys_LAV300_90mm_Fire.wav differ diff --git a/Contents/mods/FunctionalTurretsLAV300/icon.png b/Contents/mods/FunctionalTurretsLAV300/icon.png new file mode 100644 index 0000000..af65a58 Binary files /dev/null and b/Contents/mods/FunctionalTurretsLAV300/icon.png differ diff --git a/Contents/mods/FunctionalTurretsLAV300/mod.info b/Contents/mods/FunctionalTurretsLAV300/mod.info new file mode 100644 index 0000000..6f54322 --- /dev/null +++ b/Contents/mods/FunctionalTurretsLAV300/mod.info @@ -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 diff --git a/Contents/mods/FunctionalTurretsLAV300/preview.png b/Contents/mods/FunctionalTurretsLAV300/preview.png new file mode 100644 index 0000000..9a9be03 Binary files /dev/null and b/Contents/mods/FunctionalTurretsLAV300/preview.png differ diff --git a/preview.png b/preview.png new file mode 100644 index 0000000..9a9be03 Binary files /dev/null and b/preview.png differ diff --git a/workshop.txt b/workshop.txt new file mode 100644 index 0000000..6ffbdb6 --- /dev/null +++ b/workshop.txt @@ -0,0 +1,6 @@ +version=1 +id=3828342866 +title=FunctionalTurrets +description=WIP WIP +tags=Build 42;Vehicles +visibility=friendsOnly