Almost finished garage!

This commit is contained in:
FerrDev 2026-05-10 21:24:06 +02:00
parent 3ff5337437
commit 05c424e6c1
18 changed files with 874 additions and 496 deletions

2
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,2 @@
{
}

View File

@ -34,6 +34,7 @@
"ServerScriptService": {
"$path": "src/server"
},
"StarterPlayer": {
"StarterPlayerScripts": {
"$path": "src/client"

4
luau-lsp.json Normal file
View File

@ -0,0 +1,4 @@
{
"luau-lsp.sourcemap.enabled": true,
"luau-lsp.sourcemap.file": "sourcemap.json"
}

View File

@ -2,33 +2,33 @@ local DataManager = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Template = require(script.Parent.Template)
local DataTypes = require(ReplicatedStorage.Data.DataTypes)
local OrderedDataStore = require("./OrderedDatastoreHandler")
local ValueNames = require("./ValueNames")
local DataStoreNames = require("./DataStoreNames")
local ProfileStore
local ProfileStore
-- Store profiles from ProfileStore
DataManager.Profiles = {}
local synced = {}
function DataManager.getKey(player : Player,key : string)
function DataManager.getKey(player: Player, key: string)
return "PLAYER_" .. player.UserId .. "_" .. key
end
function DataManager.AddValue(player : Player,key : string,num)
local value = DataManager.GetValue(player,key)
function DataManager.AddValue(player: Player, key: string, num)
local value = DataManager.GetValue(player, key)
if not value then
return
end
DataManager.SetValue(player, key, value + num)
end
function DataManager.SubValue(player : Player,key : number,num)
local value = DataManager.GetValue(player,key)
function DataManager.SubValue(player: Player, key: number, num)
local value = DataManager.GetValue(player, key)
if not value then
return
end
@ -36,7 +36,7 @@ function DataManager.SubValue(player : Player,key : number,num)
DataManager.SetValue(player, key, value - num)
end
function DataManager.GetValue(player : Player,key : string)
function DataManager.GetValue(player: Player, key: string)
local profile = DataManager.Profiles[player]
if not profile then
return
@ -44,64 +44,68 @@ function DataManager.GetValue(player : Player,key : string)
return profile.Data[key]
end
function DataManager.SetValue(player : Player,key : string,num)
function DataManager.SetValue(player: Player, key: string, num)
local profile = DataManager.Profiles[player]
if not profile then
return
end
profile.Data[key] = num
local keyName = DataManager.getKey(player,key)
local keyName = DataManager.getKey(player, key)
local s = synced[keyName]
if not s then
return
end
local value = s.Value
if value then
value.Value = num
end
local datastore = s.Datastore
if datastore then
OrderedDataStore.SaveData(player,datastore,num)
OrderedDataStore.SaveData(player, datastore, num)
end
end
function DataManager.GetTanks(player : Player)
function DataManager.CreateLoadout(player, tank, loadoutData: DataTypes.Loadout)
local profile = DataManager.GetPlayerData(player)
local tankLoadoutsTbl = profile.Data[tank]
if not tankLoadoutsTbl then
profile.Data[tank] = {}
end
end
function DataManager.GetTankData(player : Player,name : string)
function DataManager.RemoveLoadout(player) end
function DataManager.UpdateLoadout() end
function DataManager.GetPlayerData(player: Player): typeof(Template)
return DataManager.Profiles[player]
end
function DataManager.SwitchTank(player : Player,tank : string)
end
function DataManager.GetTanks(player: Player) end
function DataManager.SwitchTankSkin(player : Player,skinName : string)
function DataManager.GetTankData(player: Player, name: string) end
end
function DataManager.SwitchTank(player: Player, tank: string) end
function DataManager.UnlockTank(player : Player,tank : string)
end
function DataManager.UnlockTankSkin(player : Player,skinName)
end
function DataManager.SwitchTankSkin(player: Player, skinName: string) end
function DataManager.UnlockTank(player: Player, tank: string) end
function DataManager.UnlockTankSkin(player: Player, skinName) end
-- yh ill add it later
function DataManager.SyncValue(player : Player,value : IntValue,key : string,datastore : string)
local keyName = DataManager.getKey(player,key)
synced[keyName] = {Value = value,Datastore = datastore}
DataManager.SetValue(player,key,DataManager.GetValue(player,key))
function DataManager.SyncValue(player: Player, value: IntValue, key: string, datastore: string)
local keyName = DataManager.getKey(player, key)
synced[keyName] = { Value = value, Datastore = datastore }
DataManager.SetValue(player, key, DataManager.GetValue(player, key))
end
--sup
return DataManager
return DataManager

View File

@ -1,135 +1,173 @@
local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local RemoteFunction = ReplicatedStorage.Remote.InventoryService
local Template = require(script.Parent.Template)
local DataTypes = require(ReplicatedStorage.Data.DataTypes)
local DataManager = require(ServerScriptService.Data.DataManager)
local Remote = ReplicatedStorage.Remote
local rfn_Inventory = Remote:WaitForChild("InventoryService") :: RemoteFunction
local InventoryService = {}
local PlayersInventory : {[number] : {}} = {}
-- ─────────────────────────────────────────
-- Internal
-- ─────────────────────────────────────────
local Tanks: {string} = {}
for _, tank in ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Models"):WaitForChild("Tanks"):GetChildren() do
if tank.Name ~= "Tank (Testing)" or game:GetService("RunService"):IsStudio() then
table.insert(Tanks, tank.Name)
end
end
local Skins: {[typeof(Tanks)] : string} = {}
for _, tank in Tanks do
Skins[tank] = {}
for _, skin in ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Models"):WaitForChild("Tanks"):WaitForChild(tank):GetChildren() do
if skin.Name ~= "Base" then
table.insert(Skins[tank], skin.Name)
end
type Inventory = typeof(Template.Inventory)
local function getInventory(player: Player): Inventory?
local profile = DataManager.Profiles[player]
if not profile then
return nil
end
print(profile.Data.Inventory)
return profile.Data.Inventory
end
function InventoryService.GetModel(tankName : string, skinName : string)
local base = ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Models"):WaitForChild("Tanks"):WaitForChild(tankName):FindFirstChild("Base")
local skin = ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Models"):WaitForChild("Tanks"):WaitForChild(tankName):FindFirstChild(skinName)
if base and skin then
return base, skin
local function syncToClient(player: Player)
if true then
return
end
local inv = getInventory(player)
--if inv then
-- rev_InventorySync:FireClient(player, inv)
--end
end
function createPlayerInventory(player : Player)
print("Creating inventory: ".. player.Name)
PlayersInventory[player.UserId] = {}
local Loadouts: {[string] : string} = {}
local SelectedLoadout: string
local PlayerSkins: {[string] : string} = {}
function InventoryService.GetInventory(player: Player): Inventory?
return getInventory(player)
end
local Inventory = {}
Inventory["SelectedLoadout"] = ""
Inventory["Loadouts"] = Loadouts
Inventory["Tanks"] = {}
Inventory["Skins"] = PlayerSkins
PlayersInventory[player.UserId] = Inventory
if Inventory["Tanks"] == nil or #Inventory["Tanks"] == 0 then
Inventory["Tanks"] = {"Tank"}
-- Returns the skin currently equipped for a given tank.
function InventoryService.GetSelectedSkin(player: Player, tankName: string): string
local inv = getInventory(player)
if not inv then
return "Default"
end
return inv.Loadouts[tankName] or "Default"
end
function InventoryService.GetCurrentLoadout(player: Player)
local inv = getInventory(player)
local current = inv.CurrentLoadouts[inv.SelectedTank]
return current
end
function InventoryService.CreateLoadout(player, tankName, loadout: DataTypes.Loadout): string
local inv = getInventory(player)
local randomId = HttpService:GenerateGUID(false)
inv.Loadouts[tankName][randomId] = loadout
return randomId
end
function InventoryService.SelectTank(player: Player, tankName: string): (boolean, string?)
local inv = getInventory(player)
if not inv then
return false, "Profile not loaded"
end
if not table.find(inv.Tanks, tankName) then
return false, "Tank not owned"
end
if Inventory["SelectedLoadout"] == "" or Inventory["SelectedLoadout"] == nil or table.find(Inventory["Tanks"], Inventory["SelectedLoadout"]) then
print(InventoryService.SelectLoadout(player, Inventory["Tanks"][1]))
inv.SelectedTank = tankName
syncToClient(player)
return true
end
-- Sets which skin is equipped for a specific tank.
function InventoryService.UpdateLoadout(player: Player, tank: string, skin: string): (boolean, string?)
local inv = getInventory(player)
if not inv then
return false, "Profile not loaded"
end
if not table.find(inv.Tanks, tank) then
return false, "Tank not owned"
end
for _, tank in Inventory["Tanks"] do
if Inventory["Skins"][tank] == nil or #Inventory["Skins"][tank] == 0 then
Inventory["Skins"][tank] = {"Default"}
end
local ownedSkins = inv.Skins[tank]
if not ownedSkins or not table.find(ownedSkins, skin) then
return false, "Skin not owned"
end
if not CheckIfLoadoutExists(player, Inventory["Tanks"][1]) then
InventoryService.UpdateLoadout(player, Inventory["SelectedLoadout"], Inventory["Skins"][Inventory["Tanks"][1]][1])
inv.Loadouts[tank] = skin
syncToClient(player)
return true
end
function InventoryService.UnlockTank(player: Player, tankName: string)
local inv = getInventory(player)
if not inv then
return
end
if table.find(inv.Tanks, tankName) then
return
end -- already owned
table.insert(inv.Tanks, tankName)
InventoryService.UnlockSkin(player, tankName, "Default")
local loadout: DataTypes.Loadout = { Name = "Default", Tank = tankName }
InventoryService.CreateLoadout(player, tankName, loadout)
inv.CurrentLoadouts[tankName] = loadout
syncToClient(player)
end
function InventoryService.UnlockSkin(player: Player, tankName: string, skinName: string)
local inv = getInventory(player)
if not inv then
return
end
if not table.find(inv.Tanks, tankName) then
return
end
return Inventory
inv.Skins[tankName] = inv.Skins[tankName] or {}
if table.find(inv.Skins[tankName], skinName) then
return
end -- already owned
table.insert(inv.Skins[tankName], skinName)
syncToClient(player)
end
function InventoryService.GetAllTanks()
return Tanks
end
-- ─────────────────────────────────────────
-- Init (called by ModuleLoader)
-- ─────────────────────────────────────────
function InventoryService.GetAllSkins()
return Skins
end
function InventoryService.UpdateLoadout(player : Player, tank : string, skin : string)
PlayersInventory[player.UserId]["Loadouts"][tank] = skin
return PlayersInventory[player.UserId]["Loadouts"][tank]
end
function InventoryService.SelectLoadout(player : Player, loadout : string)
PlayersInventory[player.UserId]["SelectedLoadout"] = loadout
return PlayersInventory[player.UserId]["SelectedLoadout"]
end
function CheckIfLoadoutExists(player : Player, tank : string)
local Loadouts = PlayersInventory[player.UserId].Loadouts
if Loadouts[tank] then
return true
else
return false
end
end
function InventoryService.GetInventory(player : Player)
return PlayersInventory[player.UserId]
end
function InventoryService.AllInventories()
return PlayersInventory
end
function InventoryService.Init()
for _, player in Players:GetPlayers() do
createPlayerInventory(player)
end
Players.PlayerAdded:Connect(function(player)
createPlayerInventory(player)
end)
Players.PlayerRemoving:Connect(function(player)
PlayersInventory[player.UserId] = nil
end)
RemoteFunction.OnServerInvoke = function(player, action, ...)
local args = {...}
function InventoryService:Init()
-- Handle all client requests through one RemoteFunction.
-- Each action is validated server-side — client input is never trusted.
rfn_Inventory.OnServerInvoke = function(player: Player, action: string, ...)
if action == "GetInventory" then
return InventoryService.GetInventory(player)
elseif action == "AllInventories" then
return InventoryService.AllInventories()
elseif action == "SelectTank" then
local ok, err = InventoryService.SelectTank(player, ...)
return ok, err
elseif action == "UpdateLoadout" then
InventoryService.UpdateLoadout(player, args[1], args[2])
elseif action == "SelectLoadout" then
return InventoryService.SelectLoadout(player, args[1])
elseif action == "GetAllTanks" then
return InventoryService.GetAllTanks()
elseif action == "GetAllSkins" then
return InventoryService.GetAllSkins()
elseif action == "GetModel" then
return InventoryService.GetModel(args[1], args[2])
local ok, err = InventoryService.UpdateLoadout(player, ...)
return ok, err
end
return false, "Unknown action: " .. tostring(action)
end
-- Sync inventory to client once their profile is ready.
-- DataManager loads profiles on PlayerAdded, so we wait for it.
Players.PlayerAdded:Connect(function(player)
local waited = 0
while not DataManager.Profiles[player] and waited < 10 do
task.wait(0.5)
waited += 0.5
end
if DataManager.Profiles[player] then
syncToClient(player)
end
end)
end
return InventoryService

View File

@ -1,12 +1,19 @@
--!nonstrict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataTypes = require(ReplicatedStorage.Data.DataTypes)
local Inventory: DataTypes.Inventory = {
Tanks = { "Tank" }, -- tanks the player owns
Skins = { Tank = { "Default" } }, -- skins per tank
Loadouts = {} :: { [string]: { [string]: DataTypes.Loadout } }, -- Saved loadouts,
CurrentLoadouts = {} :: { [string]: DataTypes.Loadout }, -- Current loadout of tank
SelectedTank = "Tank",
}
return {
Money = 50,
Wins = 0,
UnlockedSkins = {},
UnlockedTanks = {},
UnlockedAccessories = {},
Loadouts = {},
CurrentLoadout = "Default",
CurrentTank = "Tank"
}
Inventory = Inventory,
}

View File

@ -3,11 +3,11 @@
local GameObject = require(script.Parent)
local InventoryService = require(game:GetService("ServerScriptService").Data.InventoryService)
local Bot = setmetatable({},GameObject)
local Bot = setmetatable({}, GameObject)
Bot.__index = Bot
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local StarterGui = game:GetService("StarterGui")
local rAssets = ReplicatedStorage.Assets
local rHUD = rAssets.HUD
@ -18,110 +18,101 @@ local TankFolder = rModels.Tanks
local rData = ReplicatedStorage.Data
local BotData = require(rData.BotData)
type self = {
accuracy : number,
HUD : typeof(rHUD.HUD_TANK)
accuracy: number,
HUD: typeof(rHUD.HUD_TANK),
}
export type Bot = typeof( setmetatable({} :: self, Bot) ) & GameObject.GameObject
export type Bot = typeof(setmetatable({} :: self, Bot)) & GameObject.GameObject
local Component = script.Parent.Parent.Component
local HitboxComponent = require(Component.HitboxComponent)
local ProjectileComponent = require(Component.ProjectileComponent)
local HealthComponent = require(Component.HealthComponent)
function Bot.new(player : Player, name, character)
local self : Bot = setmetatable(GameObject.new(player.UserId,nil,player.UserId) ,Bot)
function Bot.new(player: Player, name, character)
local self: Bot = setmetatable(GameObject.new(player.UserId, nil, player.UserId), Bot)
local Inventory = InventoryService.GetInventory(player)
local selectedTank = Inventory["SelectedTank"]
local selectedSkin = Inventory["Loadouts"][selectedTank]
name = selectedTank or "Tank"
local skin = selectedSkin or "Default"
local bData : BotData.BotData = BotData[name]
print(name,bData)
local bData: BotData.BotData = BotData[name]
print(name, bData)
print(name)
self.name = name
self.skin = skin
self.accuracy = bData.accuracy
self.weapons = bData.weapons -- { basic = "Missile", special = "ClusterRocket" }
self.name = name
self.skin = skin
self.accuracy = bData.accuracy
self.weapons = bData.weapons -- { basic = "Missile", special = "ClusterRocket" }
self:CreateModel()
local h = HealthComponent.new(self.key,bData.maxHp)
h.HealthChangedSignal:Connect(function(oldHP,newHP)
local h = HealthComponent.new(self.key, bData.maxHp)
h.HealthChangedSignal:Connect(function(oldHP, newHP)
local diff = oldHP - newHP
if oldHP > newHP then
print(self.name .. " took " .. diff .. " damage!")
else
print(self.name .. " healed " .. diff .. " health!")
end
self:DisplayHealth(newHP)
end)
h.DiedSignal:Connect(function()
print("noooo he died!")
end)
self.Components.Health = h
self.Components.Hitbox = HitboxComponent.new(self.key,self.model)
self.Components.Hitbox = HitboxComponent.new(self.key, self.model)
self.Components.Health:Init()
return self
end
function Bot:CreateModel()
print(self.name)
print(TankFolder)
local model : Model = TankFolder:FindFirstChild(self.name):FindFirstChild("Base")
local model: Model = TankFolder:FindFirstChild(self.name):FindFirstChild("Base")
model = model:Clone()
local skin : Model = TankFolder:FindFirstChild(self.name):FindFirstChild(self.skin)
local skin: Model = TankFolder:FindFirstChild(self.name):FindFirstChild(self.skin)
skin.Parent = model
print(self.key)
model.Name = self.key
model:SetAttribute("Type",self.name)
model:SetAttribute("Type", self.name)
self.model = model
self.root = model.PrimaryPart or model.Root
ReplicatedStorage.Assets.Cooldowns:Clone().Parent = model
local HUD_TANK = rHUD.HUD_TANK:Clone()
HUD_TANK.Parent = model
self.HUD = HUD_TANK
self.HUD = HUD_TANK
self:_ResetMass()
end
function Bot:DisplayHealth(hp)
local self = self :: Bot
local Health = self.Components.Health
hp = hp or Health.Health
local max = Health.MaxHealth
local dif = hp / max
local hud = self.HUD
hud.Health.ContainerText.HealthText.Text = hp
hud.Health.BlackBar.GreenBar.Size = UDim2.new(dif,0,1,0)
hud.Health.BlackBar.GreenBar.Size = UDim2.new(dif, 0, 1, 0)
end
function Bot:heal(amount)
self.hp = math.min(self.maxHp, self.hp + amount)
self:DisplayHealth()
end
@ -131,7 +122,6 @@ end
function Bot:die()
self.model:Destroy()
end
return Bot

View File

@ -1,26 +1,26 @@
local ClientController = {}
local Players = game:GetService("Players")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local localPlayer = Players.LocalPlayer
local localPlayer = Players.LocalPlayer
local Data = ReplicatedStorage.Data -- server modules (read-only on client)
local GameStates = require(Data.GameState)
local Data = ReplicatedStorage.Data -- server modules (read-only on client)
local GameStates = require(Data.GameState)
local ClientModules = script.Parent.Modules
local ClientModules = script.Parent.Parent.Modules
local AimRenderer = require(ClientModules.AimRenderer)
local InputHandler = require(ClientModules.InputHandler)
local BotSelectUI = require(ClientModules.BotSelectUI)
local AimRenderer = require(ClientModules.AimRenderer)
local InputHandler = require(ClientModules.InputHandler)
local BotSelectUI = require(ClientModules.BotSelectUI)
local CameraController = require(ClientModules.CameraController)
local BotAbilityUI = require(ClientModules.BotAbilityUI)
local Remote = ReplicatedStorage.Remote
local rev_SubmitAction = Remote.SubmitAction
local rev_UpdateGameState = Remote.UpdateGameState
local rev_HpUpdated = Remote.HpUpdated
local rev_BotDied = Remote.BotDied
local Remote = ReplicatedStorage.Remote
local rev_SubmitAction = Remote.SubmitAction
local rev_UpdateGameState = Remote.UpdateGameState
local rev_HpUpdated = Remote.HpUpdated
local rev_BotDied = Remote.BotDied
--finish later brochacho chip
local turns = 1
@ -28,52 +28,39 @@ local turns = 1
local PLAYING = "PLAYING"
function ClientController:Init()
-- React to server phase changes
rev_UpdateGameState.OnClientEvent:Connect(function(phase)
shared.Phase = phase
if phase == GameStates.LOBBY then
turns = 1
CameraController.ResetCamera()
elseif phase == GameStates.GRACE then
CameraController.WideMapView()
BotAbilityUI.NewAbilities()
elseif phase == GameStates.AIMING then
AimRenderer.UnHighlight()
InputHandler.enable(function(input)
-- Player confirmed their action — send to server
rev_SubmitAction:FireServer(input)
InputHandler.disable()
BotAbilityUI.DisableAll()
AimRenderer.Highlight()
end,turns)
end, turns)
elseif phase == GameStates.RESOLVING then
turns += 1
BotAbilityUI.HideGUI()
InputHandler.disable()
AimRenderer.hide()
elseif phase == "Results" then
if localPlayer:HasTag(PLAYING) then
BotAbilityUI.HideGUI()
CameraController.ResetCamera()
end
-- TODO: show win screen
end
end)
end
return ClientController
return ClientController

View File

@ -0,0 +1,86 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local GarageUIHandler = require(ReplicatedStorage.Client.Garage.GarageUIHandler)
local InventoryClient = require(ReplicatedStorage.Client.Garage.InventoryClient)
local TankShowcase = require(ReplicatedStorage.Client.Garage.TankShowcase)
local DataTypes = require(ReplicatedStorage.Data.DataTypes)
local GarageController = {}
local tabName
local items
local index
local garageShown = false
local AccessorySlotIndex = 1
local AccessorySlots = {
"Head",
"Turret",
"Back",
}
function OnItemClick(i: ImageButton)
local oldIndex = index
index = tonumber(i.Name)
if oldIndex == index then -- same
return
end
local inv = InventoryClient.GetInventory()
local itemName = i:GetAttribute("ItemName")
if tabName == "Tanks" then
InventoryClient.SelectTank(itemName)
end
local currentLoadout = inv.CurrentLoadouts[inv.SelectedTank]
if tabName == "Skins" then
currentLoadout.Skin = i.Name
end
TankShowcase.Showcase(currentLoadout)
end
function OnHandleClicked()
garageShown = not garageShown
if garageShown then
GarageController.ShowGarage(true)
else
GarageController.HideGarage(true)
end
end
function OnNavigClick(value) end
function ChangeItem(index)
local currentItem = items[index]
end
function OnTabClicked(tab: TextButton)
tabName = tab.Name
GarageUIHandler.SetActiveTab(tab)
items = InventoryClient.GetItemsByTab(tabName)
GarageUIHandler.SetItems(items, tabName)
end
function GarageController.ShowGarage(anim: boolean?)
GarageUIHandler.Show(anim)
TankShowcase.Focus()
TankShowcase.Showcase({ Tank = "Tank" })
end
function GarageController.HideGarage(anim: boolean?)
GarageUIHandler.Hide(anim)
TankShowcase.UnFocus()
end
function GarageController:Init()
GarageUIHandler.Events.OnTabClicked:Connect(OnTabClicked)
GarageUIHandler.Events.OnHandleClicked:Connect(OnHandleClicked)
GarageController.HideGarage(false)
end
return GarageController

View File

@ -1,43 +0,0 @@
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local player = Players.LocalPlayer
local Garage = workspace.Garage
local TankFolder = ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Models"):WaitForChild("Tanks")
if not game:GetService("RunService"):IsStudio() then
TankFolder:WaitForChild("Tank (Testing)"):Destroy()
end
local SelectedTank = player:WaitForChild("SelectedTank")
local SelectedSkin = player:WaitForChild("SelectedSkin")
local TankDisplay = Garage:WaitForChild("TankDisplay")
local garage = {}
local function updateTank()
local tankName = SelectedTank.Value
local skinName = SelectedSkin.Value
local tankModel = TankFolder:FindFirstChild(tankName)
if tankModel then
local tankBase = tankModel:FindFirstChild("Base")
if tankModel and tankBase then
local skin = tankModel:FindFirstChild(skinName) or tankModel:FindFirstChild("Default")
if skin then
TankDisplay:ClearAllChildren()
local Tank = tankBase:Clone()
Tank.Parent = TankDisplay
skin = skin:Clone()
skin.Parent = Tank
Tank:PivotTo(TankDisplay.CFrame)
end
end
end
end
function garage:Init()
SelectedTank:GetPropertyChangedSignal("Value"):Connect(updateTank)
SelectedSkin:GetPropertyChangedSignal("Value"):Connect(updateTank)
updateTank()
end
return garage

View File

@ -0,0 +1,246 @@
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local StarterGui = game:GetService("StarterGui")
local BotData = require(ReplicatedStorage.Data.BotData)
local InventoryClient = require(script.Parent.InventoryClient)
local DataTypes = require(ReplicatedStorage.Data.DataTypes)
local Signal = require(ReplicatedStorage.Shared.SharedUtils.Signal)
local sharedBotUtils = require(ReplicatedStorage.Shared.SharedUtils.sharedBotUtils)
local GarageUIHandler = {}
local localPlayer = Players.LocalPlayer
local PlayerGui = localPlayer.PlayerGui
local GarageGui = PlayerGui:WaitForChild("GarageGui")
local WorldOverlay = GarageGui:WaitForChild("WorldOverlay")
local nav_Left = GarageGui.Navigation.Left
local nav_Right = GarageGui.Navigation.Right
local StatusHUD = GarageGui:WaitForChild("StatusHUD")
local TopCenter = GarageGui:WaitForChild("TopCenter")
local BotName = TopCenter:WaitForChild("BotName")
local SkinName = TopCenter:WaitForChild("SkinName")
local Drawer = GarageGui:WaitForChild("Drawer")
local SelectedTab = Drawer:WaitForChild("SelectedTab")
local Selector = SelectedTab:WaitForChild("Selector")
local Handle = Drawer:WaitForChild("Handle")
local Tabs = Drawer:WaitForChild("Tabs")
local t_Accessories = Tabs:WaitForChild("Accessories")
local t_Skins = Tabs:WaitForChild("Skins")
local t_Tanks = Tabs:WaitForChild("Tanks")
local SafetyDrawer = Drawer:WaitForChild("SafetyDrawer")
local ScrollingFrame = SafetyDrawer:WaitForChild("ScollingFrame")
local InteractionContainer = SafetyDrawer:WaitForChild("InteractionContainer")
local BuyContainer = InteractionContainer:WaitForChild("BuyContainer")
local BuyButton = BuyContainer:WaitForChild("LowerButton")
local EquipIndicator = BuyContainer:WaitForChild("Indicator")
local EquipContainer = InteractionContainer:WaitForChild("EquipContainer")
local EquipButton = EquipContainer:WaitForChild("LowerButton")
local EquipIndicator = EquipContainer:WaitForChild("Indicator")
type Inventory = DataTypes.Inventory
local Models = ReplicatedStorage.Assets.Models
local ITEM_TEMPLATE = ReplicatedStorage.Assets.UI:WaitForChild("ITEM_TEMPLATE")
--t is a tab button :)
local HANDLE_UP_POSITION = UDim2.new(-0.005, 0, 0.773, 0)
local HANDLE_DOWN_POSITION = UDim2.new(-0.005, 0, 0.97, 0)
local _cachedItemTable = {}
function _moveTabSelector(t: TextButton)
print(t.AbsoluteSize)
Selector.Size = UDim2.fromOffset(t.AbsoluteSize.X, t.AbsoluteSize.Y)
Selector.Position = UDim2.fromOffset(t.AbsolutePosition.X, t.AbsolutePosition.Y)
end
function _cleanUpItems()
for _, v in pairs(ScrollingFrame:GetChildren()) do
if v:IsA("ImageButton") then
v:Destroy()
end
end
table.clear(_cachedItemTable)
end
function _newTemplate(model: Model)
local new = ITEM_TEMPLATE:Clone()
model.Parent = new.Viewport
new.Viewport.CurrentCamera = Instance.new("Camera", new.Viewport)
new.Viewport.CurrentCamera.CFrame = CFrame.new(Vector3.new(0, 2, 12), model:GetPivot().Position)
new.Parent = ScrollingFrame
table.insert(_cachedItemTable, new)
new.Name = #_cachedItemTable
new:SetAttribute("ItemName", model.Name)
return new
end
function GarageUIHandler.SetItems(items, currentTab)
_cleanUpItems()
for _, itemModel in items do
local ui = _newTemplate(itemModel)
ui.Activated:Connect(function()
GarageUIHandler.Events.OnItemClicked:Fire(itemModel)
end)
end
GarageUIHandler.UpdateItemsState(currentTab)
end
function _GetAllItems()
return _cachedItemTable
end
function _BrightenItem(item: typeof(ITEM_TEMPLATE))
item.BackgroundTransparency = 0
end
function _ShadowItem(item: typeof(ITEM_TEMPLATE))
item.BackgroundTransparency = 0.5
end
function _PullHandleUp(anim: boolean?)
if anim then
Drawer:TweenPosition(HANDLE_UP_POSITION, Enum.EasingDirection.In, Enum.EasingStyle.Linear, 0.3)
else
Drawer.Position = HANDLE_UP_POSITION
end
end
function _PullHandleDown(anim: boolean?)
if anim then
Drawer:TweenPosition(HANDLE_DOWN_POSITION, Enum.EasingDirection.In, Enum.EasingStyle.Linear, 0.3)
else
Drawer.Position = HANDLE_DOWN_POSITION
end
end
function _TransitionBlack(hide: boolean?)
if hide then
for i = 0, 100, 5 do
local v = i / 100
WorldOverlay.BackgroundTransparency = v
task.wait(0.03)
end
else
for i = 100, 0, -5 do
local v = i / 100
WorldOverlay.BackgroundTransparency = v
task.wait(0.03)
end
end
end
function _ToggleVisibility(value: boolean?)
value = value or false
StatusHUD.Visible = value
Drawer.Controls.Visible = value
GarageGui.AbilityHUD.Visible = value
StatusHUD.Visible = value
TopCenter.Visible = value
end
function GarageUIHandler.Hide(anim: boolean?)
_PullHandleDown(anim)
if anim then
_TransitionBlack(false)
end
_ToggleVisibility(false)
if anim then
_TransitionBlack(true)
end
end
function GarageUIHandler.Show(anim: boolean?)
_PullHandleUp(anim)
if anim then
_TransitionBlack(false)
end
_ToggleVisibility(true)
if anim then
_TransitionBlack(true)
end
end
function GarageUIHandler.SetActiveTab(t: TextButton)
_moveTabSelector(t)
end
function GarageUIHandler.UpdateItemsState(tabName)
for _, item in pairs(_GetAllItems()) do
local itemName = item:GetAttribute("ItemName")
local can = true
if tabName == "Tanks" then
can = InventoryClient.HasTank(itemName)
elseif tabName == "Skins" then
can = InventoryClient.HasSkin(itemName)
end
if can then
_BrightenItem(item)
else
_ShadowItem(item)
end
end
end
GarageUIHandler.Messages = {
CANT_AFFORD = "Not Enough Money",
CAN_AFFORD = "BUY",
CAN_EQUIP = "EQUIP",
ALREADY_EQUIPPED = "EQUIPPED",
ERROR = "Something went wrong",
}
GarageUIHandler.Events = {
OnTabClicked = Signal.new(),
OnItemClicked = Signal.new(),
OnBuyClicked = Signal.new(),
OnEquipClicked = Signal.new(),
OnNavigClicked = Signal.new(),
OnHandleClicked = Signal.new(),
}
function GarageUIHandler:Init()
for _, v in pairs(Tabs:GetChildren()) do
if not v:IsA("TextButton") then
continue
end
v.Activated:Connect(function()
_moveTabSelector(v)
GarageUIHandler.Events.OnTabClicked:Fire(v)
end)
end
nav_Left.Activated:Connect(function()
GarageUIHandler.Events.OnNavigClicked:Fire(-1)
end)
nav_Right.Activated:Connect(function()
GarageUIHandler.Events.OnNavigClicked:Fire(1)
end)
EquipButton.Activated:Connect(function()
GarageUIHandler.Events.OnEquipClicked:Fire(EquipButton)
end)
Handle.Activated:Connect(function()
GarageUIHandler.Events.OnHandleClicked:Fire()
end)
end
return GarageUIHandler

