diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/default.project.json b/default.project.json index c014c55..ff522f1 100644 --- a/default.project.json +++ b/default.project.json @@ -34,6 +34,7 @@ "ServerScriptService": { "$path": "src/server" }, + "StarterPlayer": { "StarterPlayerScripts": { "$path": "src/client" diff --git a/luau-lsp.json b/luau-lsp.json new file mode 100644 index 0000000..2268032 --- /dev/null +++ b/luau-lsp.json @@ -0,0 +1,4 @@ +{ + "luau-lsp.sourcemap.enabled": true, + "luau-lsp.sourcemap.file": "sourcemap.json" +} \ No newline at end of file diff --git a/src/server/Data/DataManager.luau b/src/server/Data/DataManager.luau index 6003aca..46b6053 100644 --- a/src/server/Data/DataManager.luau +++ b/src/server/Data/DataManager.luau @@ -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 \ No newline at end of file +return DataManager diff --git a/src/server/Data/InventoryService.luau b/src/server/Data/InventoryService.luau index de22dab..8fc0013 100644 --- a/src/server/Data/InventoryService.luau +++ b/src/server/Data/InventoryService.luau @@ -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 diff --git a/src/server/Data/Template.luau b/src/server/Data/Template.luau index 06d6c44..f03026c 100644 --- a/src/server/Data/Template.luau +++ b/src/server/Data/Template.luau @@ -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" -} \ No newline at end of file + + Inventory = Inventory, +} diff --git a/src/server/Modules/Classes/GameObject/Bot.luau b/src/server/Modules/Classes/GameObject/Bot.luau index 2d1534c..f63ad6a 100644 --- a/src/server/Modules/Classes/GameObject/Bot.luau +++ b/src/server/Modules/Classes/GameObject/Bot.luau @@ -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 diff --git a/src/shared/client/ClientController.luau b/src/shared/client/Controllers/ClientController.luau similarity index 61% rename from src/shared/client/ClientController.luau rename to src/shared/client/Controllers/ClientController.luau index 9978abf..e374695 100644 --- a/src/shared/client/ClientController.luau +++ b/src/shared/client/Controllers/ClientController.luau @@ -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 \ No newline at end of file +return ClientController diff --git a/src/shared/client/Controllers/GarageController.luau b/src/shared/client/Controllers/GarageController.luau new file mode 100644 index 0000000..6998ac9 --- /dev/null +++ b/src/shared/client/Controllers/GarageController.luau @@ -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 diff --git a/src/shared/client/Garage.luau b/src/shared/client/Garage.luau deleted file mode 100644 index 3cb5559..0000000 --- a/src/shared/client/Garage.luau +++ /dev/null @@ -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 diff --git a/src/shared/client/Garage/GarageUIHandler.luau b/src/shared/client/Garage/GarageUIHandler.luau new file mode 100644 index 0000000..c2ae5bc --- /dev/null +++ b/src/shared/client/Garage/GarageUIHandler.luau @@ -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 diff --git a/src/shared/client/Garage/InventoryClient.luau b/src/shared/client/Garage/InventoryClient.luau new file mode 100644 index 0000000..5a130bb --- /dev/null +++ b/src/shared/client/Garage/InventoryClient.luau @@ -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 diff --git a/src/shared/client/Garage/TankShowcase.luau b/src/shared/client/Garage/TankShowcase.luau new file mode 100644 index 0000000..d2694cf --- /dev/null +++ b/src/shared/client/Garage/TankShowcase.luau @@ -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 diff --git a/src/shared/client/Modules/GarageUIController.luau b/src/shared/client/Modules/GarageUIController.luau deleted file mode 100644 index 4714093..0000000 --- a/src/shared/client/Modules/GarageUIController.luau +++ /dev/null @@ -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 diff --git a/src/shared/data/DataTypes.luau b/src/shared/data/DataTypes.luau index 3a586a5..a9428dc 100644 --- a/src/shared/data/DataTypes.luau +++ b/src/shared/data/DataTypes.luau @@ -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 {} \ No newline at end of file +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 {} diff --git a/src/shared/data/SkinData.luau b/src/shared/data/SkinData.luau new file mode 100644 index 0000000..e69de29 diff --git a/src/shared/janitor/Promise.luau b/src/shared/janitor/Promise.luau deleted file mode 100644 index df3490c..0000000 --- a/src/shared/janitor/Promise.luau +++ /dev/null @@ -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: (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: (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 = { - andThen: (self: Promise, successHandler: (T...) -> ...any, failureHandler: ((...any) -> ...any)?) -> Promise, - andThenCall: (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: (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, -} - -type Signal = { - Connect: (self: Signal, 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: (promises: {TypedPromise}) -> TypedPromise<{T}>, - allSettled: (promise: {TypedPromise}) -> TypedPromise<{Status}>, - any: (promise: {TypedPromise}) -> TypedPromise, - defer: ( - executor: ( - resolve: (TReturn...) -> (), - reject: (...any) -> (), - onCancel: (abortHandler: (() -> ())?) -> boolean - ) -> () - ) -> TypedPromise, - delay: (seconds: number) -> TypedPromise, - each: ( - list: {T | TypedPromise}, - predicate: (value: T, index: number) -> TReturn | TypedPromise - ) -> TypedPromise<{TReturn}>, - fold: ( - list: {T | TypedPromise}, - reducer: (accumulator: TReturn, value: T, index: number) -> TReturn | TypedPromise - ) -> TypedPromise, - fromEvent: ( - event: Signal, - predicate: ((TReturn...) -> boolean)? - ) -> TypedPromise, - is: (object: any) -> boolean, - new: ( - executor: ( - resolve: (TReturn...) -> (), - reject: (...any) -> (), - onCancel: (abortHandler: (() -> ())?) -> boolean - ) -> () - ) -> TypedPromise, - onUnhandledRejection: (callback: (promise: TypedPromise, ...any) -> ()) -> () -> (), - promisify: (callback: (TArgs...) -> TReturn...) -> (TArgs...) -> TypedPromise, - race: (promises: {TypedPromise}) -> TypedPromise, - reject: (...any) -> TypedPromise<...any>, - resolve: (TReturn...) -> TypedPromise, - retry: ( - callback: (TArgs...) -> TypedPromise, - times: number, - TArgs... - ) -> TypedPromise, - retryWithDelay: ( - callback: (TArgs...) -> TypedPromise, - times: number, - seconds: number, - TArgs... - ) -> TypedPromise, - some: (promise: {TypedPromise}, count: number) -> TypedPromise<{T}>, - try: (callback: (TArgs...) -> TReturn..., TArgs...) -> TypedPromise, -} - -return Promise :: PromiseStatic? diff --git a/src/shared/shared/SharedUtils/sharedBotUtils.luau b/src/shared/shared/SharedUtils/sharedBotUtils.luau index 3919e7a..8dbf2ca 100644 --- a/src/shared/shared/SharedUtils/sharedBotUtils.luau +++ b/src/shared/shared/SharedUtils/sharedBotUtils.luau @@ -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