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": { "ServerScriptService": {
"$path": "src/server" "$path": "src/server"
}, },
"StarterPlayer": { "StarterPlayer": {
"StarterPlayerScripts": { "StarterPlayerScripts": {
"$path": "src/client" "$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 ReplicatedStorage = game:GetService("ReplicatedStorage")
local Template = require(script.Parent.Template)
local DataTypes = require(ReplicatedStorage.Data.DataTypes)
local OrderedDataStore = require("./OrderedDatastoreHandler") local OrderedDataStore = require("./OrderedDatastoreHandler")
local ValueNames = require("./ValueNames") local ValueNames = require("./ValueNames")
local DataStoreNames = require("./DataStoreNames") local DataStoreNames = require("./DataStoreNames")
local ProfileStore local ProfileStore
-- Store profiles from ProfileStore -- Store profiles from ProfileStore
DataManager.Profiles = {} DataManager.Profiles = {}
local synced = {} local synced = {}
function DataManager.getKey(player : Player,key : string) function DataManager.getKey(player: Player, key: string)
return "PLAYER_" .. player.UserId .. "_" .. key return "PLAYER_" .. player.UserId .. "_" .. key
end end
function DataManager.AddValue(player : Player,key : string,num) function DataManager.AddValue(player: Player, key: string, num)
local value = DataManager.GetValue(player,key) local value = DataManager.GetValue(player, key)
if not value then if not value then
return return
end end
DataManager.SetValue(player, key, value + num) DataManager.SetValue(player, key, value + num)
end end
function DataManager.SubValue(player : Player,key : number,num) function DataManager.SubValue(player: Player, key: number, num)
local value = DataManager.GetValue(player,key) local value = DataManager.GetValue(player, key)
if not value then if not value then
return return
end end
@ -36,7 +36,7 @@ function DataManager.SubValue(player : Player,key : number,num)
DataManager.SetValue(player, key, value - num) DataManager.SetValue(player, key, value - num)
end end
function DataManager.GetValue(player : Player,key : string) function DataManager.GetValue(player: Player, key: string)
local profile = DataManager.Profiles[player] local profile = DataManager.Profiles[player]
if not profile then if not profile then
return return
@ -44,64 +44,68 @@ function DataManager.GetValue(player : Player,key : string)
return profile.Data[key] return profile.Data[key]
end end
function DataManager.SetValue(player : Player,key : string,num) function DataManager.SetValue(player: Player, key: string, num)
local profile = DataManager.Profiles[player] local profile = DataManager.Profiles[player]
if not profile then if not profile then
return return
end end
profile.Data[key] = num profile.Data[key] = num
local keyName = DataManager.getKey(player,key) local keyName = DataManager.getKey(player, key)
local s = synced[keyName] local s = synced[keyName]
if not s then if not s then
return return
end end
local value = s.Value local value = s.Value
if value then if value then
value.Value = num value.Value = num
end end
local datastore = s.Datastore local datastore = s.Datastore
if datastore then if datastore then
OrderedDataStore.SaveData(player,datastore,num) OrderedDataStore.SaveData(player, datastore, num)
end end
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 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 end
function DataManager.SwitchTank(player : Player,tank : string) function DataManager.GetTanks(player: Player) end
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) function DataManager.SwitchTankSkin(player: Player, skinName: string) end
end
function DataManager.UnlockTankSkin(player : Player,skinName)
end
function DataManager.UnlockTank(player: Player, tank: string) end
function DataManager.UnlockTankSkin(player: Player, skinName) end
-- yh ill add it later -- yh ill add it later
function DataManager.SyncValue(player : Player,value : IntValue,key : string,datastore : string) function DataManager.SyncValue(player: Player, value: IntValue, key: string, datastore: string)
local keyName = DataManager.getKey(player,key) local keyName = DataManager.getKey(player, key)
synced[keyName] = {Value = value,Datastore = datastore} synced[keyName] = { Value = value, Datastore = datastore }
DataManager.SetValue(player,key,DataManager.GetValue(player,key)) DataManager.SetValue(player, key, DataManager.GetValue(player, key))
end end
--sup --sup
return DataManager return DataManager

View File

@ -1,135 +1,173 @@
local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players") local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService") 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 InventoryService = {}
local PlayersInventory : {[number] : {}} = {} -- ─────────────────────────────────────────
-- Internal
-- ─────────────────────────────────────────
local Tanks: {string} = {} type Inventory = typeof(Template.Inventory)
for _, tank in ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Models"):WaitForChild("Tanks"):GetChildren() do
if tank.Name ~= "Tank (Testing)" or game:GetService("RunService"):IsStudio() then local function getInventory(player: Player): Inventory?
table.insert(Tanks, tank.Name) local profile = DataManager.Profiles[player]
end if not profile then
end return nil
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
end end
print(profile.Data.Inventory)
return profile.Data.Inventory
end end
function InventoryService.GetModel(tankName : string, skinName : string) local function syncToClient(player: Player)
local base = ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Models"):WaitForChild("Tanks"):WaitForChild(tankName):FindFirstChild("Base") if true then
local skin = ReplicatedStorage:WaitForChild("Assets"):WaitForChild("Models"):WaitForChild("Tanks"):WaitForChild(tankName):FindFirstChild(skinName) return
if base and skin then
return base, skin
end end
local inv = getInventory(player)
--if inv then
-- rev_InventorySync:FireClient(player, inv)
--end
end end
function createPlayerInventory(player : Player) function InventoryService.GetInventory(player: Player): Inventory?
print("Creating inventory: ".. player.Name) return getInventory(player)
PlayersInventory[player.UserId] = {} end
local Loadouts: {[string] : string} = {}
local SelectedLoadout: string
local PlayerSkins: {[string] : string} = {}
local Inventory = {} -- Returns the skin currently equipped for a given tank.
Inventory["SelectedLoadout"] = "" function InventoryService.GetSelectedSkin(player: Player, tankName: string): string
Inventory["Loadouts"] = Loadouts local inv = getInventory(player)
Inventory["Tanks"] = {} if not inv then
Inventory["Skins"] = PlayerSkins return "Default"
PlayersInventory[player.UserId] = Inventory end
return inv.Loadouts[tankName] or "Default"
if Inventory["Tanks"] == nil or #Inventory["Tanks"] == 0 then end
Inventory["Tanks"] = {"Tank"}
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 end
if Inventory["SelectedLoadout"] == "" or Inventory["SelectedLoadout"] == nil or table.find(Inventory["Tanks"], Inventory["SelectedLoadout"]) then inv.SelectedTank = tankName
print(InventoryService.SelectLoadout(player, Inventory["Tanks"][1])) 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 end
for _, tank in Inventory["Tanks"] do local ownedSkins = inv.Skins[tank]
if Inventory["Skins"][tank] == nil or #Inventory["Skins"][tank] == 0 then if not ownedSkins or not table.find(ownedSkins, skin) then
Inventory["Skins"][tank] = {"Default"} return false, "Skin not owned"
end
end end
if not CheckIfLoadoutExists(player, Inventory["Tanks"][1]) then inv.Loadouts[tank] = skin
InventoryService.UpdateLoadout(player, Inventory["SelectedLoadout"], Inventory["Skins"][Inventory["Tanks"][1]][1]) 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 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 end
function InventoryService.GetAllTanks() -- ─────────────────────────────────────────
return Tanks -- Init (called by ModuleLoader)
end -- ─────────────────────────────────────────
function InventoryService.GetAllSkins() function InventoryService:Init()
return Skins -- Handle all client requests through one RemoteFunction.
end -- Each action is validated server-side — client input is never trusted.
rfn_Inventory.OnServerInvoke = function(player: Player, action: string, ...)
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 = {...}
if action == "GetInventory" then if action == "GetInventory" then
return InventoryService.GetInventory(player) return InventoryService.GetInventory(player)
elseif action == "AllInventories" then elseif action == "SelectTank" then
return InventoryService.AllInventories() local ok, err = InventoryService.SelectTank(player, ...)
return ok, err
elseif action == "UpdateLoadout" then elseif action == "UpdateLoadout" then
InventoryService.UpdateLoadout(player, args[1], args[2]) local ok, err = InventoryService.UpdateLoadout(player, ...)
elseif action == "SelectLoadout" then return ok, err
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])
end end
return false, "Unknown action: " .. tostring(action)
end 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 end
return InventoryService 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 { return {
Money = 50, Money = 50,
Wins = 0, Wins = 0,
UnlockedSkins = {}, Inventory = Inventory,
UnlockedTanks = {}, }
UnlockedAccessories = {},
Loadouts = {},
CurrentLoadout = "Default",
CurrentTank = "Tank"
}