View File

@ -0,0 +1,87 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataTypes = require(ReplicatedStorage.Data.DataTypes)
local sharedBotUtils = require(ReplicatedStorage.Shared.SharedUtils.sharedBotUtils)
local InventoryClient = {}
InventoryClient.Inventory = {}
local Models = ReplicatedStorage.Assets.Models
local Remote = ReplicatedStorage.Remote
local rfn_Inventory = Remote.InventoryService
local _inv = nil
function InventoryClient.Pull(): DataTypes.Inventory
local oldTank = _inv and _inv.SelectedTank
local new_inv = rfn_Inventory:InvokeServer("GetInventory")
if not new_inv then
return _inv
end
if oldTank then
new_inv.SelectedTank = oldTank
end
_inv = new_inv
return _inv
end
function InventoryClient.GetItemsByTab(tabName)
local inv = InventoryClient.GetInventory()
local items = {}
if tabName == "Tanks" then
for _, v in pairs(Models.Tanks:GetChildren()) do
local currentTankName = v.Name
local currentLoadout = inv.CurrentLoadouts[currentTankName]
local newModel = sharedBotUtils.CreateBot(currentLoadout, currentTankName)
table.insert(items, newModel)
end
elseif tabName == "Skins" then
local tankSelected = InventoryClient.GetTankSelected()
local currentTankFolder = Models.Tanks:FindFirstChild(tankSelected)
for _, v in pairs(currentTankFolder.Skins:GetChildren()) do
local currentLoadout = {
Skin = v.Name,
Tank = tankSelected,
}
local newModel = sharedBotUtils.CreateBot(currentLoadout, tankSelected)
table.insert(items, newModel)
end
end
return items
end
function InventoryClient.GetTankSelected()
return InventoryClient.GetInventory().SelectedTank
end
function InventoryClient.SelectTank(tank)
local inv = InventoryClient.GetInventory()
inv.SelectedTank = tank
inv.CurrentLoadouts[tank].Tank = tank
end
function InventoryClient.HasTank(tank)
return table.find(InventoryClient.GetInventory().Tanks, tank)
end
function InventoryClient.HasSkin(skin)
local tank = InventoryClient.GetTankSelected()
return table.find(InventoryClient.GetInventory().Skins[tank], skin)
end
function InventoryClient.GetInventory(): DataTypes.Inventory
if not _inv then
return InventoryClient.Pull()
end
return _inv
end
return InventoryClient

