#include "ui/ui_helpers.lua" #include "ui/ui_extensions.lua" #include "script/common.lua" #include "input.lua" camPan = 0 camPitch = 45 camDist = 10 openState = 0 open = 0 attachmentPoint = nil attachmentShape = nil groundContact = false underWaterTime = 0 cooldown = 0.0 fireOffset = 1 function init() active = false body = FindBody("ball_main_body") shapes = GetBodyShapes(body) for i=1, #shapes do SetTag(shapes[i], "autobreak") end xDir, zDir = Vec(1, 0, 0), Vec(0, 0, 1) openSnd = LoadSound("open.ogg") closeSnd = LoadSound("close.ogg") shootSnd = LoadSound("silenced0.ogg") rocketSnd = LoadSound("tools/launcher0.ogg") attachSnd = LoadSound("tools/wire-attach0.ogg") detachSnd = LoadSound("tools/wire-detach0.ogg") swooshSnd = LoadSound("tools/sledge0.ogg") rollLoop = LoadLoop("vehicle/gravel-loop.ogg") reticle = LoadSprite("gfx/reticle4.png") AnimInit(body) end function deactivate() active = false RemoveTag(body, "bounded") local pt = GetPlayerTransform() pt.pos = TransformToParentPoint(pt, Vec(0, -2.5, 3)) SetPlayerTransform(pt) InputResetOnTransition() end function tick(dt) if LastInputDevice() == UI_DEVICE_GAMEPAD then input.layout = "gamepad" else input.layout = "keyboard" end if InputPressed("interact") and not GetBool("game.disableinteract") then if active then deactivate() else if GetPlayerInteractBody() == body then active = true local pp = GetPlayerTransform().pos local bp = GetBodyTransform(body).pos local v = VecSub(pp,bp) camPan = math.atan2(v[3], -v[1])*180/math.pi + 90 camPitch = 30 SetTag(body, "bounded") end end end -- process interact tag local inside = active local interactable = not GetBool("game.disableinteract") local hasTag = HasTag(body, "interact") if not interactable and hasTag then RemoveTag(body, "interact") end if interactable then if not inside and not hasTag then SetTag(body, "interact", "loc@WRECKINGBALL_USE") end if inside and hasTag then RemoveTag(body, "interact") end end if active then SetBool("game.flashlight.enabled", false) -- disable flashlight only if we inside robot local bt = GetBodyTransform(body) SetPlayerTransform( Transform(VecAdd(bt.pos, Vec(0, 2, 0)), QuatEuler(0, 180+camPan, 0)) ) SetBool("game.player.disableinput", true) camera(dt) if InputPressed(input.keys[input.layout].transform) then InputResetOnTransition() openState = 1-openState if openState == 1 then PlaySound(openSnd, GetBodyTransform(body).pos) else PlaySound(closeSnd, GetBodyTransform(body).pos) end end if InputPressed(input.keys[input.layout].jump) and groundContact then local vel = GetBodyVelocity(body) vel = VecAdd(vel, Vec(0, 6, 0)) SetBodyVelocity(body, vel) end local ct = GetCameraTransform() local pos = ct.pos local dir = TransformToParentVec(ct, Vec(0, 0, -1)) QueryRejectBody(body) QueryRequire("physical") local cd = VecDot(dir, VecSub(GetBodyTransform(body).pos, ct.pos)) local origin = VecAdd(pos, VecScale(dir, cd)) local hit, dist, n, hitShape = QueryRaycast(origin, dir, 200) local hitPos = VecAdd(origin, VecScale(dir, dist)) if openState == 1 then if not GetBool("game.map.enabled") then DrawSprite(reticle, Transform(hitPos, ct.rot), 1, 0.5) end local bt = GetBodyTransform(body) local pos = TransformToParentPoint(bt, Vec(-1.3, 0, -1.4*fireOffset)) local dir = VecNormalize(VecSub(hitPos, pos)) if InputDown(input.keys[input.layout].fire) and cooldown <= 0 then Shoot(pos, dir, "bullet", 0.6) PlaySound(shootSnd, pos) PointLight(pos, 1, 0.8, 0.6, 10) cooldown = 0.09 fireOffset = -fireOffset end if InputPressed(input.keys[input.layout].altfire) then Shoot(pos, dir, "rocket") PlaySound(rocketSnd, pos) fireOffset = -fireOffset end attachmentPoint = nil attachmentShape = nil else if InputPressed(input.keys[input.layout].fire) then if attachmentPoint then attachmentPoint = nil attachmentShape = nil PlaySound(detachSnd) else if hit then attachmentPoint = TransformToLocalPoint(GetShapeWorldTransform(hitShape), hitPos) attachmentShape = hitShape PlaySound(attachSnd) end end end if attachmentPoint and InputPressed(input.keys[input.layout].altfire) then local wap = TransformToParentPoint(GetShapeWorldTransform(attachmentShape), attachmentPoint) local d = VecNormalize(VecSub(wap, GetBodyTransform(body).pos)) local b = GetShapeBody(attachmentShape) if IsBodyDynamic(b) then local t = clamp(GetBodyMass(body) / (GetBodyMass(body) + GetBodyMass(b)), 0.2, 0.8) SetBodyVelocity(b, VecScale(d, -25*t)) end SetBodyVelocity(body, VecScale(d, 25)) attachmentPoint = nil attachmentShape = nil PlaySound(detachSnd) PlaySound(swooshSnd) end end cooldown = math.max(0.0, cooldown - dt) else attachmentPoint = nil attachmentShape = nil end if attachmentPoint then local wap = TransformToParentPoint(GetShapeWorldTransform(attachmentShape), attachmentPoint) DrawLine(GetBodyTransform(body).pos, wap, 0, 0, 0, 1) end local up = TransformToParentVec(GetBodyTransform(body), Vec(0, 1, 0)) if openState == 0 or up[2] > 0.7 then open = open + clamp(openState-open, -dt*2, dt*2) end AnimTick(body, open, dt) end function camera(dt) if GetBool("game.map.enabled") then return end camPan = camPan - InputValue("camerax")*100 camPitch = clamp(camPitch + InputValue("cameray")*100, -20, 80) camDist = clamp(camDist * (1.0 - InputValue("mousewheel")*0.2), 5.0, 20.0) local target = GetBodyTransform(body).pos local d = QuatRotateVec(QuatEuler(camPitch, camPan, 0), Vec(0,0,-1)) local cd = camDist QueryRejectBody(body) QueryRequire("physical static") local hit, dist = QueryRaycast(target, d, cd) if hit then --Make a second raycast to see if it's just rubble around the sphere QueryRejectBody(body) QueryRequire("physical static") if QueryRaycast(VecAdd(target, VecScale(d, 4)), d, cd-4) then cd = dist-0.1 end end local camPos = VecAdd(target, VecScale(d, cd)) target = VecAdd(target, Vec(0, 2.0, 0)) SetCameraTransform(Transform(camPos, QuatLookAt(camPos, target))) local qpan = QuatEuler(0, camPan, 0) xDir = QuatRotateVec(qpan, Vec(-1, 0, 0)) zDir = QuatRotateVec(qpan, Vec(0, 0, -1)) end function update(dt) local bodyTransform = GetBodyTransform(body) local pos = bodyTransform.pos --Maintain some distance above ground QueryRejectBody(body) QueryRequire("physical large") local targetHeight = 1.4 + 0.2*openState local raycastHeight = targetHeight - 0.2 groundContact, dist = QueryRaycast(VecSub(pos, Vec(0, raycastHeight, 0)), Vec(0,-1,0), 2.0 - raycastHeight) if groundContact then local diff = targetHeight - (dist + raycastHeight) ConstrainVelocity(body, 0, pos, Vec(0,1,0), diff*10, 0, 7000) if openState == 0 then local vol = clamp(VecLength(GetBodyVelocity(body))/12, 0, 1) PlayLoop(rollLoop, pos, vol) end end local speed = 4 if openState == 0 then speed = 6 if InputDown(input.keys[input.layout].boost) then speed = 12 end end local xv, zv = 0, 0 if active then if InputDown(input.keys[input.layout].up) then zv = zv - speed end if InputDown(input.keys[input.layout].down) then zv = zv + speed end if InputDown(input.keys[input.layout].left) then xv = xv - speed end if InputDown(input.keys[input.layout].right) then xv = xv + speed end local l = math.sqrt(zv*zv + xv*xv) if l > speed then xv = xv * speed / l zv = zv * speed / l zv = zv * speed / l end local inWater, depth = IsPointInWater(pos) if inWater then if depth > targetHeight then underWaterTime = underWaterTime + dt; else underWaterTime = 0; end if underWaterTime > 0.2 then deactivate() end end end if openState == 1 then local f = 10000 local up, d --Wiggle a bit when walking local dx = math.sin(animAng*2.5)*0.1 local dz = math.cos(animAng)*0.1 --Upright around z up = VecCross(TransformToParentVec(bodyTransform, VecNormalize(Vec(dz, 1, dz))), Vec(0, 1, 0)) d = VecDot(up, zDir) ConstrainAngularVelocity(body, 0, zDir, d*10, -f , f) --Upright around y up = VecCross(TransformToParentVec(bodyTransform, Vec(1, 0, 0)), zDir) d = up[2] ConstrainAngularVelocity(body, 0, Vec(0,1,0), d*10, -f , f) --Upright around x up = VecCross(TransformToParentVec(bodyTransform, VecNormalize(Vec(dx, 1, dz))), Vec(0, 1, 0)) d = VecDot(up, xDir) ConstrainAngularVelocity(body, 0, xDir, d*10, -f , f) local idle = 7000 local xmi, xma, zmi, zma = -idle, idle, -idle, idle ConstrainVelocity(body, 0, pos, xDir, xv, xmi, xma) ConstrainVelocity(body, 0, pos, zDir, zv, zmi, zma) else local idle = 1700 local xmi, xma, zmi, zma = -idle, idle, -idle, idle local pos = GetBodyTransform(body).pos local imp = 7000 if xv > 0 then xma = imp end if xv < 0 then xmi = -imp end if zv > 0 then zma = imp end if zv < 0 then zmi = -imp end ConstrainVelocity(body, 0, pos, xDir, xv, xmi, xma) ConstrainVelocity(body, 0, pos, zDir, zv, zmi, zma) local bv = GetBodyVelocity(body) local xav = VecDot(bv, zDir) * 1.3 local zav = -VecDot(bv, xDir) * 1.3 if math.abs(zv) > 0 then xav = zv*1.3 end if math.abs(xv) > 0 then zav = -xv*1.3 end ConstrainAngularVelocity(body, 0, xDir, xav, zmi, zma) ConstrainAngularVelocity(body, 0, Vec(0,1,0), 0, -idle, idle) ConstrainAngularVelocity(body, 0, zDir, zav, -xma, -xmi) end end function draw() if GetBool("game.map.enabled") or not active then return end if openState == 0 then UiPush() UiTranslate(UiCenter(), UiMiddle()) UiAlign("center middle") UiImage("ui/hud/crosshair-dot.png") UiPop() end if active and not GetBool("game.windows.weather.active") then drawHints() end end function drawHints() info = {} if openState == 0 then for key,val in ipairs(input.keys.orderball) do getBind = GetString("game.input."..input.keys[input.layout][val]) if getBind == "" then getBind = input.keys[input.layout][val] end local inputKey = string.upper(getBind) local desc = GetTranslatedStringByKey(input.keys.descball[val]) key = val if input.layout == "gamepad" then inputKey = "[[" .. input.keys[input.layout][val] .. ";iconsize=42,42]]" end info[#info+1] = {inputKey, desc} end else for key,val in ipairs(input.keys.orderrobot) do getBind = GetString("game.input."..input.keys[input.layout][val]) if getBind == "" then getBind = input.keys[input.layout][val] end local inputKey = string.upper(getBind) local desc = GetTranslatedStringByKey(input.keys.descrobot[val]) key = val if input.layout == "gamepad" then inputKey = "[[" .. input.keys[input.layout][val] .. ";iconsize=42,42]]" end info[#info+1] = {inputKey, desc} end end UiPush() UiAlign("top left") local kw = 0 local vw = 0 for i=1, #info do UiFont("bold.ttf", 27) local w0 = 75 w0 = UiGetTextSize(info[i][1]) UiFont("regular.ttf", 27) local w1 = UiGetTextSize(info[i][2]) kw = math.max(kw,w0) vw = math.max(vw,w1) end local iconSize = 42 local w = kw + vw + 60 local h = #info * (iconSize + 6) + 20 SetInt("game.hud.hintH", h + GetInt("game.hud.hintH")) UiTranslate(UiWidth()-w-20, UiHeight()-h-20) -- because I don't know how big the official vehicle UI will be UiColor(0,0,0,0.5) UiImageBox("ui/common/box-solid-6.png", w, h, 6, 6) UiTranslate(kw + 30, 10) UiColor(1,1,1) for i=1, #info do local key = info[i][1] local func = info[i][2] UiPush() UiTranslate(0, iconSize / 2) UiFont("bold.ttf", 27) UiAlign("right middle") UiText(key) UiPop() UiPush() UiTranslate(10, iconSize / 2) UiFont("regular.ttf", 27) UiAlign("left middle") UiText(func) UiPop() UiTranslate(0, iconSize+6) end UiPop() end ---------------------------------------------------------------------------------------------------- -- ANIMATION ---------------------------------------------------------------------------------------------------- BallMiddleBodyTag = "ball_middle_body" BallUpperBodyTag = "ball_upper_body" BallBottomBodyTag = "ball_bottom_body" BallRightHandTag = "r_hand" BallLeftHandTag = "l_hand" BallRightArmTag = "r_arm" BallLeftArmTag = "l_arm" BallRightCapTag = "r_cap" BallLeftCapTag = "l_cap" RightForwardLegTag = "bottom_right_forward_leg" RightRearLegTag = "bottom_right_rear_leg" LeftForwardLegTag = "bottom_left_forward_leg" LeftRearLegTag = "bottom_left_rear_leg" animParts = { } animAng = 0 function setBodyVisible(b, v) local s = GetBodyShapes(b) for i=1, #s do if v then RemoveTag(s[i], "invisible") else SetTag(s[i], "invisible") end end end function AnimInit(body) local bt = GetBodyTransform(body) local tags = {BallMiddleBodyTag, BallUpperBodyTag, BallBottomBodyTag, BallRightHandTag, BallLeftHandTag, BallRightArmTag, BallLeftArmTag, BallRightCapTag, BallLeftCapTag, RightForwardLegTag, RightRearLegTag, LeftForwardLegTag, LeftRearLegTag} setBodyVisible(body, false) for i=1, #tags do local tag = tags[i] local b = FindBody(tag) local t = GetBodyTransform(b) setBodyVisible(b, true) animParts[tag] = { handle = b, localTransform = TransformToLocalTransform(bt, t), ang = i*1.5 } end local lx = 0.4 local lz = 0.4 animation = { l_cap = { { 0.0, 0, 0, 0, 0, 0, 0 }, { 0.3, 0, 0, 0, -97, 0, 0 }, }, r_cap = { { 0.0, 0, 0, 0, 0, 0, 0 }, { 0.3, 0, 0, 0, 97, 0, 0 }, }, l_arm = { { 0.0, 0, 0, 0, 0, 0, 0}, { 0.5, -1.8, 0, 0, 0, 0, 0}, { 1.0, -1.8, 0, 0, 0, -90, 0}, }, l_hand = { { 0.0, 0, 0, 0, 0, 0, 0}, { 0.5, 0, 0, 1.8, 0, 0, 0}, }, r_arm = { { 0.0, 0, 0, 0, 0, 0, 0}, { 0.5, -1.8, 0, 0, 0, 0, 0}, { 1.0, -1.8, 0, 0, 0, 90, 0}, }, r_hand = { { 0.0, 0, 0, 0, 0, 0, 0}, { 0.5, 0, 0, -1.8, 0, 0, 0}, }, bottom_left_rear_leg = { { 0.0, 0, 0, 0, 0, 0, 0}, { 1.0, lx, -0.5, lz, 0, 0, 0}, }, bottom_left_forward_leg = { { 0.0, 0, 0, 0, 0, 0, 0}, { 1.0, -lx, -0.5, lz, 0, 0, 0}, }, bottom_right_rear_leg = { { 0.0, 0, 0, 0, 0, 0, 0}, { 1.0, 0.3, -0.5, -lz, 0, 0, 0}, }, bottom_right_forward_leg = { { 0.0, 0, 0, 0, 0, 0, 0}, { 1.0, -0.3, -0.5, -lz, 0, 0, 0}, }, } end function getAnimTransform(tag, open) local tr = Transform() local a = animation[tag] local t = 0 if a then local before = 1 local after = #a for i=1, #a do if open >= a[i][1] then before = i end end for i=#a,1,-1 do if open <= a[i][1] then after = i end end local p = a[before] local n = a[after] local t = (open-p[1]) / (n[1]-p[1]+0.0001) tr.pos = Vec(p[2]*(1-t)+n[2]*t, p[3]*(1-t)+n[3]*t, p[4]*(1-t)+n[4]*t) tr.rot = QuatEuler(p[5]*(1-t)+n[5]*t, p[6]*(1-t)+n[6]*t, p[7]*(1-t)+n[7]*t) end return tr end function AnimTick(body, open, dt) local av = VecLength(GetBodyVelocity(body)) - 2.0*GetBodyAngularVelocity(body)[2] animAng = animAng - clamp(av * 0.08, -0.5, 0.5) local bt = GetBodyTransform(body) for tag, part in pairs(animParts) do local lt = TransformToParentTransform(part.localTransform, getAnimTransform(tag, open)) local t = TransformToParentTransform(bt, lt) if openState > 0 and string.find(tag, "leg") then local fwd = GetBodyVelocityAtPos(body, t.pos) local l = VecLength(fwd) if l > 0.1 then fwd = VecNormalize(fwd) local scale = open * 0.25 * clamp((l-0.1)*0.5, 0.0, 1.0) t.pos = VecAdd(t.pos, VecScale(fwd, math.cos(animAng + part.ang)*scale)) t.pos = VecAdd(t.pos, Vec(0, math.max(0, math.sin(animAng + part.ang))*scale, 0)) end end SetBodyTransform(part.handle, t) end end