View File

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

View File

@ -1,26 +1,26 @@
local ClientController = {} local ClientController = {}
local Players = game:GetService("Players") local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = game:GetService("ReplicatedStorage")
local localPlayer = Players.LocalPlayer local localPlayer = Players.LocalPlayer
local Data = ReplicatedStorage.Data -- server modules (read-only on client) local Data = ReplicatedStorage.Data -- server modules (read-only on client)
local GameStates = require(Data.GameState) local GameStates = require(Data.GameState)
local ClientModules = script.Parent.Modules local ClientModules = script.Parent.Parent.Modules
local AimRenderer = require(ClientModules.AimRenderer) local AimRenderer = require(ClientModules.AimRenderer)
local InputHandler = require(ClientModules.InputHandler) local InputHandler = require(ClientModules.InputHandler)
local BotSelectUI = require(ClientModules.BotSelectUI) local BotSelectUI = require(ClientModules.BotSelectUI)
local CameraController = require(ClientModules.CameraController) local CameraController = require(ClientModules.CameraController)
local BotAbilityUI = require(ClientModules.BotAbilityUI) local BotAbilityUI = require(ClientModules.BotAbilityUI)
local Remote = ReplicatedStorage.Remote local Remote = ReplicatedStorage.Remote
local rev_SubmitAction = Remote.SubmitAction local rev_SubmitAction = Remote.SubmitAction
local rev_UpdateGameState = Remote.UpdateGameState local rev_UpdateGameState = Remote.UpdateGameState
local rev_HpUpdated = Remote.HpUpdated local rev_HpUpdated = Remote.HpUpdated
local rev_BotDied = Remote.BotDied local rev_BotDied = Remote.BotDied
--finish later brochacho chip --finish later brochacho chip
local turns = 1 local turns = 1
@ -28,52 +28,39 @@ local turns = 1
local PLAYING = "PLAYING" local PLAYING = "PLAYING"
function ClientController:Init() function ClientController:Init()
-- React to server phase changes -- React to server phase changes
rev_UpdateGameState.OnClientEvent:Connect(function(phase) rev_UpdateGameState.OnClientEvent:Connect(function(phase)
shared.Phase = phase shared.Phase = phase
if phase == GameStates.LOBBY then if phase == GameStates.LOBBY then
turns = 1 turns = 1
CameraController.ResetCamera() CameraController.ResetCamera()
elseif phase == GameStates.GRACE then elseif phase == GameStates.GRACE then
CameraController.WideMapView() CameraController.WideMapView()
BotAbilityUI.NewAbilities() BotAbilityUI.NewAbilities()
elseif phase == GameStates.AIMING then elseif phase == GameStates.AIMING then
AimRenderer.UnHighlight() AimRenderer.UnHighlight()
InputHandler.enable(function(input) InputHandler.enable(function(input)
-- Player confirmed their action — send to server -- Player confirmed their action — send to server
rev_SubmitAction:FireServer(input) rev_SubmitAction:FireServer(input)
InputHandler.disable() InputHandler.disable()
BotAbilityUI.DisableAll() BotAbilityUI.DisableAll()
AimRenderer.Highlight() AimRenderer.Highlight()
end, turns)
end,turns)
elseif phase == GameStates.RESOLVING then elseif phase == GameStates.RESOLVING then
turns += 1 turns += 1
BotAbilityUI.HideGUI() BotAbilityUI.HideGUI()
InputHandler.disable() InputHandler.disable()
AimRenderer.hide() AimRenderer.hide()
elseif phase == "Results" then elseif phase == "Results" then
if localPlayer:HasTag(PLAYING) then if localPlayer:HasTag(PLAYING) then
BotAbilityUI.HideGUI() BotAbilityUI.HideGUI()
CameraController.ResetCamera() CameraController.ResetCamera()
end end
-- TODO: show win screen -- TODO: show win screen
end end
end) 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 = { export type FireData = {
weapon : string, weapon: string,
angle : number, angle: number,
power : number, power: number,
origin : Vector3, origin: Vector3,
specialArgs : {any} specialArgs: { any },
} }
export type VoteOptionData = { export type VoteOptionData = {
Name : string, Name: string,
DisplayName : string, DisplayName: string,
DisplayImage : string DisplayImage: string,
} }
export type Loadout = { export type Loadout = {
Skin : string, Tank: string,
HeadAccessory : 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 sharedBotUtils = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage") local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Assets = ReplicatedStorage.Assets
local Models = Assets.Models
local TankFolder = Models.Tanks :: Folder
local rData = ReplicatedStorage.Data local rData = ReplicatedStorage.Data
local DataTypes = require(ReplicatedStorage.Data.DataTypes)
local BotData = require(rData.BotData) local BotData = require(rData.BotData)
local GameState = require(rData.GameState) local GameState = require(rData.GameState)
local WeaponData = require(rData.WeaponData) local WeaponData = require(rData.WeaponData)
local sUtils = ReplicatedStorage.Shared.SharedUtils local sUtils = ReplicatedStorage.Shared.SharedUtils
local TwoDimensionUtils = require(sUtils.TwoDimensionUtils) local TwoDimensionUtils = require(sUtils.TwoDimensionUtils)
local WeldModule = require(script.Parent.WeldModule)
local wBots = workspace.Bots local wBots = workspace.Bots
@ -19,32 +24,97 @@ local NO_COOLDOWN = {
local COOLDOWN_ABILITY = { local COOLDOWN_ABILITY = {
NormalAbility = 2, 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) local model = sharedBotUtils.FindBotModel(UserId)
return BotData[model:GetAttribute("Type")] return BotData[model:GetAttribute("Type")]
end end
function sharedBotUtils.GetWeaponName(UserId : number,weaponType : string) function sharedBotUtils.GetWeaponName(UserId: number, weaponType: string)
local bData = sharedBotUtils.GetBotData(UserId) local bData = sharedBotUtils.GetBotData(UserId)
local name = bData.weapons[weaponType] or weaponType local name = bData.weapons[weaponType] or weaponType
return name return name
end end
function sharedBotUtils.GetTurret(UserId : number) : Model function sharedBotUtils.GetTurret(UserId: number): Model
local model = sharedBotUtils.FindBotModel(UserId) local model = sharedBotUtils.FindBotModel(UserId)
return model.RotatePart return model.RotatePart
end end
function sharedBotUtils.RotateTurret(UserId : number,launchDirection) function sharedBotUtils.RotateTurret(UserId: number, launchDirection)
local turret = sharedBotUtils.GetTurret(UserId) local turret = sharedBotUtils.GetTurret(UserId)
if not turret then return end if not turret then
return
end
local weld = turret.PrimaryPart:FindFirstChildOfClass("Weld") local weld = turret.PrimaryPart:FindFirstChildOfClass("Weld")
if not weld then return end if not weld then
return
end
local base = weld.Part0 local base = weld.Part0
local turretPart = weld.Part1 local turretPart = weld.Part1
@ -57,29 +127,29 @@ function sharedBotUtils.RotateTurret(UserId : number,launchDirection)
weld.C0 = base.CFrame:Inverse() * worldCF weld.C0 = base.CFrame:Inverse() * worldCF
end end
function sharedBotUtils.GetWeaponsData(UserId : number) : {[string] : WeaponData.WeaponData} function sharedBotUtils.GetWeaponsData(UserId: number): { [string]: WeaponData.WeaponData }
local tbl = {} local tbl = {}
local bData = sharedBotUtils.GetBotData(UserId) local bData = sharedBotUtils.GetBotData(UserId)
tbl.Move = WeaponData.Move tbl.Move = WeaponData.Move
tbl.Missile = WeaponData.Missile tbl.Missile = WeaponData.Missile
for weaponType,weaponName in pairs(bData.weapons) do for weaponType, weaponName in pairs(bData.weapons) do
local wData = WeaponData[weaponName] local wData = WeaponData[weaponName]
tbl[weaponType] = wData tbl[weaponType] = wData
end end
return tbl return tbl
end end
function sharedBotUtils.GetBotPosition(UserId : number) function sharedBotUtils.GetBotPosition(UserId: number)
local model = sharedBotUtils.FindBotModel(UserId) local model = sharedBotUtils.FindBotModel(UserId)
return model.PrimaryPart.Position return model.PrimaryPart.Position
end end
function sharedBotUtils.CanUseAbility(UserId : number, abilityType : string) function sharedBotUtils.CanUseAbility(UserId: number, abilityType: string)
if true then if true then
return true return true
end end
local isInCooldown = sharedBotUtils.AbilityInCooldown(UserId,abilityType) local isInCooldown = sharedBotUtils.AbilityInCooldown(UserId, abilityType)
if isInCooldown then if isInCooldown then
return return
end end
@ -87,64 +157,66 @@ function sharedBotUtils.CanUseAbility(UserId : number, abilityType : string)
if Phase ~= GameState.AIMING then if Phase ~= GameState.AIMING then
return return
end end
return true return true
end end
function sharedBotUtils.GetCooldowns(UserId : number) : Folder function sharedBotUtils.GetCooldowns(UserId: number): Folder
local model = sharedBotUtils.FindBotModel(UserId) local model = sharedBotUtils.FindBotModel(UserId)
return model.Cooldowns return model.Cooldowns
end end
function sharedBotUtils.AbilityInCooldown(UserId,ability : string) function sharedBotUtils.AbilityInCooldown(UserId, ability: string)
if NO_COOLDOWN[ability] then if NO_COOLDOWN[ability] then
return return
end end
local cooldown = sharedBotUtils.GetCooldowns(UserId) local cooldown = sharedBotUtils.GetCooldowns(UserId)
local cAbil : IntValue = cooldown:FindFirstChild(ability) local cAbil: IntValue = cooldown:FindFirstChild(ability)
if not cAbil then 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) print(cooldown)
return return
end end
return COOLDOWN_ABILITY[ability] ~= cAbil.Value return COOLDOWN_ABILITY[ability] ~= cAbil.Value
end end
function sharedBotUtils.FindBotModel(UserId : number) : Model function sharedBotUtils.FindBotModel(UserId: number): Model
local b = wBots:WaitForChild(tostring(UserId)) local b = wBots:WaitForChild(tostring(UserId))
return b return b
end end
function sharedBotUtils.GetShootPos(UserId : number) function sharedBotUtils.GetShootPos(UserId: number)
local turret = sharedBotUtils.GetTurret(UserId) local turret = sharedBotUtils.GetTurret(UserId)
if not turret then if not turret then
warn("no turret model >:(") warn("no turret model >:(")
return sharedBotUtils.GetBotPosition(UserId) return sharedBotUtils.GetBotPosition(UserId)
end end
local Shoot = turret:FindFirstChild("Shoot") local Shoot = turret:FindFirstChild("Shoot")
if not Shoot or not Shoot.PrimaryPart then if not Shoot or not Shoot.PrimaryPart then
warn("either not shoot model or not shoot primarypart") warn("either not shoot model or not shoot primarypart")
return turret:GetPivot().Position return turret:GetPivot().Position
end end
return Shoot.PrimaryPart.Position return Shoot.PrimaryPart.Position
end end
function sharedBotUtils.GetBotRoot(UserId : number) : Part function sharedBotUtils.GetBotRoot(UserId: number): Part
local b = sharedBotUtils.FindBotModel(UserId) local b = sharedBotUtils.FindBotModel(UserId)
return b.PrimaryPart return b.PrimaryPart
end end
function sharedBotUtils.IsObjectOnFloor(model) : boolean function sharedBotUtils.IsObjectOnFloor(model): boolean
local root = model.PrimaryPart 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) -- Get the model size (assumes the root's parent is the full model)
local model = root.Parent 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 local origin = root.Position
-- Raycast downward relative to half the model height + small buffer -- Raycast downward relative to half the model height + small buffer
@ -152,7 +224,7 @@ function sharedBotUtils.IsObjectOnFloor(model) : boolean
local params = RaycastParams.new() local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {model} params.FilterDescendantsInstances = { model }
local result = workspace:Raycast(origin, direction, params) local result = workspace:Raycast(origin, direction, params)
if not result then if not result then
@ -166,7 +238,7 @@ function sharedBotUtils.IsObjectOnFloor(model) : boolean
-- must be close to ground and not moving vertically -- must be close to ground and not moving vertically
return distance <= (size.Y / 2 + 0.5) and math.abs(velY) < 1 return distance <= (size.Y / 2 + 0.5) and math.abs(velY) < 1
end end
function sharedBotUtils.GetBotVelocity(UserId : number) function sharedBotUtils.GetBotVelocity(UserId: number)
local root = sharedBotUtils.GetBotRoot(UserId) local root = sharedBotUtils.GetBotRoot(UserId)
return root.AssemblyLinearVelocity return root.AssemblyLinearVelocity
end end