View File

@ -0,0 +1,68 @@
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local DataTypes = require(ReplicatedStorage.Data.DataTypes)
local sharedBotUtils = require(ReplicatedStorage.Shared.SharedUtils.sharedBotUtils)
local TankShowcase = {}
local GarageFolder = workspace.Garage
local TankDisplay = GarageFolder:WaitForChild("TankDisplay")
local cCamera = workspace.CurrentCamera
local rotOffset = CFrame.Angles(0, 0, 0)
local currentTank
--[[
Anim
2:From right to center
1:From left to center
0:No Anim
]]
function _cleanupTank()
if currentTank then
currentTank:Destroy()
currentTank = nil
end
end
function _resetRot()
rotOffset = CFrame.Angles(0, 0, 0)
end
function TankShowcase.Focus()
cCamera.CameraType = Enum.CameraType.Scriptable
local position = TankDisplay.Position + TankDisplay.CFrame:VectorToWorldSpace(Vector3.new(0, 0, 10))
local newCFrame = CFrame.lookAt(position, TankDisplay.Position)
cCamera.CFrame = newCFrame
end
function TankShowcase.UnFocus()
cCamera.CameraType = Enum.CameraType.Custom
end
function TankShowcase.Showcase(loadout: DataTypes.Loadout)
_cleanupTank()
local new = sharedBotUtils.CreateBot(loadout)
print(new)
new:PivotTo(TankDisplay.CFrame)
_resetRot()
currentTank = new
currentTank.Parent = workspace
end
function TankShowcase.Update(dt)
if currentTank then
rotOffset = rotOffset * CFrame.Angles(0, math.rad(dt), 0)
currentTank:PivotTo(CFrame.new(currentTank:GetPivot().Position) * rotOffset)
else
_resetRot()
end
end
function TankShowcase:Init() end
return TankShowcase

