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,6 +2,8 @@ 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")
@ -9,8 +11,6 @@ local DataStoreNames = require("./DataStoreNames")
local ProfileStore
-- Store profiles from ProfileStore
DataManager.Profiles = {}
@ -68,31 +68,35 @@ function DataManager.SetValue(player : Player,key : string,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)
function DataManager.GetTanks(player: Player) end
end
function DataManager.GetTankData(player: Player, name: string) end
function DataManager.SwitchTankSkin(player : Player,skinName : string)
function DataManager.SwitchTank(player: Player, tank: string) end
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

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} = {}
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"}
function InventoryService.GetInventory(player: Player): Inventory?
return getInventory(player)
end
if Inventory["SelectedLoadout"] == "" or Inventory["SelectedLoadout"] == nil or table.find(Inventory["Tanks"], Inventory["SelectedLoadout"]) then
print(InventoryService.SelectLoadout(player, Inventory["Tanks"][1]))
-- 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
for _, tank in Inventory["Tanks"] do
if Inventory["Skins"][tank] == nil or #Inventory["Skins"][tank] == 0 then
Inventory["Skins"][tank] = {"Default"}
end
function InventoryService.GetCurrentLoadout(player: Player)
local inv = getInventory(player)
local current = inv.CurrentLoadouts[inv.SelectedTank]
return current
end
if not CheckIfLoadoutExists(player, Inventory["Tanks"][1]) then
InventoryService.UpdateLoadout(player, Inventory["SelectedLoadout"], Inventory["Skins"][Inventory["Tanks"][1]][1])
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
return Inventory
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
function InventoryService.GetAllTanks()
return Tanks
end
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
inv.SelectedTank = tankName
syncToClient(player)
return true
else
return false
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
function InventoryService.GetInventory(player : Player)
return PlayersInventory[player.UserId]
local ownedSkins = inv.Skins[tank]
if not ownedSkins or not table.find(ownedSkins, skin) then
return false, "Skin not owned"
end
function InventoryService.AllInventories()
return PlayersInventory
inv.Loadouts[tank] = skin
syncToClient(player)
return true
end
function InventoryService.Init()
for _, player in Players:GetPlayers() do
createPlayerInventory(player)
function InventoryService.UnlockTank(player: Player, tankName: string)
local inv = getInventory(player)
if not inv then
return
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 = {...}
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
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
-- ─────────────────────────────────────────
-- Init (called by ModuleLoader)
-- ─────────────────────────────────────────
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

@ -7,7 +7,7 @@ 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,10 +18,9 @@ local TankFolder = rModels.Tanks
local rData = ReplicatedStorage.Data
local BotData = require(rData.BotData)
type self = {
accuracy: number,
HUD : typeof(rHUD.HUD_TANK)
HUD: typeof(rHUD.HUD_TANK),
}
export type Bot = typeof(setmetatable({} :: self, Bot)) & GameObject.GameObject
@ -51,7 +50,6 @@ function Bot.new(player : Player, name, character)
local h = HealthComponent.new(self.key, bData.maxHp)
h.HealthChangedSignal:Connect(function(oldHP, newHP)
local diff = oldHP - newHP
if oldHP > newHP then
@ -72,12 +70,9 @@ function Bot.new(player : Player, name, character)
self.Components.Health:Init()
return self
end
function Bot:CreateModel()
print(self.name)
print(TankFolder)
@ -92,23 +87,19 @@ function Bot:CreateModel()
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:_ResetMass()
end
function Bot:DisplayHealth(hp)
local self = self :: Bot
local Health = self.Components.Health
hp = hp or Health.Health
local max = Health.MaxHealth
@ -131,7 +122,6 @@ end
function Bot:die()
self.model:Destroy()
end
return Bot

View File

@ -8,7 +8,7 @@ local localPlayer = Players.LocalPlayer
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)
@ -28,20 +28,16 @@ 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)
@ -50,30 +46,21 @@ function ClientController:Init()
InputHandler.disable()
BotAbilityUI.DisableAll()
AimRenderer.Highlight()
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

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}
specialArgs: { any },
}
export type VoteOptionData = {
Name: string,
DisplayName: string,
DisplayImage : string
DisplayImage: string,
}
export type Loadout = {
Skin : string,
Tank: string,
Skin: string?,
HeadAccessory: string?,
Name: string?,
Locked: boolean?, -- can be edited
}
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 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,9 +24,70 @@ local NO_COOLDOWN = {
local COOLDOWN_ABILITY = {
NormalAbility = 2,
SpecialAbility = 3
SpecialAbility = 3,
}
--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")]
@ -41,10 +107,14 @@ end
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
@ -140,7 +210,9 @@ function sharedBotUtils.GetBotRoot(UserId : number) : Part
end
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