View File

@ -1,22 +0,0 @@
local GarageUIController = {}
local localPlayer = game.Players.LocalPlayer
local PlayerGui = localPlayer.PlayerGui
local GarageGui = PlayerGui:WaitForChild("GarageGui")
function GarageUIController.OpenGUI()
end
function GarageUIController.CloseGUI()
end
function GarageUIController:Init()
end
return GarageUIController

View File

@ -1,22 +1,34 @@
local ServerScriptService = game:GetService("ServerScriptService")
export type FireData = {
weapon : string,
angle : number,
power : number,
origin : Vector3,
specialArgs : {any}
weapon: string,
angle: number,
power: number,
origin: Vector3,
specialArgs: { any },
}
export type VoteOptionData = {
Name : string,
DisplayName : string,
DisplayImage : string
Name: string,
DisplayName: string,
DisplayImage: string,
}
export type Loadout = {
Skin : string,
HeadAccessory : string?,
Tank: string,
Skin: string?,
HeadAccessory: string?,
Name: string?,
Locked: boolean?, -- can be edited
}
return {}
export type Inventory = {
Tanks: { string }, -- tanks the player owns
Skins: { [string]: { string } }, -- skins per tank
Loadouts: { [string]: { [string]: Loadout } },
CurrentLoadouts: { [string]: Loadout }, -- Current loadout of tank
SelectedTank: string,
}
return {}

View File

View File

@ -1,161 +0,0 @@
--!strict
-- Partial types for Promise
local Packages = script.Parent.Packages
local Promise: any = if Packages:FindFirstChild("Promise") then require(Packages.Promise) else nil
export type Status = "Started" | "Resolved" | "Rejected" | "Cancelled"
export type ErrorKind = "ExecutionError" | "AlreadyCancelled" | "NotResolvedInTime" | "TimedOut"
type ErrorStaticAndShared = {
Kind: {
ExecutionError: "ExecutionError",
AlreadyCancelled: "AlreadyCancelled",
NotResolvedInTime: "NotResolvedInTime",
TimedOut: "TimedOut",
},
}
type ErrorOptions = {
error: string,
trace: string?,
context: string?,
kind: ErrorKind,
}
export type Error = typeof(setmetatable(
{} :: ErrorStaticAndShared & {
error: string,
trace: string?,
context: string?,
kind: ErrorKind,
parent: Error?,
createdTick: number,
createdTrace: string,
extend: (self: Error, options: ErrorOptions?) -> Error,
getErrorChain: (self: Error) -> {Error},
},
{} :: {__tostring: (self: Error) -> string}
))
type ErrorStatic = ErrorStaticAndShared & {
new: (options: ErrorOptions?, parent: Error?) -> Error,
is: (anything: any) -> boolean,
isKind: (anything: any, kind: ErrorKind) -> boolean,
}
export type Promise = {
andThen: (
self: Promise,
successHandler: (...any) -> ...any,
failureHandler: ((...any) -> ...any)?
) -> Promise,
andThenCall: <TArgs...>(self: Promise, callback: (TArgs...) -> ...any, TArgs...) -> any,
andThenReturn: (self: Promise, ...any) -> Promise,
await: (self: Promise) -> (boolean, ...any),
awaitStatus: (self: Promise) -> (Status, ...any),
cancel: (self: Promise) -> (),
catch: (self: Promise, failureHandler: (...any) -> ...any) -> Promise,
expect: (self: Promise) -> ...any,
finally: (self: Promise, finallyHandler: (status: Status) -> ...any) -> Promise,
finallyCall: <TArgs...>(self: Promise, callback: (TArgs...) -> ...any, TArgs...) -> Promise,
finallyReturn: (self: Promise, ...any) -> Promise,
getStatus: (self: Promise) -> Status,
now: (self: Promise, rejectionValue: any?) -> Promise,
tap: (self: Promise, tapHandler: (...any) -> ...any) -> Promise,
timeout: (self: Promise, seconds: number, rejectionValue: any?) -> Promise,
}
export type TypedPromise<T...> = {
andThen: (self: Promise, successHandler: (T...) -> ...any, failureHandler: ((...any) -> ...any)?) -> Promise,
andThenCall: <TArgs...>(self: Promise, callback: (TArgs...) -> ...any, TArgs...) -> Promise,
andThenReturn: (self: Promise, ...any) -> Promise,
await: (self: Promise) -> (boolean, T...),
awaitStatus: (self: Promise) -> (Status, T...),
cancel: (self: Promise) -> (),
catch: (self: Promise, failureHandler: (...any) -> ...any) -> Promise,
expect: (self: Promise) -> T...,
finally: (self: Promise, finallyHandler: (status: Status) -> ...any) -> Promise,
finallyCall: <TArgs...>(self: Promise, callback: (TArgs...) -> ...any, TArgs...) -> Promise,
finallyReturn: (self: Promise, ...any) -> Promise,
getStatus: (self: Promise) -> Status,
now: (self: Promise, rejectionValue: any?) -> Promise,
tap: (self: Promise, tapHandler: (T...) -> ...any) -> Promise,
timeout: (self: Promise, seconds: number, rejectionValue: any?) -> TypedPromise<T...>,
}
type Signal<T...> = {
Connect: (self: Signal<T...>, callback: (T...) -> ...any) -> SignalConnection,
}
type SignalConnection = {
Disconnect: (self: SignalConnection) -> ...any,
[any]: any,
}
export type PromiseStatic = {
Error: ErrorStatic,
Status: {
Started: "Started",
Resolved: "Resolved",
Rejected: "Rejected",
Cancelled: "Cancelled",
},
all: <T>(promises: {TypedPromise<T>}) -> TypedPromise<{T}>,
allSettled: <T>(promise: {TypedPromise<T>}) -> TypedPromise<{Status}>,
any: <T>(promise: {TypedPromise<T>}) -> TypedPromise<T>,
defer: <TReturn...>(
executor: (
resolve: (TReturn...) -> (),
reject: (...any) -> (),
onCancel: (abortHandler: (() -> ())?) -> boolean
) -> ()
) -> TypedPromise<TReturn...>,
delay: (seconds: number) -> TypedPromise<number>,
each: <T, TReturn>(
list: {T | TypedPromise<T>},
predicate: (value: T, index: number) -> TReturn | TypedPromise<TReturn>
) -> TypedPromise<{TReturn}>,
fold: <T, TReturn>(
list: {T | TypedPromise<T>},
reducer: (accumulator: TReturn, value: T, index: number) -> TReturn | TypedPromise<TReturn>
) -> TypedPromise<TReturn>,
fromEvent: <TReturn...>(
event: Signal<TReturn...>,
predicate: ((TReturn...) -> boolean)?
) -> TypedPromise<TReturn...>,
is: (object: any) -> boolean,
new: <TReturn...>(
executor: (
resolve: (TReturn...) -> (),
reject: (...any) -> (),
onCancel: (abortHandler: (() -> ())?) -> boolean
) -> ()
) -> TypedPromise<TReturn...>,
onUnhandledRejection: (callback: (promise: TypedPromise<any>, ...any) -> ()) -> () -> (),
promisify: <TArgs..., TReturn...>(callback: (TArgs...) -> TReturn...) -> (TArgs...) -> TypedPromise<TReturn...>,
race: <T>(promises: {TypedPromise<T>}) -> TypedPromise<T>,
reject: (...any) -> TypedPromise<...any>,
resolve: <TReturn...>(TReturn...) -> TypedPromise<TReturn...>,
retry: <TArgs..., TReturn...>(
callback: (TArgs...) -> TypedPromise<TReturn...>,
times: number,
TArgs...
) -> TypedPromise<TReturn...>,
retryWithDelay: <TArgs..., TReturn...>(
callback: (TArgs...) -> TypedPromise<TReturn...>,
times: number,
seconds: number,
TArgs...
) -> TypedPromise<TReturn...>,
some: <T>(promise: {TypedPromise<T>}, count: number) -> TypedPromise<{T}>,
try: <TArgs..., TReturn...>(callback: (TArgs...) -> TReturn..., TArgs...) -> TypedPromise<TReturn...>,
}
return Promise :: PromiseStatic?

View File

@ -1,14 +1,19 @@
local sharedBotUtils = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Assets = ReplicatedStorage.Assets
local Models = Assets.Models
local TankFolder = Models.Tanks :: Folder
local rData = ReplicatedStorage.Data
local DataTypes = require(ReplicatedStorage.Data.DataTypes)
local BotData = require(rData.BotData)
local GameState = require(rData.GameState)
local GameState = require(rData.GameState)
local WeaponData = require(rData.WeaponData)
local sUtils = ReplicatedStorage.Shared.SharedUtils
local TwoDimensionUtils = require(sUtils.TwoDimensionUtils)
local WeldModule = require(script.Parent.WeldModule)
local wBots = workspace.Bots
@ -19,32 +24,97 @@ local NO_COOLDOWN = {
local COOLDOWN_ABILITY = {
NormalAbility = 2,
SpecialAbility = 3
SpecialAbility = 3,
}
function sharedBotUtils.GetBotData(UserId : number) : BotData.BotData
--Not cloned
function sharedBotUtils.GetTankBase(name): Model
name = name or "Tank"
local currentFolder = TankFolder:FindFirstChild(name)
if not currentFolder then
currentFolder = TankFolder.Tank
end
local base = currentFolder:FindFirstChild("Base")
if not base then
base = currentFolder.Base
end
return base
end
--Not cloned
function sharedBotUtils.GetTankSkin(name, skinName): Model
name = name or "Tank"
local currentFolder = TankFolder:FindFirstChild(name)
if not currentFolder then
currentFolder = TankFolder.Tank
end
skinName = skinName or "Default"
local skin = currentFolder.Skins:FindFirstChild(name)
if not skin then
skin = currentFolder.Skins.Default
end
return skin
end
function sharedBotUtils.CreateBot(loadout: DataTypes.Loadout, tankName: string?)
if not loadout then
loadout = { Name = "Whatever", Tank = "Tank" }
end
if not tankName then
tankName = loadout.Tank
end
local base = sharedBotUtils.GetTankBase(tankName):Clone()
local skin = sharedBotUtils.GetTankSkin(tankName, loadout.Skin):Clone()
skin.Name = "Skin"
base.Name = "Base"
local newTank = Instance.new("Model")
skin.Parent = newTank
base.Parent = newTank
newTank.PrimaryPart = base.PrimaryPart
WeldModule.UnWeldModel(newTank)
WeldModule.WeldModel(newTank)
return newTank
end
function sharedBotUtils.GetBotData(UserId: number): BotData.BotData
local model = sharedBotUtils.FindBotModel(UserId)
return BotData[model:GetAttribute("Type")]
end
function sharedBotUtils.GetWeaponName(UserId : number,weaponType : string)
function sharedBotUtils.GetWeaponName(UserId: number, weaponType: string)
local bData = sharedBotUtils.GetBotData(UserId)
local name = bData.weapons[weaponType] or weaponType
return name
end
function sharedBotUtils.GetTurret(UserId : number) : Model
function sharedBotUtils.GetTurret(UserId: number): Model
local model = sharedBotUtils.FindBotModel(UserId)
return model.RotatePart
end
function sharedBotUtils.RotateTurret(UserId : number,launchDirection)
function sharedBotUtils.RotateTurret(UserId: number, launchDirection)
local turret = sharedBotUtils.GetTurret(UserId)
if not turret then return end
if not turret then
return
end
local weld = turret.PrimaryPart:FindFirstChildOfClass("Weld")
if not weld then return end
if not weld then
return
end
local base = weld.Part0
local turretPart = weld.Part1
@ -57,29 +127,29 @@ function sharedBotUtils.RotateTurret(UserId : number,launchDirection)
weld.C0 = base.CFrame:Inverse() * worldCF
end
function sharedBotUtils.GetWeaponsData(UserId : number) : {[string] : WeaponData.WeaponData}
function sharedBotUtils.GetWeaponsData(UserId: number): { [string]: WeaponData.WeaponData }
local tbl = {}
local bData = sharedBotUtils.GetBotData(UserId)
tbl.Move = WeaponData.Move
tbl.Missile = WeaponData.Missile
for weaponType,weaponName in pairs(bData.weapons) do
for weaponType, weaponName in pairs(bData.weapons) do
local wData = WeaponData[weaponName]
tbl[weaponType] = wData
end
return tbl
end
function sharedBotUtils.GetBotPosition(UserId : number)
function sharedBotUtils.GetBotPosition(UserId: number)
local model = sharedBotUtils.FindBotModel(UserId)
return model.PrimaryPart.Position
end
function sharedBotUtils.CanUseAbility(UserId : number, abilityType : string)
function sharedBotUtils.CanUseAbility(UserId: number, abilityType: string)
if true then
return true
end
local isInCooldown = sharedBotUtils.AbilityInCooldown(UserId,abilityType)
local isInCooldown = sharedBotUtils.AbilityInCooldown(UserId, abilityType)
if isInCooldown then
return
end
@ -87,64 +157,66 @@ function sharedBotUtils.CanUseAbility(UserId : number, abilityType : string)
if Phase ~= GameState.AIMING then
return
end
return true
end
function sharedBotUtils.GetCooldowns(UserId : number) : Folder
function sharedBotUtils.GetCooldowns(UserId: number): Folder
local model = sharedBotUtils.FindBotModel(UserId)
return model.Cooldowns
end
function sharedBotUtils.AbilityInCooldown(UserId,ability : string)
function sharedBotUtils.AbilityInCooldown(UserId, ability: string)
if NO_COOLDOWN[ability] then
return
end
local cooldown = sharedBotUtils.GetCooldowns(UserId)
local cAbil : IntValue = cooldown:FindFirstChild(ability)
local cAbil: IntValue = cooldown:FindFirstChild(ability)
if not cAbil then
warn("Ability name " .. ability .." doesnt match with any of the cooldown values, what the hell did you do?")
warn("Ability name " .. ability .. " doesnt match with any of the cooldown values, what the hell did you do?")
print(cooldown)
return
end
return COOLDOWN_ABILITY[ability] ~= cAbil.Value
return COOLDOWN_ABILITY[ability] ~= cAbil.Value
end
function sharedBotUtils.FindBotModel(UserId : number) : Model
function sharedBotUtils.FindBotModel(UserId: number): Model
local b = wBots:WaitForChild(tostring(UserId))
return b
end
function sharedBotUtils.GetShootPos(UserId : number)
function sharedBotUtils.GetShootPos(UserId: number)
local turret = sharedBotUtils.GetTurret(UserId)
if not turret then
warn("no turret model >:(")
return sharedBotUtils.GetBotPosition(UserId)
end
end
local Shoot = turret:FindFirstChild("Shoot")
if not Shoot or not Shoot.PrimaryPart then
warn("either not shoot model or not shoot primarypart")
return turret:GetPivot().Position
end
return Shoot.PrimaryPart.Position
end
function sharedBotUtils.GetBotRoot(UserId : number) : Part
function sharedBotUtils.GetBotRoot(UserId: number): Part
local b = sharedBotUtils.FindBotModel(UserId)
return b.PrimaryPart
end
function sharedBotUtils.IsObjectOnFloor(model) : boolean
function sharedBotUtils.IsObjectOnFloor(model): boolean
local root = model.PrimaryPart
if not root then return false end
if not root then
return false
end
-- Get the model size (assumes the root's parent is the full model)
local model = root.Parent
local size, cf = model:GetBoundingBox() -- size: Vector3, cf: CFrame of bounding box
local size, cf = model:GetBoundingBox() -- size: Vector3, cf: CFrame of bounding box
local origin = root.Position
-- Raycast downward relative to half the model height + small buffer
@ -152,7 +224,7 @@ function sharedBotUtils.IsObjectOnFloor(model) : boolean
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {model}
params.FilterDescendantsInstances = { model }
local result = workspace:Raycast(origin, direction, params)
if not result then
@ -166,7 +238,7 @@ function sharedBotUtils.IsObjectOnFloor(model) : boolean
-- must be close to ground and not moving vertically
return distance <= (size.Y / 2 + 0.5) and math.abs(velY) < 1
end
function sharedBotUtils.GetBotVelocity(UserId : number)
function sharedBotUtils.GetBotVelocity(UserId: number)
local root = sharedBotUtils.GetBotRoot(UserId)
return root.AssemblyLinearVelocity
end