Repository: berezaa/vesteria
Branch: master
Commit: d99de18cff82
Files: 454
Total size: 2.6 MB
Directory structure:
gitextract_1n4n_pm1/
├── .gitattributes
├── .github/
│ └── workflows/
│ └── luacheck.yml
├── .gitignore
├── .luacheckrc
├── .vscode/
│ └── settings.json
├── LICENSE
├── assets/
│ └── readme.md
├── default.project.json
├── readme.md
├── roblox.toml
├── selene.toml
├── src/
│ ├── Chat/
│ │ ├── BubbleChat.client.lua
│ │ ├── ChatModules/
│ │ │ ├── ChatCommandsTeller.lua
│ │ │ ├── ChatFloodDetector.lua
│ │ │ ├── ChatMessageValidator.lua
│ │ │ ├── CustomCommands.lua
│ │ │ ├── ExtraDataInitializer.lua
│ │ │ ├── FriendJoinNotifier.lua
│ │ │ ├── GuildChat.lua
│ │ │ ├── MeCommand.lua
│ │ │ ├── MuteSpeaker.lua
│ │ │ ├── PartyChat.lua
│ │ │ ├── PrivateMessaging.lua
│ │ │ ├── TeamChat.lua
│ │ │ ├── Toggle.lua
│ │ │ └── init.meta.json
│ │ ├── ChatScript/
│ │ │ ├── ChatMain/
│ │ │ │ ├── ChannelsBar.lua
│ │ │ │ ├── ChannelsTab.lua
│ │ │ │ ├── ChatBar.lua
│ │ │ │ ├── ChatChannel.lua
│ │ │ │ ├── ChatWindow.lua
│ │ │ │ ├── CommandProcessor.lua
│ │ │ │ ├── CurveUtil.lua
│ │ │ │ ├── MessageLabelCreator.lua
│ │ │ │ ├── MessageLogDisplay.lua
│ │ │ │ ├── MessageSender.lua
│ │ │ │ ├── ObjectPool.lua
│ │ │ │ └── init.lua
│ │ │ └── init.client.lua
│ │ ├── ChatServiceRunner/
│ │ │ ├── ChatChannel.lua
│ │ │ ├── ChatService.lua
│ │ │ ├── Speaker.lua
│ │ │ ├── Util.lua
│ │ │ └── init.server.lua
│ │ └── ClientChatModules/
│ │ ├── ChatConstants.lua
│ │ ├── ChatLocalization.lua
│ │ ├── ChatSettings.lua
│ │ ├── CommandModules/
│ │ │ ├── ClearMessages.lua
│ │ │ ├── Commands.lua
│ │ │ ├── DeveloperConsole.lua
│ │ │ ├── GetVersion.lua
│ │ │ ├── Guild.lua
│ │ │ ├── Party.lua
│ │ │ ├── SwallowGuestChat.lua
│ │ │ ├── SwitchChannel.lua
│ │ │ ├── Team.lua
│ │ │ ├── Toggle.lua
│ │ │ ├── Util.lua
│ │ │ ├── Whisper.lua
│ │ │ └── init.meta.json
│ │ ├── MessageCreatorModules/
│ │ │ ├── DefaultChatMessage.lua
│ │ │ ├── MeCommandMessage.lua
│ │ │ ├── SetCoreMessage.lua
│ │ │ ├── SystemMessage.lua
│ │ │ ├── UnknownMessage.lua
│ │ │ ├── Util.lua
│ │ │ ├── WelcomeMessage.lua
│ │ │ ├── WhisperMessage.lua
│ │ │ └── init.meta.json
│ │ └── init.meta.json
│ ├── ReplicatedStorage/
│ │ ├── abilityBookLookup/
│ │ │ ├── admin.lua
│ │ │ ├── adventurer.lua
│ │ │ ├── assassin.lua
│ │ │ ├── berserker.lua
│ │ │ ├── cleric.lua
│ │ │ ├── hunter.lua
│ │ │ ├── init.lua
│ │ │ ├── knight.lua
│ │ │ ├── mage.lua
│ │ │ ├── paladin.lua
│ │ │ ├── ranger.lua
│ │ │ ├── shadow.lua
│ │ │ ├── sorcerer.lua
│ │ │ ├── trickster.lua
│ │ │ ├── warlock.lua
│ │ │ └── warrior.lua
│ │ ├── abilityLookup/
│ │ │ ├── groundSlam.lua
│ │ │ ├── init.lua
│ │ │ └── regeneration.lua
│ │ ├── accessoryLookup.lua
│ │ ├── beam-stuf.lua
│ │ ├── blessingLookup.lua
│ │ ├── defaultCharacterAppearance.lua
│ │ ├── defaultMonsterState.lua
│ │ ├── defaultPetState.lua
│ │ ├── dialogue/
│ │ │ ├── genericShopkeeper.lua
│ │ │ ├── init.meta.json
│ │ │ └── testNPCDialog.lua
│ │ ├── dialogueLookup.lua
│ │ ├── itemAttributes.lua
│ │ ├── itemData/
│ │ │ ├── 100% armor defense scroll.lua
│ │ │ ├── 100% headgear defense scroll.lua
│ │ │ ├── 100% weapon attack scroll.lua
│ │ │ ├── apple.lua
│ │ │ ├── arrow.lua
│ │ │ ├── chicken egg.lua
│ │ │ ├── chicken feather.lua
│ │ │ ├── chicken leg.lua
│ │ │ ├── coin piece.lua
│ │ │ ├── copper axe.lua
│ │ │ ├── copper ore.lua
│ │ │ ├── copper pickaxe.lua
│ │ │ ├── copper sword.lua
│ │ │ ├── fish.lua
│ │ │ ├── frying pan.lua
│ │ │ ├── health potion.lua
│ │ │ ├── hog headdress.lua
│ │ │ ├── hog meat.lua
│ │ │ ├── hog tusk dagger.lua
│ │ │ ├── init.lua
│ │ │ ├── iron ore.lua
│ │ │ ├── item lore.lua
│ │ │ ├── item renamer.lua
│ │ │ ├── leather tunic.lua
│ │ │ ├── mana potion.lua
│ │ │ ├── megaphone.lua
│ │ │ ├── mushroom beard.lua
│ │ │ ├── mushroom hat.lua
│ │ │ ├── mushroom mini.lua
│ │ │ ├── mushroom soup.lua
│ │ │ ├── mushroom spore.lua
│ │ │ ├── oak axe.lua
│ │ │ ├── oak pickaxe.lua
│ │ │ ├── oak polearm.lua
│ │ │ ├── oak wood.lua
│ │ │ ├── pear.lua
│ │ │ ├── rune mushtown.lua
│ │ │ ├── rune nilgarf.lua
│ │ │ ├── rusty dagger.lua
│ │ │ ├── shoulder pads 2.lua
│ │ │ ├── shoulder pads 3.lua
│ │ │ ├── stick.lua
│ │ │ ├── stone axe.lua
│ │ │ ├── stone pickaxe.lua
│ │ │ ├── stone.lua
│ │ │ ├── wooden bow.lua
│ │ │ ├── wooden club.lua
│ │ │ ├── wooden dagger.lua
│ │ │ ├── wooden fishing pole.lua
│ │ │ ├── wooden sword.lua
│ │ │ ├── worn boots.lua
│ │ │ └── yellow puffer fish.lua
│ │ ├── loreBooks.lua
│ │ ├── modules/
│ │ │ ├── ability_utilities.lua
│ │ │ ├── camera_shaker/
│ │ │ │ ├── CameraShakeInstance.lua
│ │ │ │ ├── CameraShakePresets.lua
│ │ │ │ └── init.lua
│ │ │ ├── client_quest_util.lua
│ │ │ ├── client_utilities.lua
│ │ │ ├── configuration.lua
│ │ │ ├── contains.lua
│ │ │ ├── damage.lua
│ │ │ ├── debug.lua
│ │ │ ├── detection.lua
│ │ │ ├── economy.lua
│ │ │ ├── effects.lua
│ │ │ ├── enchantment.lua
│ │ │ ├── entityUtilities.lua
│ │ │ ├── events.lua
│ │ │ ├── init.lua
│ │ │ ├── init.meta.json
│ │ │ ├── levels.lua
│ │ │ ├── levels_old.lua
│ │ │ ├── levels_older.lua
│ │ │ ├── localization.lua
│ │ │ ├── mapping.lua
│ │ │ ├── network.lua
│ │ │ ├── pathfinding.lua
│ │ │ ├── physics.lua
│ │ │ ├── placeSetup.lua
│ │ │ ├── projectile.lua
│ │ │ ├── projectile_predictive.lua
│ │ │ ├── tableUtil.lua
│ │ │ ├── tempData.lua
│ │ │ ├── terrainUtil.lua
│ │ │ ├── thread.lua
│ │ │ ├── tween.lua
│ │ │ └── utilities.lua
│ │ ├── monsterLookup/
│ │ │ ├── Baby Shroom.lua
│ │ │ ├── Chicken.lua
│ │ │ ├── Dummy.lua
│ │ │ ├── Elder Shroom.lua
│ │ │ ├── Shroom.lua
│ │ │ └── init.lua
│ │ ├── perkLookup/
│ │ │ ├── colosseum equipment.lua
│ │ │ ├── dunes equipment.lua
│ │ │ ├── init.lua
│ │ │ ├── misc.lua
│ │ │ ├── mokotuaa equipment.lua
│ │ │ ├── mushroom equipment.lua
│ │ │ └── spider equipment.lua
│ │ ├── professionLookup/
│ │ │ ├── fishing.lua
│ │ │ └── init.lua
│ │ ├── questLookupNew/
│ │ │ ├── init.lua
│ │ │ └── testQuest.lua
│ │ ├── sounds.lua
│ │ ├── statusEffectLookup/
│ │ │ ├── ablaze.lua
│ │ │ ├── empower.lua
│ │ │ ├── explode.lua
│ │ │ ├── heat exhausted.lua
│ │ │ ├── init.lua
│ │ │ ├── mystically bound.lua
│ │ │ ├── poison.lua
│ │ │ ├── potion.lua
│ │ │ ├── ranger stance.lua
│ │ │ ├── refreshed.lua
│ │ │ ├── regenerate.lua
│ │ │ ├── stealth.lua
│ │ │ ├── stunned.lua
│ │ │ ├── taunted.lua
│ │ │ └── weakened by venom.lua
│ │ ├── statusEffectsV3/
│ │ │ ├── ablaze.lua
│ │ │ ├── bleed.lua
│ │ │ ├── init.lua
│ │ │ └── regenerate.lua
│ │ └── temporaryEquipment/
│ │ ├── init.meta.json
│ │ └── lantern/
│ │ ├── application.lua
│ │ └── init.meta.json
│ ├── ServerScriptService/
│ │ ├── ChatServiceRunner/
│ │ │ ├── ChatChannel.lua
│ │ │ ├── ChatService.lua
│ │ │ ├── Speaker.lua
│ │ │ ├── Util.lua
│ │ │ └── init.server.lua
│ │ ├── analytics/
│ │ │ ├── GameAnalytics/
│ │ │ │ ├── INSTRUCTIONS.server.lua
│ │ │ │ ├── bit.lua
│ │ │ │ ├── init.lua
│ │ │ │ └── lockbox/
│ │ │ │ ├── digest/
│ │ │ │ │ ├── init.meta.json
│ │ │ │ │ └── sha2_256.lua
│ │ │ │ ├── init.lua
│ │ │ │ ├── mac/
│ │ │ │ │ ├── hmac.lua
│ │ │ │ │ └── init.meta.json
│ │ │ │ └── util/
│ │ │ │ ├── array.lua
│ │ │ │ ├── base64.lua
│ │ │ │ ├── bit.lua
│ │ │ │ ├── init.meta.json
│ │ │ │ ├── queue.lua
│ │ │ │ └── stream.lua
│ │ │ ├── init.server.lua
│ │ │ ├── key.lua
│ │ │ └── testing.lua
│ │ ├── contents/
│ │ │ ├── datastoreInterface.lua
│ │ │ ├── inputStorage.lua
│ │ │ ├── manager_ability.lua
│ │ │ ├── manager_bounty.lua
│ │ │ ├── manager_challenge.lua
│ │ │ ├── manager_chat.lua
│ │ │ ├── manager_crafting.lua
│ │ │ ├── manager_damage.lua
│ │ │ ├── manager_dayNightCycle.lua
│ │ │ ├── manager_effects.lua
│ │ │ ├── manager_enchant.lua
│ │ │ ├── manager_events.lua
│ │ │ ├── manager_exploit.lua
│ │ │ ├── manager_firepit.lua
│ │ │ ├── manager_fishing.lua
│ │ │ ├── manager_gift.lua
│ │ │ ├── manager_guild.lua
│ │ │ ├── manager_interact.lua
│ │ │ ├── manager_item.lua
│ │ │ ├── manager_messagingService.lua
│ │ │ ├── manager_monster.lua
│ │ │ ├── manager_network.lua
│ │ │ ├── manager_orb.lua
│ │ │ ├── manager_party.lua
│ │ │ ├── manager_pet.lua
│ │ │ ├── manager_player.lua
│ │ │ ├── manager_product.lua
│ │ │ ├── manager_profession.lua
│ │ │ ├── manager_pvp.lua
│ │ │ ├── manager_quest.lua
│ │ │ ├── manager_resources.lua
│ │ │ ├── manager_security.lua
│ │ │ ├── manager_servers.lua
│ │ │ ├── manager_statusEffect.lua
│ │ │ ├── manager_taxi/
│ │ │ │ ├── dialogue.lua
│ │ │ │ └── init.lua
│ │ │ ├── manager_teleport.lua
│ │ │ ├── manager_trade.lua
│ │ │ ├── manager_treasure.lua
│ │ │ ├── secretCodes/
│ │ │ │ ├── init.lua
│ │ │ │ └── verify/
│ │ │ │ ├── init.lua
│ │ │ │ └── key.lua
│ │ │ └── teleport.lua
│ │ ├── modules/
│ │ │ ├── cannonScript.lua
│ │ │ ├── enterGame.lua
│ │ │ └── firepitScript.lua
│ │ └── server.server.lua
│ ├── StarterGui/
│ │ ├── abilities.lua
│ │ ├── bossHealth.lua
│ │ ├── chat.lua
│ │ ├── checkpoint.lua
│ │ ├── customization.lua
│ │ ├── dataFail.lua
│ │ ├── deathScreen.lua
│ │ ├── dialogue.lua
│ │ ├── dyePreview.lua
│ │ ├── enchant.lua
│ │ ├── equipment.lua
│ │ ├── focus.lua
│ │ ├── fx.lua
│ │ ├── guild.lua
│ │ ├── hotbarHandler.lua
│ │ ├── input.lua
│ │ ├── inspectPlayer.lua
│ │ ├── inspectPlayerPreview.lua
│ │ ├── interactShell.lua
│ │ ├── interaction.lua
│ │ ├── inventory.lua
│ │ ├── itemAcquistion.lua
│ │ ├── leaderboard.lua
│ │ ├── loadingScreen.lua
│ │ ├── loreBook.lua
│ │ ├── menuButtons.lua
│ │ ├── mobileButtons.lua
│ │ ├── money.lua
│ │ ├── monsterBook.lua
│ │ ├── notifications.lua
│ │ ├── party.lua
│ │ ├── playerInfo.lua
│ │ ├── playerInfoClone.lua
│ │ ├── playerInteract.lua
│ │ ├── products.lua
│ │ ├── prompting.lua
│ │ ├── prompting_Fullscreen.lua
│ │ ├── questLog.lua
│ │ ├── save.client.lua
│ │ ├── serverBrowser.lua
│ │ ├── serverMessage.lua
│ │ ├── settings.lua
│ │ ├── shop.lua
│ │ ├── social.lua
│ │ ├── stamina.lua
│ │ ├── statusBars.lua
│ │ ├── statusEffects.lua
│ │ ├── storage.lua
│ │ ├── taxi.lua
│ │ ├── textInputPrompt.lua
│ │ ├── trading.lua
│ │ ├── uiCreator.lua
│ │ ├── unstucker.lua
│ │ └── verifications.lua
│ └── StarterPlayer/
│ ├── StarterCharacterScripts/
│ │ ├── Animate.client.lua
│ │ ├── Health.client.lua
│ │ ├── LocalTeleportStuff.client.lua
│ │ └── Sound.client.lua
│ └── StarterPlayerScripts/
│ ├── ChatScript/
│ │ ├── ChatMain/
│ │ │ ├── ChannelsBar.lua
│ │ │ ├── ChannelsTab.lua
│ │ │ ├── ChatBar.lua
│ │ │ ├── ChatChannel.lua
│ │ │ ├── ChatWindow.lua
│ │ │ ├── CommandProcessor.lua
│ │ │ ├── CurveUtil.lua
│ │ │ ├── MessageLabelCreator.lua
│ │ │ ├── MessageLogDisplay.lua
│ │ │ ├── MessageSender.lua
│ │ │ ├── ObjectPool.lua
│ │ │ └── init.lua
│ │ └── init.client.lua
│ ├── PlayerScriptsLoader.client.lua
│ ├── assets/
│ │ ├── animations/
│ │ │ ├── axeAnimations.lua
│ │ │ ├── bowAnimations.lua
│ │ │ ├── bowToolAnimations_noChar.lua
│ │ │ ├── daggerAnimations.lua
│ │ │ ├── dualAnimations.lua
│ │ │ ├── emoteAnimations.lua
│ │ │ ├── fishing-rodAnimations.lua
│ │ │ ├── greatswordAnimations.lua
│ │ │ ├── init.meta.json
│ │ │ ├── movementAnimations.lua
│ │ │ ├── pickaxeAnimations.lua
│ │ │ ├── staffAnimations.lua
│ │ │ ├── swordAndShieldAnimations.lua
│ │ │ └── swordAnimations.lua
│ │ ├── attackableScript.lua
│ │ ├── damageInterfaces/
│ │ │ ├── axe.lua
│ │ │ ├── bow.lua
│ │ │ ├── bow.meta.json
│ │ │ ├── dagger.lua
│ │ │ ├── dagger.meta.json
│ │ │ ├── dagger_old.lua
│ │ │ ├── dagger_old.meta.json
│ │ │ ├── dual.lua
│ │ │ ├── dual.meta.json
│ │ │ ├── fishing-rod.lua
│ │ │ ├── fishing-rod.meta.json
│ │ │ ├── greatsword.lua
│ │ │ ├── greatsword.meta.json
│ │ │ ├── init.meta.json
│ │ │ ├── pickaxe.lua
│ │ │ ├── staff.lua
│ │ │ ├── staff.meta.json
│ │ │ ├── staff_melee_old.lua
│ │ │ ├── staff_melee_old.meta.json
│ │ │ ├── staff_ranged_old.lua
│ │ │ ├── staff_ranged_old.meta.json
│ │ │ ├── sword.lua
│ │ │ ├── sword.meta.json
│ │ │ └── swordAndShield.lua
│ │ ├── defaultChestProps.lua
│ │ └── init.meta.json
│ ├── client.client.lua
│ ├── contents/
│ │ ├── ambience.lua
│ │ ├── animationInterface.lua
│ │ ├── bolt.lua
│ │ ├── cameraScript.lua
│ │ ├── chatRunner.lua
│ │ ├── clientServerDamageSourceInterface.client.lua
│ │ ├── client_events.lua
│ │ ├── control.lua
│ │ ├── coreRenderServices/
│ │ │ ├── appearance_manager.lua
│ │ │ ├── bow_manager.lua
│ │ │ ├── chat_manager.lua
│ │ │ ├── init.lua
│ │ │ ├── item_manager.lua
│ │ │ ├── melee_manager.lua
│ │ │ ├── ragdoll_manager.lua
│ │ │ └── staff_manager.lua
│ │ ├── effectsClient.client.lua
│ │ ├── entityRenderer.lua
│ │ ├── firefly.client.lua
│ │ ├── guiLoader.client.lua
│ │ ├── inputController/
│ │ │ ├── characterController.lua
│ │ │ └── init.client.lua
│ │ ├── itemInterface.lua
│ │ ├── loadData.lua
│ │ ├── mapInteraction.lua
│ │ ├── npcMarkers.lua
│ │ ├── playerDataPropogationCache.lua
│ │ ├── resetButtonCallback.lua
│ │ ├── resources.lua
│ │ ├── sitting.lua
│ │ ├── teleportation.client.lua
│ │ └── treasureChests.client.lua
│ └── modules/
│ ├── AeroProxy.lua
│ ├── cannonScriptLocal.lua
│ ├── doorScript.lua
│ ├── escapeRope.lua
│ ├── firepitLocal.lua
│ ├── loadUiControl.lua
│ ├── proxy.lua
│ ├── safeScript.lua
│ ├── seatScript.lua
│ └── warpPad.lua
└── tools/
└── serializer.lua
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
================================================
FILE: .github/workflows/luacheck.yml
================================================
name: luacheck
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: lint
uses: Roang-zero1/factorio-mod-luacheck@master
with:
luacheckrc_url: https://raw.githubusercontent.com/Quenty/NevermoreEngine/version2/.luacheckrc
================================================
FILE: .gitignore
================================================
# Compiled Lua sources
luac.out
# luarocks build files
*.src.rock
*.zip
*.tar.gz
# Object files
*.o
*.os
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
*.def
*.exp
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
================================================
FILE: .luacheckrc
================================================
-- Quenty/NevermoreEngine https://github.com/Quenty/NevermoreEngine/blob/version2/.luacheckrc
local empty = {}
local read_write = { read_only = false }
local read_write_class = { read_only = false, other_fields = true }
local read_only = { read_only = true }
local function def_fields(field_list)
local fields = {}
for _, field in ipairs(field_list) do
fields[field] = empty
end
return { fields = fields }
end
local enum = def_fields({"Value", "Name"})
local function def_enum(field_list)
local fields = {}
for _, field in ipairs(field_list) do
fields[field] = enum
end
fields["GetEnumItems"] = read_only
return { fields = fields }
end
stds.roblox = {
globals = {
script = {
other_fields = true,
fields = {
Source = read_write;
GetHash = read_write;
Disabled = read_write;
LinkedSource = read_write;
CurrentEditor = read_write_class;
IsDifferentFromFileSystem = read_write;
Archivable = read_write;
ClassName = read_only;
Name = read_write;
Parent = read_write_class;
RobloxLocked = read_write;
ClearAllChildren = read_write;
Clone = read_write;
Destroy = read_write;
FindFirstAncestor = read_write;
FindFirstAncestorOfClass = read_write;
FindFirstAncestorWhichIsA = read_write;
FindFirstChild = read_write;
FindFirstChildOfClass = read_write;
FindFirstChildWhichIsA = read_write;
GetAttribute = read_write;
GetAttributeChangedSignal = read_write;
GetAttributes = read_write;
GetChildren = read_write;
GetDebugId = read_write;
GetDescendants = read_write;
GetFullName = read_write;
GetPropertyChangedSignal = read_write;
IsA = read_write;
IsAncestorOf = read_write;
IsDescendantOf = read_write;
SetAttribute = read_write;
WaitForChild = read_write;
AncestryChanged = read_write;
AttributeChanged = read_write;
Changed = read_write;
ChildAdded = read_write;
ChildRemoved = read_write;
DescendantAdded = read_write;
DescendantRemoving = read_write;
}
},
game = {
other_fields = true,
fields = {
CreatorId = read_only;
CreatorType = read_only;
GameId = read_only;
Genre = read_only;
IsSFFlagsLoaded = read_only;
JobId = read_only;
PlaceId = read_only;
PlaceVersion = read_only;
PrivateServerId = read_only;
PrivateServerOwnerId = read_only;
Workspace = read_only;
BindToClose = read_write;
DefineFastFlag = read_write;
DefineFastInt = read_write;
DefineFastString = read_write;
GetEngineFeature = read_write;
GetFastFlag = read_write;
GetFastInt = read_write;
GetFastString = read_write;
GetJobIntervalPeakFraction = read_write;
GetJobTimePeakFraction = read_write;
GetJobsExtendedStats = read_write;
GetJobsInfo = read_write;
GetObjects = read_write;
GetObjectsList = read_write;
IsLoaded = read_write;
Load = read_write;
OpenScreenshotsFolder = read_write;
OpenVideosFolder = read_write;
ReportInGoogleAnalytics = read_write;
SetFastFlagForTesting = read_write;
SetFastIntForTesting = read_write;
SetFastStringForTesting = read_write;
SetPlaceId = read_write;
SetUniverseId = read_write;
Shutdown = read_write;
GetObjectsAsync = read_write;
HttpGetAsync = read_write;
HttpPostAsync = read_write;
InsertObjectsAndJoinIfLegacyAsync = read_write;
GraphicsQualityChangeRequest = read_write;
Loaded = read_write;
ScreenshotReady = read_write;
FindService = read_write;
GetService = read_write;
Close = read_write;
CloseLate = read_write;
ServiceAdded = read_write;
ServiceRemoving = read_write;
Archivable = read_write;
ClassName = read_only;
Name = read_write;
Parent = read_write_class;
RobloxLocked = read_write;
ClearAllChildren = read_write;
Clone = read_write;
Destroy = read_write;
FindFirstAncestor = read_write;
FindFirstAncestorOfClass = read_write;
FindFirstAncestorWhichIsA = read_write;
FindFirstChild = read_write;
FindFirstChildOfClass = read_write;
FindFirstChildWhichIsA = read_write;
GetAttribute = read_write;
GetAttributeChangedSignal = read_write;
GetAttributes = read_write;
GetChildren = read_write;
GetDebugId = read_write;
GetDescendants = read_write;
GetFullName = read_write;
GetPropertyChangedSignal = read_write;
IsA = read_write;
IsAncestorOf = read_write;
IsDescendantOf = read_write;
SetAttribute = read_write;
WaitForChild = read_write;
AncestryChanged = read_write;
AttributeChanged = read_write;
Changed = read_write;
ChildAdded = read_write;
ChildRemoved = read_write;
DescendantAdded = read_write;
DescendantRemoving = read_write;
}
},
workspace = {
other_fields = true,
fields = {
AllowThirdPartySales = read_write;
CurrentCamera = read_write_class;
DistributedGameTime = read_write;
FallenPartsDestroyHeight = read_write;
FilteringEnabled = read_write;
Gravity = read_write;
StreamingEnabled = read_write;
StreamingMinRadius = read_write;
StreamingPauseMode = read_write;
StreamingTargetRadius = read_write;
TemporaryLegacyPhysicsSolverOverride = read_write;
Terrain = read_only;
BreakJoints = read_write;
CalculateJumpDistance = read_write;
CalculateJumpHeight = read_write;
CalculateJumpPower = read_write;
ExperimentalSolverIsEnabled = read_write;
GetNumAwakeParts = read_write;
GetPhysicsThrottling = read_write;
GetRealPhysicsFPS = read_write;
JoinToOutsiders = read_write;
MakeJoints = read_write;
PGSIsEnabled = read_write;
SetPhysicsThrottleEnabled = read_write;
UnjoinFromOutsiders = read_write;
ZoomToExtents = read_write;
ArePartsTouchingOthers = read_write;
BulkMoveTo = read_write;
FindPartOnRay = read_write;
FindPartOnRayWithIgnoreList = read_write;
FindPartOnRayWithWhitelist = read_write;
FindPartsInRegion3 = read_write;
FindPartsInRegion3WithIgnoreList = read_write;
FindPartsInRegion3WithWhiteList = read_write;
IKMoveTo = read_write;
IsRegion3Empty = read_write;
IsRegion3EmptyWithIgnoreList = read_write;
Raycast = read_write;
SetInsertPoint = read_write;
PrimaryPart = read_write_class;
BreakJoints = read_write;
GetBoundingBox = read_write;
GetExtentsSize = read_write;
GetPrimaryPartCFrame = read_write;
MakeJoints = read_write;
MoveTo = read_write;
SetPrimaryPartCFrame = read_write;
TranslateBy = read_write;
Archivable = read_write;
ClassName = read_only;
Name = read_write;
Parent = read_write_class;
RobloxLocked = read_write;
ClearAllChildren = read_write;
Clone = read_write;
Destroy = read_write;
FindFirstAncestor = read_write;
FindFirstAncestorOfClass = read_write;
FindFirstAncestorWhichIsA = read_write;
FindFirstChild = read_write;
FindFirstChildOfClass = read_write;
FindFirstChildWhichIsA = read_write;
GetAttribute = read_write;
GetAttributeChangedSignal = read_write;
GetAttributes = read_write;
GetChildren = read_write;
GetDebugId = read_write;
GetDescendants = read_write;
GetFullName = read_write;
GetPropertyChangedSignal = read_write;
IsA = read_write;
IsAncestorOf = read_write;
IsDescendantOf = read_write;
SetAttribute = read_write;
WaitForChild = read_write;
AncestryChanged = read_write;
AttributeChanged = read_write;
Changed = read_write;
ChildAdded = read_write;
ChildRemoved = read_write;
DescendantAdded = read_write;
DescendantRemoving = read_write;
}
},
},
read_globals = {
-- Methods
delay = empty;
settings = empty;
spawn = empty;
tick = empty;
time = empty;
typeof = empty;
version = empty;
wait = empty;
warn = empty;
UserSettings = empty;
-- Libraries
math = def_fields({"abs", "acos", "asin", "atan", "atan2", "ceil", "clamp", "cos", "cosh",
"deg", "exp", "floor", "fmod", "frexp", "ldexp", "log", "log10", "max", "min", "modf",
"noise", "pow", "rad", "random", "randomseed", "sign", "sin", "sinh", "sqrt", "tan",
"tanh", "huge", "pi"}),
table = def_fields({"concat", "foreach", "foreachi", "getn", "insert", "remove", "sort",
"pack", "unpack", "move", "create", "find"}),
os = def_fields({"time", "difftime", "date"}),
debug = def_fields({"traceback", "profilebegin", "profileend"}),
utf8 = def_fields({"char", "codes", "codepoint", "len", "offset", "graphemes",
"nfcnormalize", "nfdnormalize", "charpattern"}),
bit32 = def_fields({"arshift", "band", "bnot", "bor", "btest", "bxor", "extract",
"replace", "lrotate", "lshift", "rrotate", "rshift"}),
string = def_fields({"byte", "char", "find", "format", "gmatch", "gsub", "len", "lower",
"match", "rep", "reverse", "split"}),
-- Types
Axes = def_fields({"new"}),
BrickColor = def_fields({"new", "palette", "random", "White", "Gray", "DarkGray", "Black",
"Red", "Yellow", "Green", "Blue"}),
CFrame = def_fields({"new", "fromEulerAnglesXYZ", "Angles", "fromOrientation",
"fromAxisAngle", "fromMatrix"}),
Color3 = def_fields({"new", "fromRGB", "fromHSV", "toHSV"}),
ColorSequence = def_fields({"new"}),
ColorSequenceKeypoint = def_fields({"new"}),
DockWidgetPluginGuiInfo = def_fields({"new"}),
Enums = def_fields({"GetEnums"}),
Faces = def_fields({"new"}),
Instance = def_fields({"new"}),
NumberRange = def_fields({"new"}),
NumberSequence = def_fields({"new"}),
NumberSequenceKeypoint = def_fields({"new"}),
PhysicalProperties = def_fields({"new"}),
Random = def_fields({"new"}),
Ray = def_fields({"new"}),
RaycastParams = def_fields({"new"}),
Rect = def_fields({"new"}),
Region3 = def_fields({"new"}),
Region3int16 = def_fields({"new"}),
TweenInfo = def_fields({"new"}),
UDim = def_fields({"new"}),
UDim2 = def_fields({"new", "fromScale", "fromOffset"}),
Vector2 = def_fields({"new"}),
Vector2int16 = def_fields({"new"}),
Vector3 = def_fields({"new", "FromNormalId", "FromAxis"}),
Vector3int16 = def_fields({"new"}),
-- Enums
Enum = {
readonly = true,
fields = {
ABTestLoadingStatus = def_enum({"None", "Pending", "Initialized", "Error",
"TimedOut", "ShutOff"}),
ActionType = def_enum({"Nothing", "Pause", "Lose", "Draw", "Win"}),
ActuatorRelativeTo = def_enum({"Attachment0", "Attachment1", "World"}),
ActuatorType = def_enum({"None", "Motor", "Servo"}),
AlignType = def_enum({"Parallel", "Perpendicular"}),
AlphaMode = def_enum({"Overlay", "Transparency"}),
AnimationPriority = def_enum({"Idle", "Movement", "Action", "Core"}),
AppShellActionType = def_enum({"None", "OpenApp", "TapChatTab",
"TapConversationEntry", "TapAvatarTab", "ReadConversation", "TapGamePageTab",
"TapHomePageTab", "GamePageLoaded", "HomePageLoaded", "AvatarEditorPageLoaded"}),
AspectType = def_enum({"FitWithinMaxSize", "ScaleWithParentSize"}),
AssetFetchStatus = def_enum({"Success", "Failure"}),
AssetType = def_enum({"Image", "TeeShirt", "Audio", "Mesh", "Lua", "Hat", "Place",
"Model", "Shirt", "Pants", "Decal", "Head", "Face", "Gear", "Badge",
"Animation", "Torso", "RightArm", "LeftArm", "LeftLeg", "RightLeg", "Package",
"GamePass", "Plugin", "MeshPart", "HairAccessory", "FaceAccessory",
"NeckAccessory", "ShoulderAccessory", "FrontAccessory", "BackAccessory",
"WaistAccessory", "ClimbAnimation", "DeathAnimation", "FallAnimation",
"IdleAnimation", "JumpAnimation", "RunAnimation", "SwimAnimation",
"WalkAnimation", "PoseAnimation", "EarAccessory", "EyeAccessory",
"EmoteAnimation", "Video"}),
AutoIndentRule = def_enum({"Off", "Absolute", "Relative"}),
AvatarContextMenuOption = def_enum({"Friend", "Chat", "Emote", "InspectMenu"}),
AvatarJointPositionType = def_enum({"Fixed", "ArtistIntent"}),
Axis = def_enum({"X", "Y", "Z"}),
BinType = def_enum({"Script", "GameTool", "Grab", "Clone", "Hammer"}),
BodyPart = def_enum({"Head", "Torso", "LeftArm", "RightArm", "LeftLeg", "RightLeg"}),
BodyPartR15 = def_enum({"Head", "UpperTorso", "LowerTorso", "LeftFoot",
"LeftLowerLeg", "LeftUpperLeg", "RightFoot", "RightLowerLeg", "RightUpperLeg",
"LeftHand", "LeftLowerArm", "LeftUpperArm", "RightHand", "RightLowerArm",
"RightUpperArm", "RootPart", "Unknown"}),
BorderMode = def_enum({"Outline", "Middle", "Inset"}),
BreakReason = def_enum({"Other", "Error", "UserBreakpoint", "SpecialBreakpoint"}),
BulkMoveMode = def_enum({"FireAllEvents", "FireCFrameChanged", "FireNoEvents"}),
Button = def_enum({"Jump", "Dismount"}),
ButtonStyle = def_enum({"Custom", "RobloxButtonDefault", "RobloxButton",
"RobloxRoundButton", "RobloxRoundDefaultButton", "RobloxRoundDropdownButton"}),
CameraMode = def_enum({"Classic", "LockFirstPerson"}),
CameraPanMode = def_enum({"Classic", "EdgeBump"}),
CameraType = def_enum({"Fixed", "Watch", "Attach", "Track", "Follow", "Custom",
"Scriptable", "Orbital"}),
CellBlock = def_enum({"Solid", "VerticalWedge", "CornerWedge",
"InverseCornerWedge", "HorizontalWedge"}),
CellMaterial = def_enum({"Empty", "Grass", "Sand", "Brick", "Granite", "Asphalt",
"Iron", "Aluminum", "Gold", "WoodPlank", "WoodLog", "Gravel", "CinderBlock",
"MossyStone", "Cement", "RedPlastic", "BluePlastic", "Water"}),
CellOrientation = def_enum({"NegZ", "X", "Z", "NegX"}),
CenterDialogType = def_enum({"UnsolicitedDialog", "PlayerInitiatedDialog",
"ModalDialog", "QuitDialog"}),
ChatCallbackType = def_enum({"OnCreatingChatWindow", "OnClientSendingMessage",
"OnClientFormattingMessage", "OnServerReceivingMessage"}),
ChatColor = def_enum({"Blue", "Green", "Red", "White"}),
ChatMode = def_enum({"Menu", "TextAndMenu"}),
ChatPrivacyMode = def_enum({"AllUsers", "NoOne", "Friends"}),
ChatStyle = def_enum({"Classic", "Bubble", "ClassicAndBubble"}),
CollisionFidelity = def_enum({"Default", "Hull", "Box",
"PreciseConvexDecomposition"}),
ComputerCameraMovementMode = def_enum({"Default", "Follow", "Classic", "Orbital",
"CameraToggle"}),
ComputerMovementMode = def_enum({"Default", "KeyboardMouse", "ClickToMove"}),
ConnectionError = def_enum({"OK", "DisconnectErrors", "DisconnectBadhash",
"DisconnectSecurityKeyMismatch", "DisconnectNewSecurityKeyMismatch",
"DisconnectProtocolMismatch", "DisconnectReceivePacketError",
"DisconnectReceivePacketStreamError", "DisconnectSendPacketError",
"DisconnectIllegalTeleport", "DisconnectDuplicatePlayer",
"DisconnectDuplicateTicket", "DisconnectTimeout", "DisconnectLuaKick",
"DisconnectOnRemoteSysStats", "DisconnectHashTimeout",
"DisconnectCloudEditKick", "DisconnectPlayerless", "DisconnectEvicted",
"DisconnectDevMaintenance", "DisconnectRobloxMaintenance", "DisconnectRejoin",
"DisconnectConnectionLost", "DisconnectIdle", "DisconnectRaknetErrors",
"DisconnectWrongVersion", "DisconnectBySecurityPolicy", "DisconnectBlockedIP",
"PlacelaunchErrors", "PlacelaunchDisabled", "PlacelaunchError",
"PlacelaunchGameEnded", "PlacelaunchGameFull", "PlacelaunchUserLeft",
"PlacelaunchRestricted", "PlacelaunchUnauthorized", "PlacelaunchFlooded",
"PlacelaunchHashExpired", "PlacelaunchHashException",
"PlacelaunchPartyCannotFit", "PlacelaunchHttpError",
"PlacelaunchCustomMessage", "PlacelaunchOtherError", "TeleportErrors",
"TeleportFailure", "TeleportGameNotFound", "TeleportGameEnded",
"TeleportGameFull", "TeleportUnauthorized", "TeleportFlooded",
"TeleportIsTeleporting"}),
ConnectionState = def_enum({"Connected", "Disconnected"}),
ContextActionPriority = def_enum({"Low", "Medium", "Default", "High"}),
ContextActionResult = def_enum({"Pass", "Sink"}),
ControlMode = def_enum({"MouseLockSwitch", "Classic"}),
CoreGuiType = def_enum({"PlayerList", "Health", "Backpack", "Chat", "All",
"EmotesMenu"}),
CreatorType = def_enum({"User", "Group"}),
CurrencyType = def_enum({"Default", "Robux", "Tix"}),
CustomCameraMode = def_enum({"Default", "Follow", "Classic"}),
DataStoreRequestType = def_enum({"GetAsync", "SetIncrementAsync", "UpdateAsync",
"GetSortedAsync", "SetIncrementSortedAsync", "OnUpdate"}),
DevCameraOcclusionMode = def_enum({"Zoom", "Invisicam"}),
DevComputerCameraMovementMode = def_enum({"UserChoice", "Classic", "Follow",
"Orbital", "CameraToggle"}),
DevComputerMovementMode = def_enum({"UserChoice", "KeyboardMouse", "ClickToMove",
"Scriptable"}),
DevTouchCameraMovementMode = def_enum({"UserChoice", "Classic", "Follow",
"Orbital"}),
DevTouchMovementMode = def_enum({"UserChoice", "Thumbstick", "DPad", "Thumbpad",
"ClickToMove", "Scriptable", "DynamicThumbstick"}),
DeveloperMemoryTag = def_enum({"Internal", "HttpCache", "Instances", "Signals",
"LuaHeap", "Script", "PhysicsCollision", "PhysicsParts", "GraphicsSolidModels",
"GraphicsMeshParts", "GraphicsParticles", "GraphicsParts",
"GraphicsSpatialHash", "GraphicsTerrain", "GraphicsTexture",
"GraphicsTextureCharacter", "Sounds", "StreamingSounds", "TerrainVoxels",
"Gui", "Animation", "Navigation"}),
DeviceType = def_enum({"Unknown", "Desktop", "Tablet", "Phone"}),
DialogBehaviorType = def_enum({"SinglePlayer", "MultiplePlayers"}),
DialogPurpose = def_enum({"Quest", "Help", "Shop"}),
DialogTone = def_enum({"Neutral", "Friendly", "Enemy"}),
DominantAxis = def_enum({"Width", "Height"}),
DraftStatusCode = def_enum({"OK", "DraftOutdated", "ScriptRemoved",
"DraftCommitted"}),
EasingDirection = def_enum({"In", "Out", "InOut"}),
EasingStyle = def_enum({"Linear", "Sine", "Back", "Quad", "Quart", "Quint",
"Bounce", "Elastic", "Exponential", "Circular", "Cubic"}),
ElasticBehavior = def_enum({"WhenScrollable", "Always", "Never"}),
EnviromentalPhysicsThrottle = def_enum({"DefaultAuto", "Disabled", "Always",
"Skip2", "Skip4", "Skip8", "Skip16"}),
ExplosionType = def_enum({"NoCraters", "Craters"}),
FillDirection = def_enum({"Horizontal", "Vertical"}),
FilterResult = def_enum({"Rejected", "Accepted"}),
Font = def_enum({"Legacy", "Arial", "ArialBold", "SourceSans", "SourceSansBold",
"SourceSansSemibold", "SourceSansLight", "SourceSansItalic", "Bodoni",
"Garamond", "Cartoon", "Code", "Highway", "SciFi", "Arcade", "Fantasy",
"Antique", "Gotham", "GothamSemibold", "GothamBold", "GothamBlack"}),
FontSize = def_enum({"Size8", "Size9", "Size10", "Size11", "Size12", "Size14",
"Size18", "Size24", "Size36", "Size48", "Size28", "Size32", "Size42", "Size60",
"Size96"}),
FormFactor = def_enum({"Symmetric", "Brick", "Plate", "Custom"}),
FrameStyle = def_enum({"Custom", "ChatBlue", "RobloxSquare", "RobloxRound",
"ChatGreen", "ChatRed", "DropShadow"}),
FramerateManagerMode = def_enum({"Automatic", "On", "Off"}),
FriendRequestEvent = def_enum({"Issue", "Revoke", "Accept", "Deny"}),
FriendStatus = def_enum({"Unknown", "NotFriend", "Friend", "FriendRequestSent",
"FriendRequestReceived"}),
FunctionalTestResult = def_enum({"Passed", "Warning", "Error"}),
GameAvatarType = def_enum({"R6", "R15", "PlayerChoice"}),
GearGenreSetting = def_enum({"AllGenres", "MatchingGenreOnly"}),
GearType = def_enum({"MeleeWeapons", "RangedWeapons", "Explosives", "PowerUps",
"NavigationEnhancers", "MusicalInstruments", "SocialItems", "BuildingTools",
"Transport"}),
Genre = def_enum({"All", "TownAndCity", "Fantasy", "SciFi", "Ninja", "Scary",
"Pirate", "Adventure", "Sports", "Funny", "WildWest", "War", "SkatePark",
"Tutorial"}),
GraphicsMode = def_enum({"Automatic", "Direct3D9", "Direct3D11", "OpenGL", "Metal",
"Vulkan", "NoGraphics"}),
HandlesStyle = def_enum({"Resize", "Movement"}),
HorizontalAlignment = def_enum({"Center", "Left", "Right"}),
HoverAnimateSpeed = def_enum({"VerySlow", "Slow", "Medium", "Fast", "VeryFast"}),
HttpCachePolicy = def_enum({"None", "Full", "DataOnly", "Default",
"InternalRedirectRefresh"}),
HttpContentType = def_enum({"ApplicationJson", "ApplicationXml",
"ApplicationUrlEncoded", "TextPlain", "TextXml"}),
HttpError = def_enum({"OK", "InvalidUrl", "DnsResolve", "ConnectFail",
"OutOfMemory", "TimedOut", "TooManyRedirects", "InvalidRedirect", "NetFail",
"Aborted", "SslConnectFail", "SslVerificationFail", "Unknown"}),
HttpRequestType = def_enum({"Default", "MarketplaceService", "Players", "Chat",
"Avatar", "Analytics", "Localization"}),
HumanoidCollisionType = def_enum({"OuterBox", "InnerBox"}),
HumanoidDisplayDistanceType = def_enum({"Viewer", "Subject", "None"}),
HumanoidHealthDisplayType = def_enum({"DisplayWhenDamaged", "AlwaysOn",
"AlwaysOff"}),
HumanoidRigType = def_enum({"R6", "R15"}),
HumanoidStateType = def_enum({"FallingDown", "Running", "RunningNoPhysics",
"Climbing", "StrafingNoPhysics", "Ragdoll", "GettingUp", "Jumping", "Landed",
"Flying", "Freefall", "Seated", "PlatformStanding", "Dead", "Swimming",
"Physics", "None"}),
IKCollisionsMode = def_enum({"NoCollisions", "OtherMechanismsAnchored",
"IncludeContactedMechanisms"}),
InOut = def_enum({"Edge", "Inset", "Center"}),
InfoType = def_enum({"Asset", "Product", "GamePass", "Subscription", "Bundle"}),
InitialDockState = def_enum({"Top", "Bottom", "Left", "Right", "Float"}),
InlineAlignment = def_enum({"Bottom", "Center", "Top"}),
InputType = def_enum({"NoInput", "Constant", "Sin"}),
JointCreationMode = def_enum({"All", "Surface", "None"}),
KeyCode = def_enum({"Unknown", "Backspace", "Tab", "Clear", "Return", "Pause",
"Escape", "Space", "QuotedDouble", "Hash", "Dollar", "Percent", "Ampersand",
"Quote", "LeftParenthesis", "RightParenthesis", "Asterisk", "Plus", "Comma",
"Minus", "Period", "Slash", "Zero", "One", "Two", "Three", "Four", "Five",
"Six", "Seven", "Eight", "Nine", "Colon", "Semicolon", "LessThan", "Equals",
"GreaterThan", "Question", "At", "LeftBracket", "BackSlash", "RightBracket",
"Caret", "Underscore", "Backquote", "A", "B", "C", "D", "E", "F", "G", "H",
"I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X",
"Y", "Z", "LeftCurly", "Pipe", "RightCurly", "Tilde", "Delete", "KeypadZero",
"KeypadOne", "KeypadTwo", "KeypadThree", "KeypadFour", "KeypadFive",
"KeypadSix", "KeypadSeven", "KeypadEight", "KeypadNine", "KeypadPeriod",
"KeypadDivide", "KeypadMultiply", "KeypadMinus", "KeypadPlus", "KeypadEnter",
"KeypadEquals", "Up", "Down", "Right", "Left", "Insert", "Home", "End",
"PageUp", "PageDown", "LeftShift", "RightShift", "LeftMeta", "RightMeta",
"LeftAlt", "RightAlt", "LeftControl", "RightControl", "CapsLock", "NumLock",
"ScrollLock", "LeftSuper", "RightSuper", "Mode", "Compose", "Help", "Print",
"SysReq", "Break", "Menu", "Power", "Euro", "Undo", "F1", "F2", "F3", "F4",
"F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15",
"World0", "World1", "World2", "World3", "World4", "World5", "World6", "World7",
"World8", "World9", "World10", "World11", "World12", "World13", "World14",
"World15", "World16", "World17", "World18", "World19", "World20", "World21",
"World22", "World23", "World24", "World25", "World26", "World27", "World28",
"World29", "World30", "World31", "World32", "World33", "World34", "World35",
"World36", "World37", "World38", "World39", "World40", "World41", "World42",
"World43", "World44", "World45", "World46", "World47", "World48", "World49",
"World50", "World51", "World52", "World53", "World54", "World55", "World56",
"World57", "World58", "World59", "World60", "World61", "World62", "World63",
"World64", "World65", "World66", "World67", "World68", "World69", "World70",
"World71", "World72", "World73", "World74", "World75", "World76", "World77",
"World78", "World79", "World80", "World81", "World82", "World83", "World84",
"World85", "World86", "World87", "World88", "World89", "World90", "World91",
"World92", "World93", "World94", "World95", "ButtonX", "ButtonY", "ButtonA",
"ButtonB", "ButtonR1", "ButtonL1", "ButtonR2", "ButtonL2", "ButtonR3",
"ButtonL3", "ButtonStart", "ButtonSelect", "DPadLeft", "DPadRight", "DPadUp",
"DPadDown", "Thumbstick1", "Thumbstick2"}),
KeywordFilterType = def_enum({"Include", "Exclude"}),
Language = def_enum({"Default"}),
LanguagePreference = def_enum({"SystemDefault", "English", "SimplifiedChinese",
"Korean"}),
LeftRight = def_enum({"Left", "Center", "Right"}),
LevelOfDetailSetting = def_enum({"High", "Medium", "Low"}),
Limb = def_enum({"Head", "Torso", "LeftArm", "RightArm", "LeftLeg", "RightLeg",
"Unknown"}),
ListDisplayMode = def_enum({"Horizontal", "Vertical"}),
ListenerType = def_enum({"Camera", "CFrame", "ObjectPosition", "ObjectCFrame"}),
Material = def_enum({"Plastic", "Wood", "Slate", "Concrete", "CorrodedMetal",
"DiamondPlate", "Foil", "Grass", "Ice", "Marble", "Granite", "Brick", "Pebble",
"Sand", "Fabric", "SmoothPlastic", "Metal", "WoodPlanks", "Cobblestone", "Air",
"Water", "Rock", "Glacier", "Snow", "Sandstone", "Mud", "Basalt", "Ground",
"CrackedLava", "Neon", "Glass", "Asphalt", "LeafyGrass", "Salt", "Limestone",
"Pavement", "ForceField"}),
MembershipType = def_enum({"None", "BuildersClub", "TurboBuildersClub",
"OutrageousBuildersClub", "Premium"}),
MeshType = def_enum({"Head", "Torso", "Wedge", "Prism", "Pyramid", "ParallelRamp",
"RightAngleRamp", "CornerWedge", "Brick", "Sphere", "Cylinder", "FileMesh"}),
MessageType = def_enum({"MessageOutput", "MessageInfo", "MessageWarning",
"MessageError"}),
ModifierKey = def_enum({"Alt", "Ctrl", "Meta", "Shift"}),
MouseBehavior = def_enum({"Default", "LockCenter", "LockCurrentPosition"}),
MoveState = def_enum({"Stopped", "Coasting", "Pushing", "Stopping", "AirFree"}),
NameOcclusion = def_enum({"OccludeAll", "EnemyOcclusion", "NoOcclusion"}),
NetworkOwnership = def_enum({"Automatic", "Manual", "OnContact"}),
NormalId = def_enum({"Top", "Bottom", "Back", "Front", "Right", "Left"}),
OutputLayoutMode = def_enum({"Horizontal", "Vertical"}),
OverrideMouseIconBehavior = def_enum({"None", "ForceShow", "ForceHide"}),
PacketPriority = def_enum({"IMMEDIATE_PRIORITY", "HIGH_PRIORITY",
"MEDIUM_PRIORITY", "LOW_PRIORITY"}),
PartType = def_enum({"Ball", "Block", "Cylinder"}),
PathStatus = def_enum({"Success", "ClosestNoPath", "ClosestOutOfRange",
"FailStartNotEmpty", "FailFinishNotEmpty", "NoPath"}),
PathWaypointAction = def_enum({"Walk", "Jump"}),
PermissionLevelShown = def_enum({"Game", "RobloxGame", "RobloxScript", "Studio",
"Roblox"}),
Platform = def_enum({"Windows", "OSX", "IOS", "Android", "XBoxOne", "PS4", "PS3",
"XBox360", "WiiU", "NX", "Ouya", "AndroidTV", "Chromecast", "Linux", "SteamOS",
"WebOS", "DOS", "BeOS", "UWP", "None"}),
PlaybackState = def_enum({"Begin", "Delayed", "Playing", "Paused", "Completed",
"Cancelled"}),
PlayerActions = def_enum({"CharacterForward", "CharacterBackward", "CharacterLeft",
"CharacterRight", "CharacterJump"}),
PlayerChatType = def_enum({"All", "Team", "Whisper"}),
PoseEasingDirection = def_enum({"Out", "InOut", "In"}),
PoseEasingStyle = def_enum({"Linear", "Constant", "Elastic", "Cubic", "Bounce"}),
PrivilegeType = def_enum({"Owner", "Admin", "Member", "Visitor", "Banned"}),
ProductPurchaseDecision = def_enum({"NotProcessedYet", "PurchaseGranted"}),
QualityLevel = def_enum({"Automatic", "Level01", "Level02", "Level03", "Level04",
"Level05", "Level06", "Level07", "Level08", "Level09", "Level10", "Level11",
"Level12", "Level13", "Level14", "Level15", "Level16", "Level17", "Level18",
"Level19", "Level20", "Level21"}),
R15CollisionType = def_enum({"OuterBox", "InnerBox"}),
RaycastFilterType = def_enum({"Blacklist", "Whitelist"}),
RenderFidelity = def_enum({"Automatic", "Precise"}),
RenderPriority = def_enum({"First", "Input", "Camera", "Character", "Last"}),
RenderingTestComparisonMethod = def_enum({"psnr", "diff"}),
ReturnKeyType = def_enum({"Default", "Done", "Go", "Next", "Search", "Send"}),
ReverbType = def_enum({"NoReverb", "GenericReverb", "PaddedCell", "Room",
"Bathroom", "LivingRoom", "StoneRoom", "Auditorium", "ConcertHall", "Cave",
"Arena", "Hangar", "CarpettedHallway", "Hallway", "StoneCorridor", "Alley",
"Forest", "City", "Mountains", "Quarry", "Plain", "ParkingLot", "SewerPipe",
"UnderWater"}),
RibbonTool = def_enum({"Select", "Scale", "Rotate", "Move", "Transform",
"ColorPicker", "MaterialPicker", "Group", "Ungroup", "None"}),
RollOffMode = def_enum({"Inverse", "Linear", "InverseTapered", "LinearSquare"}),
RotationType = def_enum({"MovementRelative", "CameraRelative"}),
RuntimeUndoBehavior = def_enum({"Aggregate", "Snapshot", "Hybrid"}),
SaveFilter = def_enum({"SaveAll", "SaveWorld", "SaveGame"}),
SavedQualitySetting = def_enum({"Automatic", "QualityLevel1", "QualityLevel2",
"QualityLevel3", "QualityLevel4", "QualityLevel5", "QualityLevel6",
"QualityLevel7", "QualityLevel8", "QualityLevel9", "QualityLevel10"}),
ScaleType = def_enum({"Stretch", "Slice", "Tile", "Fit", "Crop"}),
ScreenOrientation = def_enum({"LandscapeLeft", "LandscapeRight", "LandscapeSensor",
"Portrait", "Sensor"}),
ScrollBarInset = def_enum({"None", "ScrollBar", "Always"}),
ScrollingDirection = def_enum({"X", "Y", "XY"}),
ServerAudioBehavior = def_enum({"Enabled", "Muted", "OnlineGame"}),
SizeConstraint = def_enum({"RelativeXY", "RelativeXX", "RelativeYY"}),
SortOrder = def_enum({"LayoutOrder", "Name", "Custom"}),
SoundType = def_enum({"NoSound", "Boing", "Bomb", "Break", "Click", "Clock",
"Slingshot", "Page", "Ping", "Snap", "Splat", "Step", "StepOn", "Swoosh",
"Victory"}),
SpecialKey = def_enum({"Insert", "Home", "End", "PageUp", "PageDown", "ChatHotkey"}),
StartCorner = def_enum({"TopLeft", "TopRight", "BottomLeft", "BottomRight"}),
Status = def_enum({"Poison", "Confusion"}),
StreamingPauseMode = def_enum({"Default", "Disabled", "ClientPhysicsPause"}),
StudioDataModelType = def_enum({"Edit", "PlayClient", "PlayServer", "RobloxPlugin",
"UserPlugin", "None"}),
StudioStyleGuideColor = def_enum({"MainBackground", "Titlebar", "Dropdown",
"Tooltip", "Notification", "ScrollBar", "ScrollBarBackground", "TabBar", "Tab",
"RibbonTab", "RibbonTabTopBar", "Button", "MainButton", "RibbonButton",
"ViewPortBackground", "InputFieldBackground", "Item", "TableItem",
"CategoryItem", "GameSettingsTableItem", "GameSettingsTooltip", "EmulatorBar",
"EmulatorDropDown", "ColorPickerFrame", "CurrentMarker", "Border", "Shadow",
"Light", "Dark", "Mid", "MainText", "SubText", "TitlebarText", "BrightText",
"DimmedText", "LinkText", "WarningText", "ErrorText", "InfoText",
"SensitiveText", "ScriptSideWidget", "ScriptBackground", "ScriptText",
"ScriptSelectionText", "ScriptSelectionBackground",
"ScriptFindSelectionBackground", "ScriptMatchingWordSelectionBackground",
"ScriptOperator", "ScriptNumber", "ScriptString", "ScriptComment",
"ScriptPreprocessor", "ScriptKeyword", "ScriptBuiltInFunction",
"ScriptWarning", "ScriptError", "ScriptWhitespace", "ScriptRuler",
"DebuggerCurrentLine", "DebuggerErrorLine", "DiffFilePathText",
"DiffTextHunkInfo", "DiffTextNoChange", "DiffTextAddition", "DiffTextDeletion",
"DiffTextSeparatorBackground", "DiffTextNoChangeBackground",
"DiffTextAdditionBackground", "DiffTextDeletionBackground", "DiffLineNum",
"DiffLineNumSeparatorBackground", "DiffLineNumNoChangeBackground",
"DiffLineNumAdditionBackground", "DiffLineNumDeletionBackground",
"DiffFilePathBackground", "DiffFilePathBorder", "Separator", "ButtonBorder",
"ButtonText", "InputFieldBorder", "CheckedFieldBackground",
"CheckedFieldBorder", "CheckedFieldIndicator", "HeaderSection", "Midlight",
"StatusBar", "DialogButton", "DialogButtonText", "DialogButtonBorder",
"DialogMainButton", "DialogMainButtonText"}),
StudioStyleGuideModifier = def_enum({"Default", "Selected", "Pressed", "Disabled",
"Hover"}),
Style = def_enum({"AlternatingSupports", "BridgeStyleSupports", "NoSupports"}),
SurfaceConstraint = def_enum({"None", "Hinge", "SteppingMotor", "Motor"}),
SurfaceGuiSizingMode = def_enum({"FixedSize", "PixelsPerStud"}),
SurfaceType = def_enum({"Smooth", "Glue", "Weld", "Studs", "Inlet", "Universal",
"Hinge", "Motor", "SteppingMotor", "SmoothNoOutlines"}),
SwipeDirection = def_enum({"Right", "Left", "Up", "Down", "None"}),
TableMajorAxis = def_enum({"RowMajor", "ColumnMajor"}),
Technology = def_enum({"Compatibility", "Voxel", "ShadowMap", "Legacy"}),
TeleportResult = def_enum({"Success", "Failure", "GameNotFound", "GameEnded",
"GameFull", "Unauthorized", "Flooded", "IsTeleporting"}),
TeleportState = def_enum({"RequestedFromServer", "Started", "WaitingForServer",
"Failed", "InProgress"}),
TeleportType = def_enum({"ToPlace", "ToInstance", "ToReservedServer"}),
TextFilterContext = def_enum({"PublicChat", "PrivateChat"}),
TextInputType = def_enum({"Default", "NoSuggestions", "Number", "Email", "Phone",
"Password"}),
TextTruncate = def_enum({"None", "AtEnd"}),
TextXAlignment = def_enum({"Left", "Center", "Right"}),
TextYAlignment = def_enum({"Top", "Center", "Bottom"}),
TextureMode = def_enum({"Stretch", "Wrap", "Static"}),
TextureQueryType = def_enum({"NonHumanoid", "NonHumanoidOrphaned", "Humanoid",
"HumanoidOrphaned"}),
ThreadPoolConfig = def_enum({"Auto", "PerCore1", "PerCore2", "PerCore3",
"PerCore4", "Threads1", "Threads2", "Threads3", "Threads4", "Threads8",
"Threads16"}),
ThrottlingPriority = def_enum({"Extreme", "ElevatedOnServer", "Default"}),
ThumbnailSize = def_enum({"Size48x48", "Size180x180", "Size420x420", "Size60x60",
"Size100x100", "Size150x150", "Size352x352"}),
ThumbnailType = def_enum({"HeadShot", "AvatarBust", "AvatarThumbnail"}),
TickCountSampleMethod = def_enum({"Fast", "Benchmark", "Precise"}),
TopBottom = def_enum({"Top", "Center", "Bottom"}),
TouchCameraMovementMode = def_enum({"Default", "Follow", "Classic", "Orbital"}),
TouchMovementMode = def_enum({"Default", "Thumbstick", "DPad", "Thumbpad",
"ClickToMove", "DynamicThumbstick"}),
TweenStatus = def_enum({"Canceled", "Completed"}),
UITheme = def_enum({"Light", "Dark"}),
UiMessageType = def_enum({"UiMessageError", "UiMessageInfo"}),
UploadSetting = def_enum({"Never", "Ask", "Always"}),
UserCFrame = def_enum({"Head", "LeftHand", "RightHand"}),
UserInputState = def_enum({"Begin", "Change", "End", "Cancel", "None"}),
UserInputType = def_enum({"MouseButton1", "MouseButton2", "MouseButton3",
"MouseWheel", "MouseMovement", "Touch", "Keyboard", "Focus", "Accelerometer",
"Gyro", "Gamepad1", "Gamepad2", "Gamepad3", "Gamepad4", "Gamepad5", "Gamepad6",
"Gamepad7", "Gamepad8", "TextInput", "InputMethod", "None"}),
VRTouchpad = def_enum({"Left", "Right"}),
VRTouchpadMode = def_enum({"Touch", "VirtualThumbstick", "ABXY"}),
VerticalAlignment = def_enum({"Center", "Top", "Bottom"}),
VerticalScrollBarPosition = def_enum({"Left", "Right"}),
VibrationMotor = def_enum({"Large", "Small", "LeftTrigger", "RightTrigger",
"LeftHand", "RightHand"}),
VideoQualitySettings = def_enum({"LowResolution", "MediumResolution",
"HighResolution"}),
VirtualInputMode = def_enum({"Recording", "Playing", "None"}),
WaterDirection = def_enum({"NegX", "X", "NegY", "Y", "NegZ", "Z"}),
WaterForce = def_enum({"None", "Small", "Medium", "Strong", "Max"}),
ZIndexBehavior = def_enum({"Global", "Sibling"}),
}
}
},
}
stds.testez = {
read_globals = {
"describe",
"it", "itFOCUS", "itSKIP",
"FOCUS", "SKIP", "HACK_NO_XPCALL",
"expect",
}
}
stds.plugin = {
read_globals = {
"plugin",
"DebuggerManager",
}
}
ignore = {
"212", -- unused arguments
}
std = "lua51+roblox"
files["**/*.spec.lua"] = {
std = "+testez",
}
================================================
FILE: .vscode/settings.json
================================================
{
"Lua.completion.callSnippet": "Both",
"Lua.workspace.ignoreSubmodules": false
}
================================================
FILE: LICENSE
================================================
Copyright (c) 2020 Vesteria, Inc.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: assets/readme.md
================================================
Vesteria Image & Other Assets
================================================
FILE: default.project.json
================================================
{
"name": "project",
"tree": {
"$className": "DataModel",
"Chat": {
"$className": "Chat",
"$ignoreUnknownInstances": true,
"$path": "src/Chat"
},
"ReplicatedStorage": {
"$className": "ReplicatedStorage",
"$ignoreUnknownInstances": true,
"$path": "src/ReplicatedStorage"
},
"ServerScriptService": {
"$className": "ServerScriptService",
"$ignoreUnknownInstances": false,
"$path": "src/ServerScriptService"
},
"StarterGui": {
"$className": "StarterGui",
"$ignoreUnknownInstances": true,
"$path": "src/StarterGui"
},
"StarterPlayer": {
"$className": "StarterPlayer",
"StarterCharacterScripts": {
"$className": "StarterCharacterScripts",
"$ignoreUnknownInstances": true,
"$path": "src/StarterPlayer/StarterCharacterScripts"
},
"StarterPlayerScripts": {
"$className": "StarterPlayerScripts",
"$ignoreUnknownInstances": false,
"$path": "src/StarterPlayer/StarterPlayerScripts"
},
"$ignoreUnknownInstances": true
}
}
}
================================================
FILE: readme.md
================================================
Vesteria is a fantasy MMORPG on Roblox- originally founded by berezaa, sk3let0n and Polymorphic in the Roblox Incubator program.
In early 2020, Vesteria shut down and the team underwent a huge effort to convert the game to support Rojo and VSCode. Multiple global refactors followed, hackily attempting to salvage the game's codebase. Simulatnously, the game went through a signifcant creative overhaul to simplify and refine the gameplay in an attempt to return to our roots. While the project was never completed and the game has reverted back to the original version, there is still great value to be found in sharing the lessons we learned with the public.
DevForum Announcement: https://devforum.roblox.com/t/vesteria-open-source/842436
## Play Vesteria
https://www.roblox.com/games/2376885433/Vesteria/
## The Vesteria Team
https://www.roblox.com/groups/4238824/The-Vesteria-Team/
Copyright (c) 2020, Vesteria, Inc.
Provided for public use in educational, recreational or commercial purposes under the Apache 2 License.
================================================
FILE: roblox.toml
================================================
# This file was @generated by generate-roblox-std at 2020-07-16 00:32:31.501799700 -06:00
[selene]
base = "lua51"
name = "roblox"
[selene.structs.BasePart."*"]
struct = "Instance"
[selene.structs.BasePart.AncestryChanged]
struct = "Event"
[selene.structs.BasePart.Anchored]
property = true
writable = "overridden"
[selene.structs.BasePart.Archivable]
property = true
writable = "overridden"
[selene.structs.BasePart.AttributeChanged]
struct = "Event"
[selene.structs.BasePart.BackParamA]
property = true
writable = "overridden"
[selene.structs.BasePart.BackParamB]
property = true
writable = "overridden"
[selene.structs.BasePart.BackSurface]
property = true
writable = "overridden"
[selene.structs.BasePart.BackSurfaceInput]
property = true
writable = "overridden"
[selene.structs.BasePart.BottomParamA]
property = true
writable = "overridden"
[selene.structs.BasePart.BottomParamB]
property = true
writable = "overridden"
[selene.structs.BasePart.BottomSurface]
property = true
writable = "overridden"
[selene.structs.BasePart.BottomSurfaceInput]
property = true
writable = "overridden"
[selene.structs.BasePart.BreakJoints]
method = true
args = []
[selene.structs.BasePart.BrickColor]
property = true
writable = "overridden"
[selene.structs.BasePart.CFrame]
property = true
writable = "overridden"
[selene.structs.BasePart.CanCollide]
property = true
writable = "overridden"
[selene.structs.BasePart.CanCollideWith]
method = true
[[selene.structs.BasePart.CanCollideWith.args]]
required = false
type = "any"
[selene.structs.BasePart.CanSetNetworkOwnership]
method = true
args = []
[selene.structs.BasePart.CastShadow]
property = true
writable = "overridden"
[selene.structs.BasePart.CenterOfMass]
property = true
[selene.structs.BasePart.Changed]
struct = "Event"
[selene.structs.BasePart.ChildAdded]
struct = "Event"
[selene.structs.BasePart.ChildRemoved]
struct = "Event"
[selene.structs.BasePart.ClassName]
property = true
[selene.structs.BasePart.ClearAllChildren]
method = true
args = []
[selene.structs.BasePart.Clone]
method = true
args = []
[selene.structs.BasePart.CollisionGroupId]
property = true
writable = "overridden"
[selene.structs.BasePart.Color]
property = true
writable = "overridden"
[selene.structs.BasePart.CustomPhysicalProperties]
property = true
writable = "overridden"
[selene.structs.BasePart.DescendantAdded]
struct = "Event"
[selene.structs.BasePart.DescendantRemoving]
struct = "Event"
[selene.structs.BasePart.Destroy]
method = true
args = []
[selene.structs.BasePart.FindFirstAncestor]
method = true
[[selene.structs.BasePart.FindFirstAncestor.args]]
required = false
type = "any"
[selene.structs.BasePart.FindFirstAncestorOfClass]
method = true
[[selene.structs.BasePart.FindFirstAncestorOfClass.args]]
required = false
type = "any"
[selene.structs.BasePart.FindFirstAncestorWhichIsA]
method = true
[[selene.structs.BasePart.FindFirstAncestorWhichIsA.args]]
required = false
type = "any"
[selene.structs.BasePart.FindFirstChild]
method = true
[[selene.structs.BasePart.FindFirstChild.args]]
required = false
type = "any"
[[selene.structs.BasePart.FindFirstChild.args]]
required = false
type = "any"
[selene.structs.BasePart.FindFirstChildOfClass]
method = true
[[selene.structs.BasePart.FindFirstChildOfClass.args]]
required = false
type = "any"
[selene.structs.BasePart.FindFirstChildWhichIsA]
method = true
[[selene.structs.BasePart.FindFirstChildWhichIsA.args]]
required = false
type = "any"
[[selene.structs.BasePart.FindFirstChildWhichIsA.args]]
required = false
type = "any"
[selene.structs.BasePart.FrontParamA]
property = true
writable = "overridden"
[selene.structs.BasePart.FrontParamB]
property = true
writable = "overridden"
[selene.structs.BasePart.FrontSurface]
property = true
writable = "overridden"
[selene.structs.BasePart.FrontSurfaceInput]
property = true
writable = "overridden"
[selene.structs.BasePart.GetAttribute]
method = true
[[selene.structs.BasePart.GetAttribute.args]]
required = false
type = "any"
[selene.structs.BasePart.GetAttributeChangedSignal]
method = true
[[selene.structs.BasePart.GetAttributeChangedSignal.args]]
required = false
type = "any"
[selene.structs.BasePart.GetAttributes]
method = true
args = []
[selene.structs.BasePart.GetChildren]
method = true
args = []
[selene.structs.BasePart.GetConnectedParts]
method = true
[[selene.structs.BasePart.GetConnectedParts.args]]
required = false
type = "any"
[selene.structs.BasePart.GetDebugId]
method = true
[[selene.structs.BasePart.GetDebugId.args]]
required = false
type = "any"
[selene.structs.BasePart.GetDescendants]
method = true
args = []
[selene.structs.BasePart.GetFullName]
method = true
args = []
[selene.structs.BasePart.GetJoints]
method = true
args = []
[selene.structs.BasePart.GetMass]
method = true
args = []
[selene.structs.BasePart.GetNetworkOwner]
method = true
args = []
[selene.structs.BasePart.GetNetworkOwnershipAuto]
method = true
args = []
[selene.structs.BasePart.GetPropertyChangedSignal]
method = true
[[selene.structs.BasePart.GetPropertyChangedSignal.args]]
required = false
type = "any"
[selene.structs.BasePart.GetRootPart]
method = true
args = []
[selene.structs.BasePart.GetTouchingParts]
method = true
args = []
[selene.structs.BasePart.IsA]
method = true
[[selene.structs.BasePart.IsA.args]]
required = false
type = "any"
[selene.structs.BasePart.IsAncestorOf]
method = true
[[selene.structs.BasePart.IsAncestorOf.args]]
required = false
type = "any"
[selene.structs.BasePart.IsDescendantOf]
method = true
[[selene.structs.BasePart.IsDescendantOf.args]]
required = false
type = "any"
[selene.structs.BasePart.IsGrounded]
method = true
args = []
[selene.structs.BasePart.LeftParamA]
property = true
writable = "overridden"
[selene.structs.BasePart.LeftParamB]
property = true
writable = "overridden"
[selene.structs.BasePart.LeftSurface]
property = true
writable = "overridden"
[selene.structs.BasePart.LeftSurfaceInput]
property = true
writable = "overridden"
[selene.structs.BasePart.LocalTransparencyModifier]
property = true
writable = "overridden"
[selene.structs.BasePart.Locked]
property = true
writable = "overridden"
[selene.structs.BasePart.MakeJoints]
method = true
args = []
[selene.structs.BasePart.Mass]
property = true
[selene.structs.BasePart.Massless]
property = true
writable = "overridden"
[selene.structs.BasePart.Material]
property = true
writable = "overridden"
[selene.structs.BasePart.Name]
property = true
writable = "overridden"
[selene.structs.BasePart.Orientation]
property = true
writable = "overridden"
[selene.structs.BasePart.Parent]
struct = "Instance"
[selene.structs.BasePart.Position]
property = true
writable = "overridden"
[selene.structs.BasePart.ReceiveAge]
property = true
[selene.structs.BasePart.Reflectance]
property = true
writable = "overridden"
[selene.structs.BasePart.Resize]
method = true
[[selene.structs.BasePart.Resize.args]]
required = false
type = "any"
[[selene.structs.BasePart.Resize.args]]
required = false
type = "any"
[selene.structs.BasePart.ResizeIncrement]
property = true
[selene.structs.BasePart.ResizeableFaces]
property = true
[selene.structs.BasePart.RightParamA]
property = true
writable = "overridden"
[selene.structs.BasePart.RightParamB]
property = true
writable = "overridden"
[selene.structs.BasePart.RightSurface]
property = true
writable = "overridden"
[selene.structs.BasePart.RightSurfaceInput]
property = true
writable = "overridden"
[selene.structs.BasePart.RootPriority]
property = true
writable = "overridden"
[selene.structs.BasePart.RotVelocity]
property = true
writable = "overridden"
[selene.structs.BasePart.Rotation]
property = true
writable = "overridden"
[selene.structs.BasePart.SetAttribute]
method = true
[[selene.structs.BasePart.SetAttribute.args]]
required = false
type = "any"
[[selene.structs.BasePart.SetAttribute.args]]
required = false
type = "any"
[selene.structs.BasePart.SetNetworkOwner]
method = true
[[selene.structs.BasePart.SetNetworkOwner.args]]
required = false
type = "any"
[selene.structs.BasePart.SetNetworkOwnershipAuto]
method = true
args = []
[selene.structs.BasePart.Size]
property = true
writable = "overridden"
[selene.structs.BasePart.SubtractAsync]
method = true
[[selene.structs.BasePart.SubtractAsync.args]]
required = false
type = "any"
[[selene.structs.BasePart.SubtractAsync.args]]
required = false
type = "any"
[[selene.structs.BasePart.SubtractAsync.args]]
required = false
type = "any"
[selene.structs.BasePart.TopParamA]
property = true
writable = "overridden"
[selene.structs.BasePart.TopParamB]
property = true
writable = "overridden"
[selene.structs.BasePart.TopSurface]
property = true
writable = "overridden"
[selene.structs.BasePart.TopSurfaceInput]
property = true
writable = "overridden"
[selene.structs.BasePart.TouchEnded]
struct = "Event"
[selene.structs.BasePart.Touched]
struct = "Event"
[selene.structs.BasePart.Transparency]
property = true
writable = "overridden"
[selene.structs.BasePart.UnionAsync]
method = true
[[selene.structs.BasePart.UnionAsync.args]]
required = false
type = "any"
[[selene.structs.BasePart.UnionAsync.args]]
required = false
type = "any"
[[selene.structs.BasePart.UnionAsync.args]]
required = false
type = "any"
[selene.structs.BasePart.Velocity]
property = true
writable = "overridden"
[selene.structs.BasePart.WaitForChild]
method = true
[[selene.structs.BasePart.WaitForChild.args]]
required = false
type = "any"
[[selene.structs.BasePart.WaitForChild.args]]
required = false
type = "any"
[selene.structs.Camera."*"]
struct = "Instance"
[selene.structs.Camera.AncestryChanged]
struct = "Event"
[selene.structs.Camera.Archivable]
property = true
writable = "overridden"
[selene.structs.Camera.AttributeChanged]
struct = "Event"
[selene.structs.Camera.CFrame]
property = true
writable = "overridden"
[selene.structs.Camera.CameraSubject]
struct = "Instance"
[selene.structs.Camera.CameraType]
property = true
writable = "overridden"
[selene.structs.Camera.Changed]
struct = "Event"
[selene.structs.Camera.ChildAdded]
struct = "Event"
[selene.structs.Camera.ChildRemoved]
struct = "Event"
[selene.structs.Camera.ClassName]
property = true
[selene.structs.Camera.ClearAllChildren]
method = true
args = []
[selene.structs.Camera.Clone]
method = true
args = []
[selene.structs.Camera.DescendantAdded]
struct = "Event"
[selene.structs.Camera.DescendantRemoving]
struct = "Event"
[selene.structs.Camera.Destroy]
method = true
args = []
[selene.structs.Camera.FieldOfView]
property = true
writable = "overridden"
[selene.structs.Camera.FindFirstAncestor]
method = true
[[selene.structs.Camera.FindFirstAncestor.args]]
required = false
type = "any"
[selene.structs.Camera.FindFirstAncestorOfClass]
method = true
[[selene.structs.Camera.FindFirstAncestorOfClass.args]]
required = false
type = "any"
[selene.structs.Camera.FindFirstAncestorWhichIsA]
method = true
[[selene.structs.Camera.FindFirstAncestorWhichIsA.args]]
required = false
type = "any"
[selene.structs.Camera.FindFirstChild]
method = true
[[selene.structs.Camera.FindFirstChild.args]]
required = false
type = "any"
[[selene.structs.Camera.FindFirstChild.args]]
required = false
type = "any"
[selene.structs.Camera.FindFirstChildOfClass]
method = true
[[selene.structs.Camera.FindFirstChildOfClass.args]]
required = false
type = "any"
[selene.structs.Camera.FindFirstChildWhichIsA]
method = true
[[selene.structs.Camera.FindFirstChildWhichIsA.args]]
required = false
type = "any"
[[selene.structs.Camera.FindFirstChildWhichIsA.args]]
required = false
type = "any"
[selene.structs.Camera.FirstPersonTransition]
struct = "Event"
[selene.structs.Camera.Focus]
property = true
writable = "overridden"
[selene.structs.Camera.GetAttribute]
method = true
[[selene.structs.Camera.GetAttribute.args]]
required = false
type = "any"
[selene.structs.Camera.GetAttributeChangedSignal]
method = true
[[selene.structs.Camera.GetAttributeChangedSignal.args]]
required = false
type = "any"
[selene.structs.Camera.GetAttributes]
method = true
args = []
[selene.structs.Camera.GetChildren]
method = true
args = []
[selene.structs.Camera.GetDebugId]
method = true
[[selene.structs.Camera.GetDebugId.args]]
required = false
type = "any"
[selene.structs.Camera.GetDescendants]
method = true
args = []
[selene.structs.Camera.GetFullName]
method = true
args = []
[selene.structs.Camera.GetPanSpeed]
method = true
args = []
[selene.structs.Camera.GetPartsObscuringTarget]
method = true
[[selene.structs.Camera.GetPartsObscuringTarget.args]]
required = false
type = "any"
[[selene.structs.Camera.GetPartsObscuringTarget.args]]
required = false
type = "any"
[selene.structs.Camera.GetPropertyChangedSignal]
method = true
[[selene.structs.Camera.GetPropertyChangedSignal.args]]
required = false
type = "any"
[selene.structs.Camera.GetRenderCFrame]
method = true
args = []
[selene.structs.Camera.GetRoll]
method = true
args = []
[selene.structs.Camera.GetTiltSpeed]
method = true
args = []
[selene.structs.Camera.HeadLocked]
property = true
writable = "overridden"
[selene.structs.Camera.HeadScale]
property = true
writable = "overridden"
[selene.structs.Camera.InterpolationFinished]
struct = "Event"
[selene.structs.Camera.IsA]
method = true
[[selene.structs.Camera.IsA.args]]
required = false
type = "any"
[selene.structs.Camera.IsAncestorOf]
method = true
[[selene.structs.Camera.IsAncestorOf.args]]
required = false
type = "any"
[selene.structs.Camera.IsDescendantOf]
method = true
[[selene.structs.Camera.IsDescendantOf.args]]
required = false
type = "any"
[selene.structs.Camera.Name]
property = true
writable = "overridden"
[selene.structs.Camera.NearPlaneZ]
property = true
[selene.structs.Camera.Parent]
struct = "Instance"
[selene.structs.Camera.ScreenPointToRay]
method = true
[[selene.structs.Camera.ScreenPointToRay.args]]
required = false
type = "any"
[[selene.structs.Camera.ScreenPointToRay.args]]
required = false
type = "any"
[[selene.structs.Camera.ScreenPointToRay.args]]
required = false
type = "any"
[selene.structs.Camera.SetAttribute]
method = true
[[selene.structs.Camera.SetAttribute.args]]
required = false
type = "any"
[[selene.structs.Camera.SetAttribute.args]]
required = false
type = "any"
[selene.structs.Camera.SetCameraPanMode]
method = true
[[selene.structs.Camera.SetCameraPanMode.args]]
required = false
type = "any"
[selene.structs.Camera.SetImageServerView]
method = true
[[selene.structs.Camera.SetImageServerView.args]]
required = false
type = "any"
[selene.structs.Camera.SetRoll]
method = true
[[selene.structs.Camera.SetRoll.args]]
required = false
type = "any"
[selene.structs.Camera.ViewportPointToRay]
method = true
[[selene.structs.Camera.ViewportPointToRay.args]]
required = false
type = "any"
[[selene.structs.Camera.ViewportPointToRay.args]]
required = false
type = "any"
[[selene.structs.Camera.ViewportPointToRay.args]]
required = false
type = "any"
[selene.structs.Camera.ViewportSize]
property = true
[selene.structs.Camera.WaitForChild]
method = true
[[selene.structs.Camera.WaitForChild.args]]
required = false
type = "any"
[[selene.structs.Camera.WaitForChild.args]]
required = false
type = "any"
[selene.structs.Camera.WorldToScreenPoint]
method = true
[[selene.structs.Camera.WorldToScreenPoint.args]]
required = false
type = "any"
[selene.structs.Camera.WorldToViewportPoint]
method = true
[[selene.structs.Camera.WorldToViewportPoint.args]]
required = false
type = "any"
[selene.structs.Camera.Zoom]
method = true
[[selene.structs.Camera.Zoom.args]]
required = false
type = "any"
[selene.structs.DataModel."*"]
struct = "Instance"
[selene.structs.DataModel.AncestryChanged]
struct = "Event"
[selene.structs.DataModel.Archivable]
property = true
writable = "overridden"
[selene.structs.DataModel.AttributeChanged]
struct = "Event"
[selene.structs.DataModel.BindToClose]
method = true
[[selene.structs.DataModel.BindToClose.args]]
required = false
type = "any"
[selene.structs.DataModel.Changed]
struct = "Event"
[selene.structs.DataModel.ChildAdded]
struct = "Event"
[selene.structs.DataModel.ChildRemoved]
struct = "Event"
[selene.structs.DataModel.ClassName]
property = true
[selene.structs.DataModel.ClearAllChildren]
method = true
args = []
[selene.structs.DataModel.Clone]
method = true
args = []
[selene.structs.DataModel.Close]
struct = "Event"
[selene.structs.DataModel.CloseLate]
struct = "Event"
[selene.structs.DataModel.CreatorId]
property = true
[selene.structs.DataModel.CreatorType]
property = true
[selene.structs.DataModel.DefineFastFlag]
method = true
[[selene.structs.DataModel.DefineFastFlag.args]]
required = false
type = "any"
[[selene.structs.DataModel.DefineFastFlag.args]]
required = false
type = "any"
[selene.structs.DataModel.DefineFastInt]
method = true
[[selene.structs.DataModel.DefineFastInt.args]]
required = false
type = "any"
[[selene.structs.DataModel.DefineFastInt.args]]
required = false
type = "any"
[selene.structs.DataModel.DefineFastString]
method = true
[[selene.structs.DataModel.DefineFastString.args]]
required = false
type = "any"
[[selene.structs.DataModel.DefineFastString.args]]
required = false
type = "any"
[selene.structs.DataModel.DescendantAdded]
struct = "Event"
[selene.structs.DataModel.DescendantRemoving]
struct = "Event"
[selene.structs.DataModel.Destroy]
method = true
args = []
[selene.structs.DataModel.FindFirstAncestor]
method = true
[[selene.structs.DataModel.FindFirstAncestor.args]]
required = false
type = "any"
[selene.structs.DataModel.FindFirstAncestorOfClass]
method = true
[[selene.structs.DataModel.FindFirstAncestorOfClass.args]]
required = false
type = "any"
[selene.structs.DataModel.FindFirstAncestorWhichIsA]
method = true
[[selene.structs.DataModel.FindFirstAncestorWhichIsA.args]]
required = false
type = "any"
[selene.structs.DataModel.FindFirstChild]
method = true
[[selene.structs.DataModel.FindFirstChild.args]]
required = false
type = "any"
[[selene.structs.DataModel.FindFirstChild.args]]
required = false
type = "any"
[selene.structs.DataModel.FindFirstChildOfClass]
method = true
[[selene.structs.DataModel.FindFirstChildOfClass.args]]
required = false
type = "any"
[selene.structs.DataModel.FindFirstChildWhichIsA]
method = true
[[selene.structs.DataModel.FindFirstChildWhichIsA.args]]
required = false
type = "any"
[[selene.structs.DataModel.FindFirstChildWhichIsA.args]]
required = false
type = "any"
[selene.structs.DataModel.FindService]
method = true
[[selene.structs.DataModel.FindService.args]]
required = false
type = "any"
[selene.structs.DataModel.GameId]
property = true
[selene.structs.DataModel.Genre]
property = true
[selene.structs.DataModel.GetAttribute]
method = true
[[selene.structs.DataModel.GetAttribute.args]]
required = false
type = "any"
[selene.structs.DataModel.GetAttributeChangedSignal]
method = true
[[selene.structs.DataModel.GetAttributeChangedSignal.args]]
required = false
type = "any"
[selene.structs.DataModel.GetAttributes]
method = true
args = []
[selene.structs.DataModel.GetChildren]
method = true
args = []
[selene.structs.DataModel.GetDebugId]
method = true
[[selene.structs.DataModel.GetDebugId.args]]
required = false
type = "any"
[selene.structs.DataModel.GetDescendants]
method = true
args = []
[selene.structs.DataModel.GetEngineFeature]
method = true
[[selene.structs.DataModel.GetEngineFeature.args]]
required = false
type = "any"
[selene.structs.DataModel.GetFastFlag]
method = true
[[selene.structs.DataModel.GetFastFlag.args]]
required = false
type = "any"
[selene.structs.DataModel.GetFastInt]
method = true
[[selene.structs.DataModel.GetFastInt.args]]
required = false
type = "any"
[selene.structs.DataModel.GetFastString]
method = true
[[selene.structs.DataModel.GetFastString.args]]
required = false
type = "any"
[selene.structs.DataModel.GetFullName]
method = true
args = []
[selene.structs.DataModel.GetJobIntervalPeakFraction]
method = true
[[selene.structs.DataModel.GetJobIntervalPeakFraction.args]]
required = false
type = "any"
[[selene.structs.DataModel.GetJobIntervalPeakFraction.args]]
required = false
type = "any"
[selene.structs.DataModel.GetJobTimePeakFraction]
method = true
[[selene.structs.DataModel.GetJobTimePeakFraction.args]]
required = false
type = "any"
[[selene.structs.DataModel.GetJobTimePeakFraction.args]]
required = false
type = "any"
[selene.structs.DataModel.GetJobsExtendedStats]
method = true
args = []
[selene.structs.DataModel.GetJobsInfo]
method = true
args = []
[selene.structs.DataModel.GetObjects]
method = true
[[selene.structs.DataModel.GetObjects.args]]
required = false
type = "any"
[selene.structs.DataModel.GetObjectsAsync]
method = true
[[selene.structs.DataModel.GetObjectsAsync.args]]
required = false
type = "any"
[selene.structs.DataModel.GetObjectsList]
method = true
[[selene.structs.DataModel.GetObjectsList.args]]
required = false
type = "any"
[selene.structs.DataModel.GetPropertyChangedSignal]
method = true
[[selene.structs.DataModel.GetPropertyChangedSignal.args]]
required = false
type = "any"
[selene.structs.DataModel.GetService]
method = true
[[selene.structs.DataModel.GetService.args]]
type = ["ABTestService", "AdService", "AnalyticsService", "AssetManagerService", "AssetService", "BadgeService", "CoreGui", "StarterGui", "BrowserService", "BulkImportService", "CacheableContentProvider", "MeshContentProvider", "SolidModelContentProvider", "ChangeHistoryService", "Chat", "ClusterPacketCache", "CollectionService", "ContentProvider", "ContextActionService", "ControllerService", "CookiesService", "CorePackages", "CoreScriptSyncService", "DataStoreService", "Debris", "DraftsService", "EventIngestService", "FlagStandService", "FlyweightService", "CSGDictionaryService", "NonReplicatedCSGDictionaryService", "FriendService", "GamePassService", "GamepadService", "Geometry", "GoogleAnalyticsConfiguration", "GroupService", "GuiService", "GuidRegistryService", "HapticService", "Hopper", "HttpRbxApiService", "HttpService", "InsertService", "InternalContainer", "JointsService", "KeyboardService", "KeyframeSequenceProvider", "LanguageService", "Lighting", "LocalStorageService", "AppStorageService", "UserStorageService", "LocalizationService", "LogService", "LoginService", "LuaWebService", "MarketplaceService", "MemStorageService", "MessagingService", "MouseService", "NetworkClient", "NetworkServer", "NetworkSettings", "NotificationService", "Workspace", "PackageService", "PathfindingService", "PermissionsService", "PhysicsService", "PlayerEmulatorService", "Players", "PluginDebugService", "PluginGuiService", "PointsService", "PolicyService", "RbxAnalyticsService", "RenderSettings", "ReplicatedFirst", "ReplicatedScriptService", "ReplicatedStorage", "RobloxPluginGuiService", "RobloxReplicatedStorage", "RunService", "RuntimeScriptService", "ScriptContext", "ScriptService", "Selection", "ServerScriptService", "ServerStorage", "SessionService", "SocialService", "SoundService", "SpawnerService", "StarterPack", "StarterPlayer", "Stats", "StopWatchReporter", "Studio", "StudioData", "StudioService", "TaskScheduler", "Teams", "TeleportService", "TestService", "TextService", "ThirdPartyUserService", "TimerService", "TouchInputService", "TweenService", "UGCValidationService", "UserInputService", "UserService", "VRService", "VersionControlService", "VirtualInputManager", "VirtualUser", "Visit"]
[selene.structs.DataModel.GraphicsQualityChangeRequest]
struct = "Event"
[selene.structs.DataModel.HttpGetAsync]
method = true
[[selene.structs.DataModel.HttpGetAsync.args]]
required = false
type = "any"
[[selene.structs.DataModel.HttpGetAsync.args]]
required = false
type = "any"
[selene.structs.DataModel.HttpPostAsync]
method = true
[[selene.structs.DataModel.HttpPostAsync.args]]
required = false
type = "any"
[[selene.structs.DataModel.HttpPostAsync.args]]
required = false
type = "any"
[[selene.structs.DataModel.HttpPostAsync.args]]
required = false
type = "any"
[[selene.structs.DataModel.HttpPostAsync.args]]
required = false
type = "any"
[selene.structs.DataModel.InsertObjectsAndJoinIfLegacyAsync]
method = true
[[selene.structs.DataModel.InsertObjectsAndJoinIfLegacyAsync.args]]
required = false
type = "any"
[selene.structs.DataModel.IsA]
method = true
[[selene.structs.DataModel.IsA.args]]
required = false
type = "any"
[selene.structs.DataModel.IsAncestorOf]
method = true
[[selene.structs.DataModel.IsAncestorOf.args]]
required = false
type = "any"
[selene.structs.DataModel.IsDescendantOf]
method = true
[[selene.structs.DataModel.IsDescendantOf.args]]
required = false
type = "any"
[selene.structs.DataModel.IsLoaded]
method = true
args = []
[selene.structs.DataModel.JobId]
property = true
[selene.structs.DataModel.Load]
method = true
[[selene.structs.DataModel.Load.args]]
required = false
type = "any"
[selene.structs.DataModel.Loaded]
struct = "Event"
[selene.structs.DataModel.Name]
property = true
writable = "overridden"
[selene.structs.DataModel.OpenScreenshotsFolder]
method = true
args = []
[selene.structs.DataModel.OpenVideosFolder]
method = true
args = []
[selene.structs.DataModel.Parent]
struct = "Instance"
[selene.structs.DataModel.PlaceId]
property = true
[selene.structs.DataModel.PlaceVersion]
property = true
[selene.structs.DataModel.PrivateServerId]
property = true
[selene.structs.DataModel.PrivateServerOwnerId]
property = true
[selene.structs.DataModel.ReportInGoogleAnalytics]
method = true
[[selene.structs.DataModel.ReportInGoogleAnalytics.args]]
required = false
type = "any"
[[selene.structs.DataModel.ReportInGoogleAnalytics.args]]
required = false
type = "any"
[[selene.structs.DataModel.ReportInGoogleAnalytics.args]]
required = false
type = "any"
[[selene.structs.DataModel.ReportInGoogleAnalytics.args]]
required = false
type = "any"
[selene.structs.DataModel.ScreenshotReady]
struct = "Event"
[selene.structs.DataModel.ServiceAdded]
struct = "Event"
[selene.structs.DataModel.ServiceRemoving]
struct = "Event"
[selene.structs.DataModel.SetAttribute]
method = true
[[selene.structs.DataModel.SetAttribute.args]]
required = false
type = "any"
[[selene.structs.DataModel.SetAttribute.args]]
required = false
type = "any"
[selene.structs.DataModel.SetFastFlagForTesting]
method = true
[[selene.structs.DataModel.SetFastFlagForTesting.args]]
required = false
type = "any"
[[selene.structs.DataModel.SetFastFlagForTesting.args]]
required = false
type = "any"
[selene.structs.DataModel.SetFastIntForTesting]
method = true
[[selene.structs.DataModel.SetFastIntForTesting.args]]
required = false
type = "any"
[[selene.structs.DataModel.SetFastIntForTesting.args]]
required = false
type = "any"
[selene.structs.DataModel.SetFastStringForTesting]
method = true
[[selene.structs.DataModel.SetFastStringForTesting.args]]
required = false
type = "any"
[[selene.structs.DataModel.SetFastStringForTesting.args]]
required = false
type = "any"
[selene.structs.DataModel.SetPlaceId]
method = true
[[selene.structs.DataModel.SetPlaceId.args]]
required = false
type = "any"
[selene.structs.DataModel.SetUniverseId]
method = true
[[selene.structs.DataModel.SetUniverseId.args]]
required = false
type = "any"
[selene.structs.DataModel.Shutdown]
method = true
args = []
[selene.structs.DataModel.WaitForChild]
method = true
[[selene.structs.DataModel.WaitForChild.args]]
required = false
type = "any"
[[selene.structs.DataModel.WaitForChild.args]]
required = false
type = "any"
[selene.structs.DataModel.Workspace]
struct = "Workspace"
[selene.structs.EnumItem.Name]
property = true
[selene.structs.EnumItem.Value]
property = true
[selene.structs.Event.Connect]
method = true
[[selene.structs.Event.Connect.args]]
type = "function"
[selene.structs.Event.Wait]
method = true
args = []
[selene.structs.Instance."*"]
any = true
[selene.structs.Plugin."*"]
struct = "Instance"
[selene.structs.Plugin.Activate]
method = true
[[selene.structs.Plugin.Activate.args]]
required = false
type = "any"
[selene.structs.Plugin.AncestryChanged]
struct = "Event"
[selene.structs.Plugin.Archivable]
property = true
writable = "overridden"
[selene.structs.Plugin.AttributeChanged]
struct = "Event"
[selene.structs.Plugin.Changed]
struct = "Event"
[selene.structs.Plugin.ChildAdded]
struct = "Event"
[selene.structs.Plugin.ChildRemoved]
struct = "Event"
[selene.structs.Plugin.ClassName]
property = true
[selene.structs.Plugin.ClearAllChildren]
method = true
args = []
[selene.structs.Plugin.Clone]
method = true
args = []
[selene.structs.Plugin.CollisionEnabled]
property = true
[selene.structs.Plugin.CreateDockWidgetPluginGui]
method = true
[[selene.structs.Plugin.CreateDockWidgetPluginGui.args]]
required = false
type = "any"
[[selene.structs.Plugin.CreateDockWidgetPluginGui.args]]
required = false
type = "any"
[selene.structs.Plugin.CreatePluginAction]
method = true
[[selene.structs.Plugin.CreatePluginAction.args]]
required = false
type = "any"
[[selene.structs.Plugin.CreatePluginAction.args]]
required = false
type = "any"
[[selene.structs.Plugin.CreatePluginAction.args]]
required = false
type = "any"
[[selene.structs.Plugin.CreatePluginAction.args]]
required = false
type = "any"
[[selene.structs.Plugin.CreatePluginAction.args]]
required = false
type = "any"
[selene.structs.Plugin.CreatePluginMenu]
method = true
[[selene.structs.Plugin.CreatePluginMenu.args]]
required = false
type = "any"
[[selene.structs.Plugin.CreatePluginMenu.args]]
required = false
type = "any"
[[selene.structs.Plugin.CreatePluginMenu.args]]
required = false
type = "any"
[selene.structs.Plugin.CreateQWidgetPluginGui]
method = true
[[selene.structs.Plugin.CreateQWidgetPluginGui.args]]
required = false
type = "any"
[[selene.structs.Plugin.CreateQWidgetPluginGui.args]]
required = false
type = "any"
[selene.structs.Plugin.CreateToolbar]
method = true
[[selene.structs.Plugin.CreateToolbar.args]]
required = false
type = "any"
[selene.structs.Plugin.Deactivate]
method = true
args = []
[selene.structs.Plugin.Deactivation]
struct = "Event"
[selene.structs.Plugin.DescendantAdded]
struct = "Event"
[selene.structs.Plugin.DescendantRemoving]
struct = "Event"
[selene.structs.Plugin.Destroy]
method = true
args = []
[selene.structs.Plugin.FindFirstAncestor]
method = true
[[selene.structs.Plugin.FindFirstAncestor.args]]
required = false
type = "any"
[selene.structs.Plugin.FindFirstAncestorOfClass]
method = true
[[selene.structs.Plugin.FindFirstAncestorOfClass.args]]
required = false
type = "any"
[selene.structs.Plugin.FindFirstAncestorWhichIsA]
method = true
[[selene.structs.Plugin.FindFirstAncestorWhichIsA.args]]
required = false
type = "any"
[selene.structs.Plugin.FindFirstChild]
method = true
[[selene.structs.Plugin.FindFirstChild.args]]
required = false
type = "any"
[[selene.structs.Plugin.FindFirstChild.args]]
required = false
type = "any"
[selene.structs.Plugin.FindFirstChildOfClass]
method = true
[[selene.structs.Plugin.FindFirstChildOfClass.args]]
required = false
type = "any"
[selene.structs.Plugin.FindFirstChildWhichIsA]
method = true
[[selene.structs.Plugin.FindFirstChildWhichIsA.args]]
required = false
type = "any"
[[selene.structs.Plugin.FindFirstChildWhichIsA.args]]
required = false
type = "any"
[selene.structs.Plugin.GetAttribute]
method = true
[[selene.structs.Plugin.GetAttribute.args]]
required = false
type = "any"
[selene.structs.Plugin.GetAttributeChangedSignal]
method = true
[[selene.structs.Plugin.GetAttributeChangedSignal.args]]
required = false
type = "any"
[selene.structs.Plugin.GetAttributes]
method = true
args = []
[selene.structs.Plugin.GetChildren]
method = true
args = []
[selene.structs.Plugin.GetDebugId]
method = true
[[selene.structs.Plugin.GetDebugId.args]]
required = false
type = "any"
[selene.structs.Plugin.GetDescendants]
method = true
args = []
[selene.structs.Plugin.GetFullName]
method = true
args = []
[selene.structs.Plugin.GetItem]
method = true
[[selene.structs.Plugin.GetItem.args]]
required = false
type = "any"
[[selene.structs.Plugin.GetItem.args]]
required = false
type = "any"
[selene.structs.Plugin.GetJoinMode]
method = true
args = []
[selene.structs.Plugin.GetMouse]
method = true
args = []
[selene.structs.Plugin.GetPropertyChangedSignal]
method = true
[[selene.structs.Plugin.GetPropertyChangedSignal.args]]
required = false
type = "any"
[selene.structs.Plugin.GetSelectedRibbonTool]
method = true
args = []
[selene.structs.Plugin.GetSetting]
method = true
[[selene.structs.Plugin.GetSetting.args]]
required = false
type = "any"
[selene.structs.Plugin.GridSize]
property = true
[selene.structs.Plugin.ImportFbxAnimation]
method = true
[[selene.structs.Plugin.ImportFbxAnimation.args]]
required = false
type = "any"
[[selene.structs.Plugin.ImportFbxAnimation.args]]
required = false
type = "any"
[selene.structs.Plugin.ImportFbxRig]
method = true
[[selene.structs.Plugin.ImportFbxRig.args]]
required = false
type = "any"
[selene.structs.Plugin.Invoke]
method = true
[[selene.structs.Plugin.Invoke.args]]
required = false
type = "any"
[[selene.structs.Plugin.Invoke.args]]
required = false
type = "any"
[selene.structs.Plugin.IsA]
method = true
[[selene.structs.Plugin.IsA.args]]
required = false
type = "any"
[selene.structs.Plugin.IsActivated]
method = true
args = []
[selene.structs.Plugin.IsActivatedWithExclusiveMouse]
method = true
args = []
[selene.structs.Plugin.IsAncestorOf]
method = true
[[selene.structs.Plugin.IsAncestorOf.args]]
required = false
type = "any"
[selene.structs.Plugin.IsDescendantOf]
method = true
[[selene.structs.Plugin.IsDescendantOf.args]]
required = false
type = "any"
[selene.structs.Plugin.Name]
property = true
writable = "overridden"
[selene.structs.Plugin.Negate]
method = true
[[selene.structs.Plugin.Negate.args]]
required = false
type = "any"
[selene.structs.Plugin.OnInvoke]
method = true
[[selene.structs.Plugin.OnInvoke.args]]
required = false
type = "any"
[[selene.structs.Plugin.OnInvoke.args]]
required = false
type = "any"
[selene.structs.Plugin.OnSetItem]
method = true
[[selene.structs.Plugin.OnSetItem.args]]
required = false
type = "any"
[[selene.structs.Plugin.OnSetItem.args]]
required = false
type = "any"
[selene.structs.Plugin.OpenScript]
method = true
[[selene.structs.Plugin.OpenScript.args]]
required = false
type = "any"
[[selene.structs.Plugin.OpenScript.args]]
required = false
type = "any"
[selene.structs.Plugin.OpenWikiPage]
method = true
[[selene.structs.Plugin.OpenWikiPage.args]]
required = false
type = "any"
[selene.structs.Plugin.Parent]
struct = "Instance"
[selene.structs.Plugin.PauseSound]
method = true
[[selene.structs.Plugin.PauseSound.args]]
required = false
type = "any"
[selene.structs.Plugin.PlaySound]
method = true
[[selene.structs.Plugin.PlaySound.args]]
required = false
type = "any"
[selene.structs.Plugin.PromptForExistingAssetId]
method = true
[[selene.structs.Plugin.PromptForExistingAssetId.args]]
required = false
type = "any"
[selene.structs.Plugin.PromptSaveSelection]
method = true
[[selene.structs.Plugin.PromptSaveSelection.args]]
required = false
type = "any"
[selene.structs.Plugin.ResumeSound]
method = true
[[selene.structs.Plugin.ResumeSound.args]]
required = false
type = "any"
[selene.structs.Plugin.SaveSelectedToRoblox]
method = true
args = []
[selene.structs.Plugin.SelectRibbonTool]
method = true
[[selene.structs.Plugin.SelectRibbonTool.args]]
required = false
type = "any"
[[selene.structs.Plugin.SelectRibbonTool.args]]
required = false
type = "any"
[selene.structs.Plugin.Separate]
method = true
[[selene.structs.Plugin.Separate.args]]
required = false
type = "any"
[selene.structs.Plugin.SetAttribute]
method = true
[[selene.structs.Plugin.SetAttribute.args]]
required = false
type = "any"
[[selene.structs.Plugin.SetAttribute.args]]
required = false
type = "any"
[selene.structs.Plugin.SetItem]
method = true
[[selene.structs.Plugin.SetItem.args]]
required = false
type = "any"
[[selene.structs.Plugin.SetItem.args]]
required = false
type = "any"
[selene.structs.Plugin.SetSetting]
method = true
[[selene.structs.Plugin.SetSetting.args]]
required = false
type = "any"
[[selene.structs.Plugin.SetSetting.args]]
required = false
type = "any"
[selene.structs.Plugin.StartDecalDrag]
method = true
[[selene.structs.Plugin.StartDecalDrag.args]]
required = false
type = "any"
[selene.structs.Plugin.StartDrag]
method = true
[[selene.structs.Plugin.StartDrag.args]]
required = false
type = "any"
[selene.structs.Plugin.StopAllSounds]
method = true
args = []
[selene.structs.Plugin.Union]
method = true
[[selene.structs.Plugin.Union.args]]
required = false
type = "any"
[selene.structs.Plugin.Unloading]
struct = "Event"
[selene.structs.Plugin.WaitForChild]
method = true
[[selene.structs.Plugin.WaitForChild.args]]
required = false
type = "any"
[[selene.structs.Plugin.WaitForChild.args]]
required = false
type = "any"
[selene.structs.Script."*"]
struct = "Instance"
[selene.structs.Script.AncestryChanged]
struct = "Event"
[selene.structs.Script.Archivable]
property = true
writable = "overridden"
[selene.structs.Script.AttributeChanged]
struct = "Event"
[selene.structs.Script.Changed]
struct = "Event"
[selene.structs.Script.ChildAdded]
struct = "Event"
[selene.structs.Script.ChildRemoved]
struct = "Event"
[selene.structs.Script.ClassName]
property = true
[selene.structs.Script.ClearAllChildren]
method = true
args = []
[selene.structs.Script.Clone]
method = true
args = []
[selene.structs.Script.CurrentEditor]
struct = "Instance"
[selene.structs.Script.DescendantAdded]
struct = "Event"
[selene.structs.Script.DescendantRemoving]
struct = "Event"
[selene.structs.Script.Destroy]
method = true
args = []
[selene.structs.Script.Disabled]
property = true
writable = "overridden"
[selene.structs.Script.FindFirstAncestor]
method = true
[[selene.structs.Script.FindFirstAncestor.args]]
required = false
type = "any"
[selene.structs.Script.FindFirstAncestorOfClass]
method = true
[[selene.structs.Script.FindFirstAncestorOfClass.args]]
required = false
type = "any"
[selene.structs.Script.FindFirstAncestorWhichIsA]
method = true
[[selene.structs.Script.FindFirstAncestorWhichIsA.args]]
required = false
type = "any"
[selene.structs.Script.FindFirstChild]
method = true
[[selene.structs.Script.FindFirstChild.args]]
required = false
type = "any"
[[selene.structs.Script.FindFirstChild.args]]
required = false
type = "any"
[selene.structs.Script.FindFirstChildOfClass]
method = true
[[selene.structs.Script.FindFirstChildOfClass.args]]
required = false
type = "any"
[selene.structs.Script.FindFirstChildWhichIsA]
method = true
[[selene.structs.Script.FindFirstChildWhichIsA.args]]
required = false
type = "any"
[[selene.structs.Script.FindFirstChildWhichIsA.args]]
required = false
type = "any"
[selene.structs.Script.GetAttribute]
method = true
[[selene.structs.Script.GetAttribute.args]]
required = false
type = "any"
[selene.structs.Script.GetAttributeChangedSignal]
method = true
[[selene.structs.Script.GetAttributeChangedSignal.args]]
required = false
type = "any"
[selene.structs.Script.GetAttributes]
method = true
args = []
[selene.structs.Script.GetChildren]
method = true
args = []
[selene.structs.Script.GetDebugId]
method = true
[[selene.structs.Script.GetDebugId.args]]
required = false
type = "any"
[selene.structs.Script.GetDescendants]
method = true
args = []
[selene.structs.Script.GetFullName]
method = true
args = []
[selene.structs.Script.GetHash]
method = true
args = []
[selene.structs.Script.GetPropertyChangedSignal]
method = true
[[selene.structs.Script.GetPropertyChangedSignal.args]]
required = false
type = "any"
[selene.structs.Script.IsA]
method = true
[[selene.structs.Script.IsA.args]]
required = false
type = "any"
[selene.structs.Script.IsAncestorOf]
method = true
[[selene.structs.Script.IsAncestorOf.args]]
required = false
type = "any"
[selene.structs.Script.IsDescendantOf]
method = true
[[selene.structs.Script.IsDescendantOf.args]]
required = false
type = "any"
[selene.structs.Script.LinkedSource]
property = true
writable = "overridden"
[selene.structs.Script.Name]
property = true
writable = "overridden"
[selene.structs.Script.Parent]
struct = "Instance"
[selene.structs.Script.SetAttribute]
method = true
[[selene.structs.Script.SetAttribute.args]]
required = false
type = "any"
[[selene.structs.Script.SetAttribute.args]]
required = false
type = "any"
[selene.structs.Script.WaitForChild]
method = true
[[selene.structs.Script.WaitForChild.args]]
required = false
type = "any"
[[selene.structs.Script.WaitForChild.args]]
required = false
type = "any"
[selene.structs.Workspace."*"]
struct = "Instance"
[selene.structs.Workspace.AllowThirdPartySales]
property = true
writable = "overridden"
[selene.structs.Workspace.AncestryChanged]
struct = "Event"
[selene.structs.Workspace.Archivable]
property = true
writable = "overridden"
[selene.structs.Workspace.ArePartsTouchingOthers]
method = true
[[selene.structs.Workspace.ArePartsTouchingOthers.args]]
required = false
type = "any"
[[selene.structs.Workspace.ArePartsTouchingOthers.args]]
required = false
type = "any"
[selene.structs.Workspace.AttributeChanged]
struct = "Event"
[selene.structs.Workspace.BreakJoints]
method = true
args = []
[selene.structs.Workspace.BulkMoveTo]
method = true
[[selene.structs.Workspace.BulkMoveTo.args]]
required = false
type = "any"
[[selene.structs.Workspace.BulkMoveTo.args]]
required = false
type = "any"
[[selene.structs.Workspace.BulkMoveTo.args]]
required = false
type = "any"
[selene.structs.Workspace.CalculateJumpDistance]
method = true
[[selene.structs.Workspace.CalculateJumpDistance.args]]
required = false
type = "any"
[[selene.structs.Workspace.CalculateJumpDistance.args]]
required = false
type = "any"
[[selene.structs.Workspace.CalculateJumpDistance.args]]
required = false
type = "any"
[selene.structs.Workspace.CalculateJumpHeight]
method = true
[[selene.structs.Workspace.CalculateJumpHeight.args]]
required = false
type = "any"
[[selene.structs.Workspace.CalculateJumpHeight.args]]
required = false
type = "any"
[selene.structs.Workspace.CalculateJumpPower]
method = true
[[selene.structs.Workspace.CalculateJumpPower.args]]
required = false
type = "any"
[[selene.structs.Workspace.CalculateJumpPower.args]]
required = false
type = "any"
[selene.structs.Workspace.Changed]
struct = "Event"
[selene.structs.Workspace.ChildAdded]
struct = "Event"
[selene.structs.Workspace.ChildRemoved]
struct = "Event"
[selene.structs.Workspace.ClassName]
property = true
[selene.structs.Workspace.ClearAllChildren]
method = true
args = []
[selene.structs.Workspace.Clone]
method = true
args = []
[selene.structs.Workspace.CurrentCamera]
struct = "Camera"
[selene.structs.Workspace.DescendantAdded]
struct = "Event"
[selene.structs.Workspace.DescendantRemoving]
struct = "Event"
[selene.structs.Workspace.Destroy]
method = true
args = []
[selene.structs.Workspace.DistributedGameTime]
property = true
writable = "overridden"
[selene.structs.Workspace.ExperimentalSolverIsEnabled]
method = true
args = []
[selene.structs.Workspace.FindFirstAncestor]
method = true
[[selene.structs.Workspace.FindFirstAncestor.args]]
required = false
type = "any"
[selene.structs.Workspace.FindFirstAncestorOfClass]
method = true
[[selene.structs.Workspace.FindFirstAncestorOfClass.args]]
required = false
type = "any"
[selene.structs.Workspace.FindFirstAncestorWhichIsA]
method = true
[[selene.structs.Workspace.FindFirstAncestorWhichIsA.args]]
required = false
type = "any"
[selene.structs.Workspace.FindFirstChild]
method = true
[[selene.structs.Workspace.FindFirstChild.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindFirstChild.args]]
required = false
type = "any"
[selene.structs.Workspace.FindFirstChildOfClass]
method = true
[[selene.structs.Workspace.FindFirstChildOfClass.args]]
required = false
type = "any"
[selene.structs.Workspace.FindFirstChildWhichIsA]
method = true
[[selene.structs.Workspace.FindFirstChildWhichIsA.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindFirstChildWhichIsA.args]]
required = false
type = "any"
[selene.structs.Workspace.FindPartOnRay]
method = true
[[selene.structs.Workspace.FindPartOnRay.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindPartOnRay.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindPartOnRay.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindPartOnRay.args]]
required = false
type = "any"
[selene.structs.Workspace.FindPartOnRayWithIgnoreList]
method = true
[[selene.structs.Workspace.FindPartOnRayWithIgnoreList.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindPartOnRayWithIgnoreList.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindPartOnRayWithIgnoreList.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindPartOnRayWithIgnoreList.args]]
required = false
type = "any"
[selene.structs.Workspace.FindPartOnRayWithWhitelist]
method = true
[[selene.structs.Workspace.FindPartOnRayWithWhitelist.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindPartOnRayWithWhitelist.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindPartOnRayWithWhitelist.args]]
required = false
type = "any"
[selene.structs.Workspace.FindPartsInRegion3]
method = true
[[selene.structs.Workspace.FindPartsInRegion3.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindPartsInRegion3.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindPartsInRegion3.args]]
required = false
type = "any"
[selene.structs.Workspace.FindPartsInRegion3WithIgnoreList]
method = true
[[selene.structs.Workspace.FindPartsInRegion3WithIgnoreList.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindPartsInRegion3WithIgnoreList.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindPartsInRegion3WithIgnoreList.args]]
required = false
type = "any"
[selene.structs.Workspace.FindPartsInRegion3WithWhiteList]
method = true
[[selene.structs.Workspace.FindPartsInRegion3WithWhiteList.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindPartsInRegion3WithWhiteList.args]]
required = false
type = "any"
[[selene.structs.Workspace.FindPartsInRegion3WithWhiteList.args]]
required = false
type = "any"
[selene.structs.Workspace.GetAttribute]
method = true
[[selene.structs.Workspace.GetAttribute.args]]
required = false
type = "any"
[selene.structs.Workspace.GetAttributeChangedSignal]
method = true
[[selene.structs.Workspace.GetAttributeChangedSignal.args]]
required = false
type = "any"
[selene.structs.Workspace.GetAttributes]
method = true
args = []
[selene.structs.Workspace.GetBoundingBox]
method = true
args = []
[selene.structs.Workspace.GetChildren]
method = true
args = []
[selene.structs.Workspace.GetDebugId]
method = true
[[selene.structs.Workspace.GetDebugId.args]]
required = false
type = "any"
[selene.structs.Workspace.GetDescendants]
method = true
args = []
[selene.structs.Workspace.GetExtentsSize]
method = true
args = []
[selene.structs.Workspace.GetFullName]
method = true
args = []
[selene.structs.Workspace.GetNumAwakeParts]
method = true
args = []
[selene.structs.Workspace.GetPhysicsThrottling]
method = true
args = []
[selene.structs.Workspace.GetPrimaryPartCFrame]
method = true
args = []
[selene.structs.Workspace.GetPropertyChangedSignal]
method = true
[[selene.structs.Workspace.GetPropertyChangedSignal.args]]
required = false
type = "any"
[selene.structs.Workspace.GetRealPhysicsFPS]
method = true
args = []
[selene.structs.Workspace.Gravity]
property = true
writable = "overridden"
[selene.structs.Workspace.IKMoveTo]
method = true
[[selene.structs.Workspace.IKMoveTo.args]]
required = false
type = "any"
[[selene.structs.Workspace.IKMoveTo.args]]
required = false
type = "any"
[[selene.structs.Workspace.IKMoveTo.args]]
required = false
type = "any"
[[selene.structs.Workspace.IKMoveTo.args]]
required = false
type = "any"
[[selene.structs.Workspace.IKMoveTo.args]]
required = false
type = "any"
[selene.structs.Workspace.IsA]
method = true
[[selene.structs.Workspace.IsA.args]]
required = false
type = "any"
[selene.structs.Workspace.IsAncestorOf]
method = true
[[selene.structs.Workspace.IsAncestorOf.args]]
required = false
type = "any"
[selene.structs.Workspace.IsDescendantOf]
method = true
[[selene.structs.Workspace.IsDescendantOf.args]]
required = false
type = "any"
[selene.structs.Workspace.IsRegion3Empty]
method = true
[[selene.structs.Workspace.IsRegion3Empty.args]]
required = false
type = "any"
[[selene.structs.Workspace.IsRegion3Empty.args]]
required = false
type = "any"
[selene.structs.Workspace.IsRegion3EmptyWithIgnoreList]
method = true
[[selene.structs.Workspace.IsRegion3EmptyWithIgnoreList.args]]
required = false
type = "any"
[[selene.structs.Workspace.IsRegion3EmptyWithIgnoreList.args]]
required = false
type = "any"
[selene.structs.Workspace.JoinToOutsiders]
method = true
[[selene.structs.Workspace.JoinToOutsiders.args]]
required = false
type = "any"
[[selene.structs.Workspace.JoinToOutsiders.args]]
required = false
type = "any"
[selene.structs.Workspace.MakeJoints]
method = true
args = []
[selene.structs.Workspace.MoveTo]
method = true
[[selene.structs.Workspace.MoveTo.args]]
required = false
type = "any"
[selene.structs.Workspace.Name]
property = true
writable = "overridden"
[selene.structs.Workspace.PGSIsEnabled]
method = true
args = []
[selene.structs.Workspace.Parent]
struct = "Instance"
[selene.structs.Workspace.PhysicsSimulationRate]
property = true
writable = "overridden"
[selene.structs.Workspace.PrimaryPart]
struct = "BasePart"
[selene.structs.Workspace.Raycast]
method = true
[[selene.structs.Workspace.Raycast.args]]
required = false
type = "any"
[[selene.structs.Workspace.Raycast.args]]
required = false
type = "any"
[[selene.structs.Workspace.Raycast.args]]
required = false
type = "any"
[selene.structs.Workspace.SetAttribute]
method = true
[[selene.structs.Workspace.SetAttribute.args]]
required = false
type = "any"
[[selene.structs.Workspace.SetAttribute.args]]
required = false
type = "any"
[selene.structs.Workspace.SetInsertPoint]
method = true
[[selene.structs.Workspace.SetInsertPoint.args]]
required = false
type = "any"
[[selene.structs.Workspace.SetInsertPoint.args]]
required = false
type = "any"
[selene.structs.Workspace.SetPhysicsThrottleEnabled]
method = true
[[selene.structs.Workspace.SetPhysicsThrottleEnabled.args]]
required = false
type = "any"
[selene.structs.Workspace.SetPrimaryPartCFrame]
method = true
[[selene.structs.Workspace.SetPrimaryPartCFrame.args]]
required = false
type = "any"
[selene.structs.Workspace.SkinnedMeshEnabled]
property = true
writable = "overridden"
[selene.structs.Workspace.StreamingMinRadius]
property = true
writable = "overridden"
[selene.structs.Workspace.StreamingPauseMode]
property = true
writable = "overridden"
[selene.structs.Workspace.StreamingTargetRadius]
property = true
writable = "overridden"
[selene.structs.Workspace.TemporaryLegacyPhysicsSolverOverride]
property = true
writable = "overridden"
[selene.structs.Workspace.Terrain]
struct = "Instance"
[selene.structs.Workspace.TranslateBy]
method = true
[[selene.structs.Workspace.TranslateBy.args]]
required = false
type = "any"
[selene.structs.Workspace.UnjoinFromOutsiders]
method = true
[[selene.structs.Workspace.UnjoinFromOutsiders.args]]
required = false
type = "any"
[selene.structs.Workspace.WaitForChild]
method = true
[[selene.structs.Workspace.WaitForChild.args]]
required = false
type = "any"
[[selene.structs.Workspace.WaitForChild.args]]
required = false
type = "any"
[selene.structs.Workspace.ZoomToExtents]
method = true
args = []
[[Axes.new.args]]
type = "..."
[BrickColor.Black]
args = []
[BrickColor.Blue]
args = []
[BrickColor.DarkGray]
args = []
[BrickColor.Gray]
args = []
[BrickColor.Green]
args = []
[BrickColor.Red]
args = []
[BrickColor.White]
args = []
[BrickColor.Yellow]
args = []
[[BrickColor.new.args]]
type = "any"
[[BrickColor.new.args]]
required = false
type = "number"
[[BrickColor.new.args]]
required = false
type = "number"
[[BrickColor.palette.args]]
type = "number"
[BrickColor.random]
args = []
[[CFrame.Angles.args]]
required = false
type = "number"
[[CFrame.Angles.args]]
required = false
type = "number"
[[CFrame.Angles.args]]
required = false
type = "number"
[[CFrame.fromAxisAngle.args]]
[CFrame.fromAxisAngle.args.type]
display = "Vector3"
[[CFrame.fromAxisAngle.args]]
type = "number"
[[CFrame.fromEulerAnglesXYZ.args]]
type = "number"
[[CFrame.fromEulerAnglesXYZ.args]]
type = "number"
[[CFrame.fromEulerAnglesXYZ.args]]
type = "number"
[[CFrame.fromEulerAnglesYXZ.args]]
type = "number"
[[CFrame.fromEulerAnglesYXZ.args]]
type = "number"
[[CFrame.fromEulerAnglesYXZ.args]]
type = "number"
[[CFrame.fromMatrix.args]]
[CFrame.fromMatrix.args.type]
display = "Vector3"
[[CFrame.fromMatrix.args]]
[CFrame.fromMatrix.args.type]
display = "Vector3"
[[CFrame.fromMatrix.args]]
[CFrame.fromMatrix.args.type]
display = "Vector3"
[[CFrame.fromMatrix.args]]
required = false
[CFrame.fromMatrix.args.type]
display = "Vector3"
[[CFrame.fromOrientation.args]]
type = "number"
[[CFrame.fromOrientation.args]]
type = "number"
[[CFrame.fromOrientation.args]]
type = "number"
[[CFrame.new.args]]
required = false
type = "any"
[[CFrame.new.args]]
required = false
type = "any"
[[CFrame.new.args]]
required = false
type = "number"
[[CFrame.new.args]]
required = false
type = "number"
[[CFrame.new.args]]
required = false
type = "number"
[[CFrame.new.args]]
required = false
type = "number"
[[CFrame.new.args]]
required = false
type = "number"
[[CFrame.new.args]]
required = false
type = "number"
[[CFrame.new.args]]
required = false
type = "number"
[[CFrame.new.args]]
required = false
type = "number"
[[CFrame.new.args]]
required = false
type = "number"
[[CFrame.new.args]]
required = false
type = "number"
[[Color3.fromHSV.args]]
type = "number"
[[Color3.fromHSV.args]]
type = "number"
[[Color3.fromHSV.args]]
type = "number"
[[Color3.fromRGB.args]]
type = "number"
[[Color3.fromRGB.args]]
type = "number"
[[Color3.fromRGB.args]]
type = "number"
[[Color3.new.args]]
required = false
type = "number"
[[Color3.new.args]]
required = false
type = "number"
[[Color3.new.args]]
required = false
type = "number"
[[Color3.toHSV.args]]
[Color3.toHSV.args.type]
display = "Color3"
[[ColorSequence.new.args]]
type = "any"
[[ColorSequence.new.args]]
required = false
[ColorSequence.new.args.type]
display = "Color3"
[[ColorSequenceKeypoint.new.args]]
type = "number"
[[ColorSequenceKeypoint.new.args]]
[ColorSequenceKeypoint.new.args.type]
display = "Color3"
[DebuggerManager]
args = []
[[DockWidgetPluginGuiInfo.new.args]]
required = false
[DockWidgetPluginGuiInfo.new.args.type]
display = "InitialDockState"
[[DockWidgetPluginGuiInfo.new.args]]
required = false
type = "bool"
[[DockWidgetPluginGuiInfo.new.args]]
required = false
type = "bool"
[[DockWidgetPluginGuiInfo.new.args]]
required = false
type = "number"
[[DockWidgetPluginGuiInfo.new.args]]
required = false
type = "number"
[[DockWidgetPluginGuiInfo.new.args]]
required = false
type = "number"
[[DockWidgetPluginGuiInfo.new.args]]
required = false
type = "number"
[Enum.ABTestLoadingStatus.Error]
struct = "EnumItem"
[Enum.ABTestLoadingStatus.GetEnumItems]
args = []
method = true
[Enum.ABTestLoadingStatus.Initialized]
struct = "EnumItem"
[Enum.ABTestLoadingStatus.None]
struct = "EnumItem"
[Enum.ABTestLoadingStatus.Pending]
struct = "EnumItem"
[Enum.ABTestLoadingStatus.ShutOff]
struct = "EnumItem"
[Enum.ABTestLoadingStatus.TimedOut]
struct = "EnumItem"
[Enum.ActionType.Draw]
struct = "EnumItem"
[Enum.ActionType.GetEnumItems]
args = []
method = true
[Enum.ActionType.Lose]
struct = "EnumItem"
[Enum.ActionType.Nothing]
struct = "EnumItem"
[Enum.ActionType.Pause]
struct = "EnumItem"
[Enum.ActionType.Win]
struct = "EnumItem"
[Enum.ActuatorRelativeTo.Attachment0]
struct = "EnumItem"
[Enum.ActuatorRelativeTo.Attachment1]
struct = "EnumItem"
[Enum.ActuatorRelativeTo.GetEnumItems]
args = []
method = true
[Enum.ActuatorRelativeTo.World]
struct = "EnumItem"
[Enum.ActuatorType.GetEnumItems]
args = []
method = true
[Enum.ActuatorType.Motor]
struct = "EnumItem"
[Enum.ActuatorType.None]
struct = "EnumItem"
[Enum.ActuatorType.Servo]
struct = "EnumItem"
[Enum.AlignType.GetEnumItems]
args = []
method = true
[Enum.AlignType.Parallel]
struct = "EnumItem"
[Enum.AlignType.Perpendicular]
struct = "EnumItem"
[Enum.AlphaMode.GetEnumItems]
args = []
method = true
[Enum.AlphaMode.Overlay]
struct = "EnumItem"
[Enum.AlphaMode.Transparency]
struct = "EnumItem"
[Enum.AnimationPriority.Action]
struct = "EnumItem"
[Enum.AnimationPriority.Core]
struct = "EnumItem"
[Enum.AnimationPriority.GetEnumItems]
args = []
method = true
[Enum.AnimationPriority.Idle]
struct = "EnumItem"
[Enum.AnimationPriority.Movement]
struct = "EnumItem"
[Enum.AppShellActionType.AvatarEditorPageLoaded]
struct = "EnumItem"
[Enum.AppShellActionType.GamePageLoaded]
struct = "EnumItem"
[Enum.AppShellActionType.GetEnumItems]
args = []
method = true
[Enum.AppShellActionType.HomePageLoaded]
struct = "EnumItem"
[Enum.AppShellActionType.None]
struct = "EnumItem"
[Enum.AppShellActionType.OpenApp]
struct = "EnumItem"
[Enum.AppShellActionType.ReadConversation]
struct = "EnumItem"
[Enum.AppShellActionType.TapAvatarTab]
struct = "EnumItem"
[Enum.AppShellActionType.TapChatTab]
struct = "EnumItem"
[Enum.AppShellActionType.TapConversationEntry]
struct = "EnumItem"
[Enum.AppShellActionType.TapGamePageTab]
struct = "EnumItem"
[Enum.AppShellActionType.TapHomePageTab]
struct = "EnumItem"
[Enum.AspectType.FitWithinMaxSize]
struct = "EnumItem"
[Enum.AspectType.GetEnumItems]
args = []
method = true
[Enum.AspectType.ScaleWithParentSize]
struct = "EnumItem"
[Enum.AssetFetchStatus.Failure]
struct = "EnumItem"
[Enum.AssetFetchStatus.GetEnumItems]
args = []
method = true
[Enum.AssetFetchStatus.Success]
struct = "EnumItem"
[Enum.AssetType.Animation]
struct = "EnumItem"
[Enum.AssetType.Audio]
struct = "EnumItem"
[Enum.AssetType.BackAccessory]
struct = "EnumItem"
[Enum.AssetType.Badge]
struct = "EnumItem"
[Enum.AssetType.ClimbAnimation]
struct = "EnumItem"
[Enum.AssetType.DeathAnimation]
struct = "EnumItem"
[Enum.AssetType.Decal]
struct = "EnumItem"
[Enum.AssetType.EarAccessory]
struct = "EnumItem"
[Enum.AssetType.EmoteAnimation]
struct = "EnumItem"
[Enum.AssetType.EyeAccessory]
struct = "EnumItem"
[Enum.AssetType.Face]
struct = "EnumItem"
[Enum.AssetType.FaceAccessory]
struct = "EnumItem"
[Enum.AssetType.FallAnimation]
struct = "EnumItem"
[Enum.AssetType.FrontAccessory]
struct = "EnumItem"
[Enum.AssetType.GamePass]
struct = "EnumItem"
[Enum.AssetType.Gear]
struct = "EnumItem"
[Enum.AssetType.GetEnumItems]
args = []
method = true
[Enum.AssetType.HairAccessory]
struct = "EnumItem"
[Enum.AssetType.Hat]
struct = "EnumItem"
[Enum.AssetType.Head]
struct = "EnumItem"
[Enum.AssetType.IdleAnimation]
struct = "EnumItem"
[Enum.AssetType.Image]
struct = "EnumItem"
[Enum.AssetType.JumpAnimation]
struct = "EnumItem"
[Enum.AssetType.LeftArm]
struct = "EnumItem"
[Enum.AssetType.LeftLeg]
struct = "EnumItem"
[Enum.AssetType.Lua]
struct = "EnumItem"
[Enum.AssetType.Mesh]
struct = "EnumItem"
[Enum.AssetType.MeshPart]
struct = "EnumItem"
[Enum.AssetType.Model]
struct = "EnumItem"
[Enum.AssetType.NeckAccessory]
struct = "EnumItem"
[Enum.AssetType.Package]
struct = "EnumItem"
[Enum.AssetType.Pants]
struct = "EnumItem"
[Enum.AssetType.Place]
struct = "EnumItem"
[Enum.AssetType.Plugin]
struct = "EnumItem"
[Enum.AssetType.PoseAnimation]
struct = "EnumItem"
[Enum.AssetType.RightArm]
struct = "EnumItem"
[Enum.AssetType.RightLeg]
struct = "EnumItem"
[Enum.AssetType.RunAnimation]
struct = "EnumItem"
[Enum.AssetType.Shirt]
struct = "EnumItem"
[Enum.AssetType.ShoulderAccessory]
struct = "EnumItem"
[Enum.AssetType.SwimAnimation]
struct = "EnumItem"
[Enum.AssetType.TeeShirt]
struct = "EnumItem"
[Enum.AssetType.Torso]
struct = "EnumItem"
[Enum.AssetType.Video]
struct = "EnumItem"
[Enum.AssetType.WaistAccessory]
struct = "EnumItem"
[Enum.AssetType.WalkAnimation]
struct = "EnumItem"
[Enum.AutoIndentRule.Absolute]
struct = "EnumItem"
[Enum.AutoIndentRule.GetEnumItems]
args = []
method = true
[Enum.AutoIndentRule.Off]
struct = "EnumItem"
[Enum.AutoIndentRule.Relative]
struct = "EnumItem"
[Enum.AvatarContextMenuOption.Chat]
struct = "EnumItem"
[Enum.AvatarContextMenuOption.Emote]
struct = "EnumItem"
[Enum.AvatarContextMenuOption.Friend]
struct = "EnumItem"
[Enum.AvatarContextMenuOption.GetEnumItems]
args = []
method = true
[Enum.AvatarContextMenuOption.InspectMenu]
struct = "EnumItem"
[Enum.AvatarJointPositionType.ArtistIntent]
struct = "EnumItem"
[Enum.AvatarJointPositionType.Fixed]
struct = "EnumItem"
[Enum.AvatarJointPositionType.GetEnumItems]
args = []
method = true
[Enum.Axis.GetEnumItems]
args = []
method = true
[Enum.Axis.X]
struct = "EnumItem"
[Enum.Axis.Y]
struct = "EnumItem"
[Enum.Axis.Z]
struct = "EnumItem"
[Enum.BinType.Clone]
struct = "EnumItem"
[Enum.BinType.GameTool]
struct = "EnumItem"
[Enum.BinType.GetEnumItems]
args = []
method = true
[Enum.BinType.Grab]
struct = "EnumItem"
[Enum.BinType.Hammer]
struct = "EnumItem"
[Enum.BinType.Script]
struct = "EnumItem"
[Enum.BodyPart.GetEnumItems]
args = []
method = true
[Enum.BodyPart.Head]
struct = "EnumItem"
[Enum.BodyPart.LeftArm]
struct = "EnumItem"
[Enum.BodyPart.LeftLeg]
struct = "EnumItem"
[Enum.BodyPart.RightArm]
struct = "EnumItem"
[Enum.BodyPart.RightLeg]
struct = "EnumItem"
[Enum.BodyPart.Torso]
struct = "EnumItem"
[Enum.BodyPartR15.GetEnumItems]
args = []
method = true
[Enum.BodyPartR15.Head]
struct = "EnumItem"
[Enum.BodyPartR15.LeftFoot]
struct = "EnumItem"
[Enum.BodyPartR15.LeftHand]
struct = "EnumItem"
[Enum.BodyPartR15.LeftLowerArm]
struct = "EnumItem"
[Enum.BodyPartR15.LeftLowerLeg]
struct = "EnumItem"
[Enum.BodyPartR15.LeftUpperArm]
struct = "EnumItem"
[Enum.BodyPartR15.LeftUpperLeg]
struct = "EnumItem"
[Enum.BodyPartR15.LowerTorso]
struct = "EnumItem"
[Enum.BodyPartR15.RightFoot]
struct = "EnumItem"
[Enum.BodyPartR15.RightHand]
struct = "EnumItem"
[Enum.BodyPartR15.RightLowerArm]
struct = "EnumItem"
[Enum.BodyPartR15.RightLowerLeg]
struct = "EnumItem"
[Enum.BodyPartR15.RightUpperArm]
struct = "EnumItem"
[Enum.BodyPartR15.RightUpperLeg]
struct = "EnumItem"
[Enum.BodyPartR15.RootPart]
struct = "EnumItem"
[Enum.BodyPartR15.Unknown]
struct = "EnumItem"
[Enum.BodyPartR15.UpperTorso]
struct = "EnumItem"
[Enum.BorderMode.GetEnumItems]
args = []
method = true
[Enum.BorderMode.Inset]
struct = "EnumItem"
[Enum.BorderMode.Middle]
struct = "EnumItem"
[Enum.BorderMode.Outline]
struct = "EnumItem"
[Enum.BreakReason.Error]
struct = "EnumItem"
[Enum.BreakReason.GetEnumItems]
args = []
method = true
[Enum.BreakReason.Other]
struct = "EnumItem"
[Enum.BreakReason.SpecialBreakpoint]
struct = "EnumItem"
[Enum.BreakReason.UserBreakpoint]
struct = "EnumItem"
[Enum.BulkMoveMode.FireAllEvents]
struct = "EnumItem"
[Enum.BulkMoveMode.FireCFrameChanged]
struct = "EnumItem"
[Enum.BulkMoveMode.GetEnumItems]
args = []
method = true
[Enum.Button.Dismount]
struct = "EnumItem"
[Enum.Button.GetEnumItems]
args = []
method = true
[Enum.Button.Jump]
struct = "EnumItem"
[Enum.ButtonStyle.Custom]
struct = "EnumItem"
[Enum.ButtonStyle.GetEnumItems]
args = []
method = true
[Enum.ButtonStyle.RobloxButton]
struct = "EnumItem"
[Enum.ButtonStyle.RobloxButtonDefault]
struct = "EnumItem"
[Enum.ButtonStyle.RobloxRoundButton]
struct = "EnumItem"
[Enum.ButtonStyle.RobloxRoundDefaultButton]
struct = "EnumItem"
[Enum.ButtonStyle.RobloxRoundDropdownButton]
struct = "EnumItem"
[Enum.CameraMode.Classic]
struct = "EnumItem"
[Enum.CameraMode.GetEnumItems]
args = []
method = true
[Enum.CameraMode.LockFirstPerson]
struct = "EnumItem"
[Enum.CameraPanMode.Classic]
struct = "EnumItem"
[Enum.CameraPanMode.EdgeBump]
struct = "EnumItem"
[Enum.CameraPanMode.GetEnumItems]
args = []
method = true
[Enum.CameraType.Attach]
struct = "EnumItem"
[Enum.CameraType.Custom]
struct = "EnumItem"
[Enum.CameraType.Fixed]
struct = "EnumItem"
[Enum.CameraType.Follow]
struct = "EnumItem"
[Enum.CameraType.GetEnumItems]
args = []
method = true
[Enum.CameraType.Orbital]
struct = "EnumItem"
[Enum.CameraType.Scriptable]
struct = "EnumItem"
[Enum.CameraType.Track]
struct = "EnumItem"
[Enum.CameraType.Watch]
struct = "EnumItem"
[Enum.CellBlock.CornerWedge]
struct = "EnumItem"
[Enum.CellBlock.GetEnumItems]
args = []
method = true
[Enum.CellBlock.HorizontalWedge]
struct = "EnumItem"
[Enum.CellBlock.InverseCornerWedge]
struct = "EnumItem"
[Enum.CellBlock.Solid]
struct = "EnumItem"
[Enum.CellBlock.VerticalWedge]
struct = "EnumItem"
[Enum.CellMaterial.Aluminum]
struct = "EnumItem"
[Enum.CellMaterial.Asphalt]
struct = "EnumItem"
[Enum.CellMaterial.BluePlastic]
struct = "EnumItem"
[Enum.CellMaterial.Brick]
struct = "EnumItem"
[Enum.CellMaterial.Cement]
struct = "EnumItem"
[Enum.CellMaterial.CinderBlock]
struct = "EnumItem"
[Enum.CellMaterial.Empty]
struct = "EnumItem"
[Enum.CellMaterial.GetEnumItems]
args = []
method = true
[Enum.CellMaterial.Gold]
struct = "EnumItem"
[Enum.CellMaterial.Granite]
struct = "EnumItem"
[Enum.CellMaterial.Grass]
struct = "EnumItem"
[Enum.CellMaterial.Gravel]
struct = "EnumItem"
[Enum.CellMaterial.Iron]
struct = "EnumItem"
[Enum.CellMaterial.MossyStone]
struct = "EnumItem"
[Enum.CellMaterial.RedPlastic]
struct = "EnumItem"
[Enum.CellMaterial.Sand]
struct = "EnumItem"
[Enum.CellMaterial.Water]
struct = "EnumItem"
[Enum.CellMaterial.WoodLog]
struct = "EnumItem"
[Enum.CellMaterial.WoodPlank]
struct = "EnumItem"
[Enum.CellOrientation.GetEnumItems]
args = []
method = true
[Enum.CellOrientation.NegX]
struct = "EnumItem"
[Enum.CellOrientation.NegZ]
struct = "EnumItem"
[Enum.CellOrientation.X]
struct = "EnumItem"
[Enum.CellOrientation.Z]
struct = "EnumItem"
[Enum.CenterDialogType.GetEnumItems]
args = []
method = true
[Enum.CenterDialogType.ModalDialog]
struct = "EnumItem"
[Enum.CenterDialogType.PlayerInitiatedDialog]
struct = "EnumItem"
[Enum.CenterDialogType.QuitDialog]
struct = "EnumItem"
[Enum.CenterDialogType.UnsolicitedDialog]
struct = "EnumItem"
[Enum.ChatCallbackType.GetEnumItems]
args = []
method = true
[Enum.ChatCallbackType.OnClientFormattingMessage]
struct = "EnumItem"
[Enum.ChatCallbackType.OnClientSendingMessage]
struct = "EnumItem"
[Enum.ChatCallbackType.OnCreatingChatWindow]
struct = "EnumItem"
[Enum.ChatCallbackType.OnServerReceivingMessage]
struct = "EnumItem"
[Enum.ChatColor.Blue]
struct = "EnumItem"
[Enum.ChatColor.GetEnumItems]
args = []
method = true
[Enum.ChatColor.Green]
struct = "EnumItem"
[Enum.ChatColor.Red]
struct = "EnumItem"
[Enum.ChatColor.White]
struct = "EnumItem"
[Enum.ChatMode.GetEnumItems]
args = []
method = true
[Enum.ChatMode.Menu]
struct = "EnumItem"
[Enum.ChatMode.TextAndMenu]
struct = "EnumItem"
[Enum.ChatPrivacyMode.AllUsers]
struct = "EnumItem"
[Enum.ChatPrivacyMode.Friends]
struct = "EnumItem"
[Enum.ChatPrivacyMode.GetEnumItems]
args = []
method = true
[Enum.ChatPrivacyMode.NoOne]
struct = "EnumItem"
[Enum.ChatStyle.Bubble]
struct = "EnumItem"
[Enum.ChatStyle.Classic]
struct = "EnumItem"
[Enum.ChatStyle.ClassicAndBubble]
struct = "EnumItem"
[Enum.ChatStyle.GetEnumItems]
args = []
method = true
[Enum.CollisionFidelity.Box]
struct = "EnumItem"
[Enum.CollisionFidelity.Default]
struct = "EnumItem"
[Enum.CollisionFidelity.GetEnumItems]
args = []
method = true
[Enum.CollisionFidelity.Hull]
struct = "EnumItem"
[Enum.CollisionFidelity.PreciseConvexDecomposition]
struct = "EnumItem"
[Enum.ComputerCameraMovementMode.CameraToggle]
struct = "EnumItem"
[Enum.ComputerCameraMovementMode.Classic]
struct = "EnumItem"
[Enum.ComputerCameraMovementMode.Default]
struct = "EnumItem"
[Enum.ComputerCameraMovementMode.Follow]
struct = "EnumItem"
[Enum.ComputerCameraMovementMode.GetEnumItems]
args = []
method = true
[Enum.ComputerCameraMovementMode.Orbital]
struct = "EnumItem"
[Enum.ComputerMovementMode.ClickToMove]
struct = "EnumItem"
[Enum.ComputerMovementMode.Default]
struct = "EnumItem"
[Enum.ComputerMovementMode.GetEnumItems]
args = []
method = true
[Enum.ComputerMovementMode.KeyboardMouse]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectBadhash]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectBlockedIP]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectBySecurityPolicy]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectCloudEditKick]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectConnectionLost]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectDevMaintenance]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectDuplicatePlayer]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectDuplicateTicket]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectErrors]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectEvicted]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectHashTimeout]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectIdle]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectIllegalTeleport]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectLuaKick]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectNewSecurityKeyMismatch]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectOnRemoteSysStats]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectPlayerless]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectProtocolMismatch]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectRaknetErrors]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectReceivePacketError]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectReceivePacketStreamError]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectRejoin]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectRobloxMaintenance]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectSecurityKeyMismatch]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectSendPacketError]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectTimeout]
struct = "EnumItem"
[Enum.ConnectionError.DisconnectWrongVersion]
struct = "EnumItem"
[Enum.ConnectionError.GetEnumItems]
args = []
method = true
[Enum.ConnectionError.OK]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchCustomMessage]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchDisabled]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchError]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchErrors]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchFlooded]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchGameEnded]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchGameFull]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchHashException]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchHashExpired]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchHttpError]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchOtherError]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchPartyCannotFit]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchRestricted]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchUnauthorized]
struct = "EnumItem"
[Enum.ConnectionError.PlacelaunchUserLeft]
struct = "EnumItem"
[Enum.ConnectionError.TeleportErrors]
struct = "EnumItem"
[Enum.ConnectionError.TeleportFailure]
struct = "EnumItem"
[Enum.ConnectionError.TeleportFlooded]
struct = "EnumItem"
[Enum.ConnectionError.TeleportGameEnded]
struct = "EnumItem"
[Enum.ConnectionError.TeleportGameFull]
struct = "EnumItem"
[Enum.ConnectionError.TeleportGameNotFound]
struct = "EnumItem"
[Enum.ConnectionError.TeleportIsTeleporting]
struct = "EnumItem"
[Enum.ConnectionError.TeleportUnauthorized]
struct = "EnumItem"
[Enum.ConnectionState.Connected]
struct = "EnumItem"
[Enum.ConnectionState.Disconnected]
struct = "EnumItem"
[Enum.ConnectionState.GetEnumItems]
args = []
method = true
[Enum.ContextActionPriority.Default]
struct = "EnumItem"
[Enum.ContextActionPriority.GetEnumItems]
args = []
method = true
[Enum.ContextActionPriority.High]
struct = "EnumItem"
[Enum.ContextActionPriority.Low]
struct = "EnumItem"
[Enum.ContextActionPriority.Medium]
struct = "EnumItem"
[Enum.ContextActionResult.GetEnumItems]
args = []
method = true
[Enum.ContextActionResult.Pass]
struct = "EnumItem"
[Enum.ContextActionResult.Sink]
struct = "EnumItem"
[Enum.ControlMode.Classic]
struct = "EnumItem"
[Enum.ControlMode.GetEnumItems]
args = []
method = true
[Enum.ControlMode.MouseLockSwitch]
struct = "EnumItem"
[Enum.CoreGuiType.All]
struct = "EnumItem"
[Enum.CoreGuiType.Backpack]
struct = "EnumItem"
[Enum.CoreGuiType.Chat]
struct = "EnumItem"
[Enum.CoreGuiType.EmotesMenu]
struct = "EnumItem"
[Enum.CoreGuiType.GetEnumItems]
args = []
method = true
[Enum.CoreGuiType.Health]
struct = "EnumItem"
[Enum.CoreGuiType.PlayerList]
struct = "EnumItem"
[Enum.CreatorType.GetEnumItems]
args = []
method = true
[Enum.CreatorType.Group]
struct = "EnumItem"
[Enum.CreatorType.User]
struct = "EnumItem"
[Enum.CurrencyType.Default]
struct = "EnumItem"
[Enum.CurrencyType.GetEnumItems]
args = []
method = true
[Enum.CurrencyType.Robux]
struct = "EnumItem"
[Enum.CurrencyType.Tix]
struct = "EnumItem"
[Enum.CustomCameraMode.Classic]
struct = "EnumItem"
[Enum.CustomCameraMode.Default]
struct = "EnumItem"
[Enum.CustomCameraMode.Follow]
struct = "EnumItem"
[Enum.CustomCameraMode.GetEnumItems]
args = []
method = true
[Enum.DataStoreRequestType.GetAsync]
struct = "EnumItem"
[Enum.DataStoreRequestType.GetEnumItems]
args = []
method = true
[Enum.DataStoreRequestType.GetSortedAsync]
struct = "EnumItem"
[Enum.DataStoreRequestType.OnUpdate]
struct = "EnumItem"
[Enum.DataStoreRequestType.SetIncrementAsync]
struct = "EnumItem"
[Enum.DataStoreRequestType.SetIncrementSortedAsync]
struct = "EnumItem"
[Enum.DataStoreRequestType.UpdateAsync]
struct = "EnumItem"
[Enum.DevCameraOcclusionMode.GetEnumItems]
args = []
method = true
[Enum.DevCameraOcclusionMode.Invisicam]
struct = "EnumItem"
[Enum.DevCameraOcclusionMode.Zoom]
struct = "EnumItem"
[Enum.DevComputerCameraMovementMode.CameraToggle]
struct = "EnumItem"
[Enum.DevComputerCameraMovementMode.Classic]
struct = "EnumItem"
[Enum.DevComputerCameraMovementMode.Follow]
struct = "EnumItem"
[Enum.DevComputerCameraMovementMode.GetEnumItems]
args = []
method = true
[Enum.DevComputerCameraMovementMode.Orbital]
struct = "EnumItem"
[Enum.DevComputerCameraMovementMode.UserChoice]
struct = "EnumItem"
[Enum.DevComputerMovementMode.ClickToMove]
struct = "EnumItem"
[Enum.DevComputerMovementMode.GetEnumItems]
args = []
method = true
[Enum.DevComputerMovementMode.KeyboardMouse]
struct = "EnumItem"
[Enum.DevComputerMovementMode.Scriptable]
struct = "EnumItem"
[Enum.DevComputerMovementMode.UserChoice]
struct = "EnumItem"
[Enum.DevTouchCameraMovementMode.Classic]
struct = "EnumItem"
[Enum.DevTouchCameraMovementMode.Follow]
struct = "EnumItem"
[Enum.DevTouchCameraMovementMode.GetEnumItems]
args = []
method = true
[Enum.DevTouchCameraMovementMode.Orbital]
struct = "EnumItem"
[Enum.DevTouchCameraMovementMode.UserChoice]
struct = "EnumItem"
[Enum.DevTouchMovementMode.ClickToMove]
struct = "EnumItem"
[Enum.DevTouchMovementMode.DPad]
struct = "EnumItem"
[Enum.DevTouchMovementMode.DynamicThumbstick]
struct = "EnumItem"
[Enum.DevTouchMovementMode.GetEnumItems]
args = []
method = true
[Enum.DevTouchMovementMode.Scriptable]
struct = "EnumItem"
[Enum.DevTouchMovementMode.Thumbpad]
struct = "EnumItem"
[Enum.DevTouchMovementMode.Thumbstick]
struct = "EnumItem"
[Enum.DevTouchMovementMode.UserChoice]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.Animation]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.GetEnumItems]
args = []
method = true
[Enum.DeveloperMemoryTag.GraphicsMeshParts]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.GraphicsParticles]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.GraphicsParts]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.GraphicsSolidModels]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.GraphicsSpatialHash]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.GraphicsTerrain]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.GraphicsTexture]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.GraphicsTextureCharacter]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.Gui]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.HttpCache]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.Instances]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.Internal]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.LuaHeap]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.Navigation]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.PhysicsCollision]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.PhysicsParts]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.Script]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.Signals]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.Sounds]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.StreamingSounds]
struct = "EnumItem"
[Enum.DeveloperMemoryTag.TerrainVoxels]
struct = "EnumItem"
[Enum.DeviceType.Desktop]
struct = "EnumItem"
[Enum.DeviceType.GetEnumItems]
args = []
method = true
[Enum.DeviceType.Phone]
struct = "EnumItem"
[Enum.DeviceType.Tablet]
struct = "EnumItem"
[Enum.DeviceType.Unknown]
struct = "EnumItem"
[Enum.DialogBehaviorType.GetEnumItems]
args = []
method = true
[Enum.DialogBehaviorType.MultiplePlayers]
struct = "EnumItem"
[Enum.DialogBehaviorType.SinglePlayer]
struct = "EnumItem"
[Enum.DialogPurpose.GetEnumItems]
args = []
method = true
[Enum.DialogPurpose.Help]
struct = "EnumItem"
[Enum.DialogPurpose.Quest]
struct = "EnumItem"
[Enum.DialogPurpose.Shop]
struct = "EnumItem"
[Enum.DialogTone.Enemy]
struct = "EnumItem"
[Enum.DialogTone.Friendly]
struct = "EnumItem"
[Enum.DialogTone.GetEnumItems]
args = []
method = true
[Enum.DialogTone.Neutral]
struct = "EnumItem"
[Enum.DominantAxis.GetEnumItems]
args = []
method = true
[Enum.DominantAxis.Height]
struct = "EnumItem"
[Enum.DominantAxis.Width]
struct = "EnumItem"
[Enum.DraftStatusCode.DraftCommitted]
struct = "EnumItem"
[Enum.DraftStatusCode.DraftOutdated]
struct = "EnumItem"
[Enum.DraftStatusCode.GetEnumItems]
args = []
method = true
[Enum.DraftStatusCode.OK]
struct = "EnumItem"
[Enum.DraftStatusCode.ScriptRemoved]
struct = "EnumItem"
[Enum.EasingDirection.GetEnumItems]
args = []
method = true
[Enum.EasingDirection.In]
struct = "EnumItem"
[Enum.EasingDirection.InOut]
struct = "EnumItem"
[Enum.EasingDirection.Out]
struct = "EnumItem"
[Enum.EasingStyle.Back]
struct = "EnumItem"
[Enum.EasingStyle.Bounce]
struct = "EnumItem"
[Enum.EasingStyle.Circular]
struct = "EnumItem"
[Enum.EasingStyle.Cubic]
struct = "EnumItem"
[Enum.EasingStyle.Elastic]
struct = "EnumItem"
[Enum.EasingStyle.Exponential]
struct = "EnumItem"
[Enum.EasingStyle.GetEnumItems]
args = []
method = true
[Enum.EasingStyle.Linear]
struct = "EnumItem"
[Enum.EasingStyle.Quad]
struct = "EnumItem"
[Enum.EasingStyle.Quart]
struct = "EnumItem"
[Enum.EasingStyle.Quint]
struct = "EnumItem"
[Enum.EasingStyle.Sine]
struct = "EnumItem"
[Enum.ElasticBehavior.Always]
struct = "EnumItem"
[Enum.ElasticBehavior.GetEnumItems]
args = []
method = true
[Enum.ElasticBehavior.Never]
struct = "EnumItem"
[Enum.ElasticBehavior.WhenScrollable]
struct = "EnumItem"
[Enum.EnviromentalPhysicsThrottle.Always]
struct = "EnumItem"
[Enum.EnviromentalPhysicsThrottle.DefaultAuto]
struct = "EnumItem"
[Enum.EnviromentalPhysicsThrottle.Disabled]
struct = "EnumItem"
[Enum.EnviromentalPhysicsThrottle.GetEnumItems]
args = []
method = true
[Enum.EnviromentalPhysicsThrottle.Skip16]
struct = "EnumItem"
[Enum.EnviromentalPhysicsThrottle.Skip2]
struct = "EnumItem"
[Enum.EnviromentalPhysicsThrottle.Skip4]
struct = "EnumItem"
[Enum.EnviromentalPhysicsThrottle.Skip8]
struct = "EnumItem"
[Enum.ExplosionType.Craters]
struct = "EnumItem"
[Enum.ExplosionType.GetEnumItems]
args = []
method = true
[Enum.ExplosionType.NoCraters]
struct = "EnumItem"
[Enum.FillDirection.GetEnumItems]
args = []
method = true
[Enum.FillDirection.Horizontal]
struct = "EnumItem"
[Enum.FillDirection.Vertical]
struct = "EnumItem"
[Enum.FilterResult.Accepted]
struct = "EnumItem"
[Enum.FilterResult.GetEnumItems]
args = []
method = true
[Enum.FilterResult.Rejected]
struct = "EnumItem"
[Enum.Font.Antique]
struct = "EnumItem"
[Enum.Font.Arcade]
struct = "EnumItem"
[Enum.Font.Arial]
struct = "EnumItem"
[Enum.Font.ArialBold]
struct = "EnumItem"
[Enum.Font.Bodoni]
struct = "EnumItem"
[Enum.Font.Cartoon]
struct = "EnumItem"
[Enum.Font.Code]
struct = "EnumItem"
[Enum.Font.Fantasy]
struct = "EnumItem"
[Enum.Font.Garamond]
struct = "EnumItem"
[Enum.Font.GetEnumItems]
args = []
method = true
[Enum.Font.Gotham]
struct = "EnumItem"
[Enum.Font.GothamBlack]
struct = "EnumItem"
[Enum.Font.GothamBold]
struct = "EnumItem"
[Enum.Font.GothamSemibold]
struct = "EnumItem"
[Enum.Font.Highway]
struct = "EnumItem"
[Enum.Font.Legacy]
struct = "EnumItem"
[Enum.Font.SciFi]
struct = "EnumItem"
[Enum.Font.SourceSans]
struct = "EnumItem"
[Enum.Font.SourceSansBold]
struct = "EnumItem"
[Enum.Font.SourceSansItalic]
struct = "EnumItem"
[Enum.Font.SourceSansLight]
struct = "EnumItem"
[Enum.Font.SourceSansSemibold]
struct = "EnumItem"
[Enum.FontSize.GetEnumItems]
args = []
method = true
[Enum.FontSize.Size10]
struct = "EnumItem"
[Enum.FontSize.Size11]
struct = "EnumItem"
[Enum.FontSize.Size12]
struct = "EnumItem"
[Enum.FontSize.Size14]
struct = "EnumItem"
[Enum.FontSize.Size18]
struct = "EnumItem"
[Enum.FontSize.Size24]
struct = "EnumItem"
[Enum.FontSize.Size28]
struct = "EnumItem"
[Enum.FontSize.Size32]
struct = "EnumItem"
[Enum.FontSize.Size36]
struct = "EnumItem"
[Enum.FontSize.Size42]
struct = "EnumItem"
[Enum.FontSize.Size48]
struct = "EnumItem"
[Enum.FontSize.Size60]
struct = "EnumItem"
[Enum.FontSize.Size8]
struct = "EnumItem"
[Enum.FontSize.Size9]
struct = "EnumItem"
[Enum.FontSize.Size96]
struct = "EnumItem"
[Enum.FormFactor.Brick]
struct = "EnumItem"
[Enum.FormFactor.Custom]
struct = "EnumItem"
[Enum.FormFactor.GetEnumItems]
args = []
method = true
[Enum.FormFactor.Plate]
struct = "EnumItem"
[Enum.FormFactor.Symmetric]
struct = "EnumItem"
[Enum.FrameStyle.ChatBlue]
struct = "EnumItem"
[Enum.FrameStyle.ChatGreen]
struct = "EnumItem"
[Enum.FrameStyle.ChatRed]
struct = "EnumItem"
[Enum.FrameStyle.Custom]
struct = "EnumItem"
[Enum.FrameStyle.DropShadow]
struct = "EnumItem"
[Enum.FrameStyle.GetEnumItems]
args = []
method = true
[Enum.FrameStyle.RobloxRound]
struct = "EnumItem"
[Enum.FrameStyle.RobloxSquare]
struct = "EnumItem"
[Enum.FramerateManagerMode.Automatic]
struct = "EnumItem"
[Enum.FramerateManagerMode.GetEnumItems]
args = []
method = true
[Enum.FramerateManagerMode.Off]
struct = "EnumItem"
[Enum.FramerateManagerMode.On]
struct = "EnumItem"
[Enum.FriendRequestEvent.Accept]
struct = "EnumItem"
[Enum.FriendRequestEvent.Deny]
struct = "EnumItem"
[Enum.FriendRequestEvent.GetEnumItems]
args = []
method = true
[Enum.FriendRequestEvent.Issue]
struct = "EnumItem"
[Enum.FriendRequestEvent.Revoke]
struct = "EnumItem"
[Enum.FriendStatus.Friend]
struct = "EnumItem"
[Enum.FriendStatus.FriendRequestReceived]
struct = "EnumItem"
[Enum.FriendStatus.FriendRequestSent]
struct = "EnumItem"
[Enum.FriendStatus.GetEnumItems]
args = []
method = true
[Enum.FriendStatus.NotFriend]
struct = "EnumItem"
[Enum.FriendStatus.Unknown]
struct = "EnumItem"
[Enum.FunctionalTestResult.Error]
struct = "EnumItem"
[Enum.FunctionalTestResult.GetEnumItems]
args = []
method = true
[Enum.FunctionalTestResult.Passed]
struct = "EnumItem"
[Enum.FunctionalTestResult.Warning]
struct = "EnumItem"
[Enum.GameAvatarType.GetEnumItems]
args = []
method = true
[Enum.GameAvatarType.PlayerChoice]
struct = "EnumItem"
[Enum.GameAvatarType.R15]
struct = "EnumItem"
[Enum.GameAvatarType.R6]
struct = "EnumItem"
[Enum.GearGenreSetting.AllGenres]
struct = "EnumItem"
[Enum.GearGenreSetting.GetEnumItems]
args = []
method = true
[Enum.GearGenreSetting.MatchingGenreOnly]
struct = "EnumItem"
[Enum.GearType.BuildingTools]
struct = "EnumItem"
[Enum.GearType.Explosives]
struct = "EnumItem"
[Enum.GearType.GetEnumItems]
args = []
method = true
[Enum.GearType.MeleeWeapons]
struct = "EnumItem"
[Enum.GearType.MusicalInstruments]
struct = "EnumItem"
[Enum.GearType.NavigationEnhancers]
struct = "EnumItem"
[Enum.GearType.PowerUps]
struct = "EnumItem"
[Enum.GearType.RangedWeapons]
struct = "EnumItem"
[Enum.GearType.SocialItems]
struct = "EnumItem"
[Enum.GearType.Transport]
struct = "EnumItem"
[Enum.Genre.Adventure]
struct = "EnumItem"
[Enum.Genre.All]
struct = "EnumItem"
[Enum.Genre.Fantasy]
struct = "EnumItem"
[Enum.Genre.Funny]
struct = "EnumItem"
[Enum.Genre.GetEnumItems]
args = []
method = true
[Enum.Genre.Ninja]
struct = "EnumItem"
[Enum.Genre.Pirate]
struct = "EnumItem"
[Enum.Genre.Scary]
struct = "EnumItem"
[Enum.Genre.SciFi]
struct = "EnumItem"
[Enum.Genre.SkatePark]
struct = "EnumItem"
[Enum.Genre.Sports]
struct = "EnumItem"
[Enum.Genre.TownAndCity]
struct = "EnumItem"
[Enum.Genre.Tutorial]
struct = "EnumItem"
[Enum.Genre.War]
struct = "EnumItem"
[Enum.Genre.WildWest]
struct = "EnumItem"
[Enum.GraphicsMode.Automatic]
struct = "EnumItem"
[Enum.GraphicsMode.Direct3D11]
struct = "EnumItem"
[Enum.GraphicsMode.Direct3D9]
struct = "EnumItem"
[Enum.GraphicsMode.GetEnumItems]
args = []
method = true
[Enum.GraphicsMode.Metal]
struct = "EnumItem"
[Enum.GraphicsMode.NoGraphics]
struct = "EnumItem"
[Enum.GraphicsMode.OpenGL]
struct = "EnumItem"
[Enum.GraphicsMode.Vulkan]
struct = "EnumItem"
[Enum.HandlesStyle.GetEnumItems]
args = []
method = true
[Enum.HandlesStyle.Movement]
struct = "EnumItem"
[Enum.HandlesStyle.Resize]
struct = "EnumItem"
[Enum.HorizontalAlignment.Center]
struct = "EnumItem"
[Enum.HorizontalAlignment.GetEnumItems]
args = []
method = true
[Enum.HorizontalAlignment.Left]
struct = "EnumItem"
[Enum.HorizontalAlignment.Right]
struct = "EnumItem"
[Enum.HoverAnimateSpeed.Fast]
struct = "EnumItem"
[Enum.HoverAnimateSpeed.GetEnumItems]
args = []
method = true
[Enum.HoverAnimateSpeed.Medium]
struct = "EnumItem"
[Enum.HoverAnimateSpeed.Slow]
struct = "EnumItem"
[Enum.HoverAnimateSpeed.VeryFast]
struct = "EnumItem"
[Enum.HoverAnimateSpeed.VerySlow]
struct = "EnumItem"
[Enum.HttpCachePolicy.DataOnly]
struct = "EnumItem"
[Enum.HttpCachePolicy.Default]
struct = "EnumItem"
[Enum.HttpCachePolicy.Full]
struct = "EnumItem"
[Enum.HttpCachePolicy.GetEnumItems]
args = []
method = true
[Enum.HttpCachePolicy.InternalRedirectRefresh]
struct = "EnumItem"
[Enum.HttpCachePolicy.None]
struct = "EnumItem"
[Enum.HttpContentType.ApplicationJson]
struct = "EnumItem"
[Enum.HttpContentType.ApplicationUrlEncoded]
struct = "EnumItem"
[Enum.HttpContentType.ApplicationXml]
struct = "EnumItem"
[Enum.HttpContentType.GetEnumItems]
args = []
method = true
[Enum.HttpContentType.TextPlain]
struct = "EnumItem"
[Enum.HttpContentType.TextXml]
struct = "EnumItem"
[Enum.HttpError.Aborted]
struct = "EnumItem"
[Enum.HttpError.ConnectFail]
struct = "EnumItem"
[Enum.HttpError.DnsResolve]
struct = "EnumItem"
[Enum.HttpError.GetEnumItems]
args = []
method = true
[Enum.HttpError.InvalidRedirect]
struct = "EnumItem"
[Enum.HttpError.InvalidUrl]
struct = "EnumItem"
[Enum.HttpError.NetFail]
struct = "EnumItem"
[Enum.HttpError.OK]
struct = "EnumItem"
[Enum.HttpError.OutOfMemory]
struct = "EnumItem"
[Enum.HttpError.SslConnectFail]
struct = "EnumItem"
[Enum.HttpError.SslVerificationFail]
struct = "EnumItem"
[Enum.HttpError.TimedOut]
struct = "EnumItem"
[Enum.HttpError.TooManyRedirects]
struct = "EnumItem"
[Enum.HttpError.Unknown]
struct = "EnumItem"
[Enum.HttpRequestType.Analytics]
struct = "EnumItem"
[Enum.HttpRequestType.Avatar]
struct = "EnumItem"
[Enum.HttpRequestType.Chat]
struct = "EnumItem"
[Enum.HttpRequestType.Default]
struct = "EnumItem"
[Enum.HttpRequestType.GetEnumItems]
args = []
method = true
[Enum.HttpRequestType.Localization]
struct = "EnumItem"
[Enum.HttpRequestType.MarketplaceService]
struct = "EnumItem"
[Enum.HttpRequestType.Players]
struct = "EnumItem"
[Enum.HumanoidCollisionType.GetEnumItems]
args = []
method = true
[Enum.HumanoidCollisionType.InnerBox]
struct = "EnumItem"
[Enum.HumanoidCollisionType.OuterBox]
struct = "EnumItem"
[Enum.HumanoidDisplayDistanceType.GetEnumItems]
args = []
method = true
[Enum.HumanoidDisplayDistanceType.None]
struct = "EnumItem"
[Enum.HumanoidDisplayDistanceType.Subject]
struct = "EnumItem"
[Enum.HumanoidDisplayDistanceType.Viewer]
struct = "EnumItem"
[Enum.HumanoidHealthDisplayType.AlwaysOff]
struct = "EnumItem"
[Enum.HumanoidHealthDisplayType.AlwaysOn]
struct = "EnumItem"
[Enum.HumanoidHealthDisplayType.DisplayWhenDamaged]
struct = "EnumItem"
[Enum.HumanoidHealthDisplayType.GetEnumItems]
args = []
method = true
[Enum.HumanoidRigType.GetEnumItems]
args = []
method = true
[Enum.HumanoidRigType.R15]
struct = "EnumItem"
[Enum.HumanoidRigType.R6]
struct = "EnumItem"
[Enum.HumanoidStateType.Climbing]
struct = "EnumItem"
[Enum.HumanoidStateType.Dead]
struct = "EnumItem"
[Enum.HumanoidStateType.FallingDown]
struct = "EnumItem"
[Enum.HumanoidStateType.Flying]
struct = "EnumItem"
[Enum.HumanoidStateType.Freefall]
struct = "EnumItem"
[Enum.HumanoidStateType.GetEnumItems]
args = []
method = true
[Enum.HumanoidStateType.GettingUp]
struct = "EnumItem"
[Enum.HumanoidStateType.Jumping]
struct = "EnumItem"
[Enum.HumanoidStateType.Landed]
struct = "EnumItem"
[Enum.HumanoidStateType.None]
struct = "EnumItem"
[Enum.HumanoidStateType.Physics]
struct = "EnumItem"
[Enum.HumanoidStateType.PlatformStanding]
struct = "EnumItem"
[Enum.HumanoidStateType.Ragdoll]
struct = "EnumItem"
[Enum.HumanoidStateType.Running]
struct = "EnumItem"
[Enum.HumanoidStateType.RunningNoPhysics]
struct = "EnumItem"
[Enum.HumanoidStateType.Seated]
struct = "EnumItem"
[Enum.HumanoidStateType.StrafingNoPhysics]
struct = "EnumItem"
[Enum.HumanoidStateType.Swimming]
struct = "EnumItem"
[Enum.IKCollisionsMode.GetEnumItems]
args = []
method = true
[Enum.IKCollisionsMode.IncludeContactedMechanisms]
struct = "EnumItem"
[Enum.IKCollisionsMode.NoCollisions]
struct = "EnumItem"
[Enum.IKCollisionsMode.OtherMechanismsAnchored]
struct = "EnumItem"
[Enum.InOut.Center]
struct = "EnumItem"
[Enum.InOut.Edge]
struct = "EnumItem"
[Enum.InOut.GetEnumItems]
args = []
method = true
[Enum.InOut.Inset]
struct = "EnumItem"
[Enum.InfoType.Asset]
struct = "EnumItem"
[Enum.InfoType.Bundle]
struct = "EnumItem"
[Enum.InfoType.GamePass]
struct = "EnumItem"
[Enum.InfoType.GetEnumItems]
args = []
method = true
[Enum.InfoType.Product]
struct = "EnumItem"
[Enum.InfoType.Subscription]
struct = "EnumItem"
[Enum.InitialDockState.Bottom]
struct = "EnumItem"
[Enum.InitialDockState.Float]
struct = "EnumItem"
[Enum.InitialDockState.GetEnumItems]
args = []
method = true
[Enum.InitialDockState.Left]
struct = "EnumItem"
[Enum.InitialDockState.Right]
struct = "EnumItem"
[Enum.InitialDockState.Top]
struct = "EnumItem"
[Enum.InlineAlignment.Bottom]
struct = "EnumItem"
[Enum.InlineAlignment.Center]
struct = "EnumItem"
[Enum.InlineAlignment.GetEnumItems]
args = []
method = true
[Enum.InlineAlignment.Top]
struct = "EnumItem"
[Enum.InputType.Constant]
struct = "EnumItem"
[Enum.InputType.GetEnumItems]
args = []
method = true
[Enum.InputType.NoInput]
struct = "EnumItem"
[Enum.InputType.Sin]
struct = "EnumItem"
[Enum.JointCreationMode.All]
struct = "EnumItem"
[Enum.JointCreationMode.GetEnumItems]
args = []
method = true
[Enum.JointCreationMode.None]
struct = "EnumItem"
[Enum.JointCreationMode.Surface]
struct = "EnumItem"
[Enum.KeyCode.A]
struct = "EnumItem"
[Enum.KeyCode.Ampersand]
struct = "EnumItem"
[Enum.KeyCode.Asterisk]
struct = "EnumItem"
[Enum.KeyCode.At]
struct = "EnumItem"
[Enum.KeyCode.B]
struct = "EnumItem"
[Enum.KeyCode.BackSlash]
struct = "EnumItem"
[Enum.KeyCode.Backquote]
struct = "EnumItem"
[Enum.KeyCode.Backspace]
struct = "EnumItem"
[Enum.KeyCode.Break]
struct = "EnumItem"
[Enum.KeyCode.ButtonA]
struct = "EnumItem"
[Enum.KeyCode.ButtonB]
struct = "EnumItem"
[Enum.KeyCode.ButtonL1]
struct = "EnumItem"
[Enum.KeyCode.ButtonL2]
struct = "EnumItem"
[Enum.KeyCode.ButtonL3]
struct = "EnumItem"
[Enum.KeyCode.ButtonR1]
struct = "EnumItem"
[Enum.KeyCode.ButtonR2]
struct = "EnumItem"
[Enum.KeyCode.ButtonR3]
struct = "EnumItem"
[Enum.KeyCode.ButtonSelect]
struct = "EnumItem"
[Enum.KeyCode.ButtonStart]
struct = "EnumItem"
[Enum.KeyCode.ButtonX]
struct = "EnumItem"
[Enum.KeyCode.ButtonY]
struct = "EnumItem"
[Enum.KeyCode.C]
struct = "EnumItem"
[Enum.KeyCode.CapsLock]
struct = "EnumItem"
[Enum.KeyCode.Caret]
struct = "EnumItem"
[Enum.KeyCode.Clear]
struct = "EnumItem"
[Enum.KeyCode.Colon]
struct = "EnumItem"
[Enum.KeyCode.Comma]
struct = "EnumItem"
[Enum.KeyCode.Compose]
struct = "EnumItem"
[Enum.KeyCode.D]
struct = "EnumItem"
[Enum.KeyCode.DPadDown]
struct = "EnumItem"
[Enum.KeyCode.DPadLeft]
struct = "EnumItem"
[Enum.KeyCode.DPadRight]
struct = "EnumItem"
[Enum.KeyCode.DPadUp]
struct = "EnumItem"
[Enum.KeyCode.Delete]
struct = "EnumItem"
[Enum.KeyCode.Dollar]
struct = "EnumItem"
[Enum.KeyCode.Down]
struct = "EnumItem"
[Enum.KeyCode.E]
struct = "EnumItem"
[Enum.KeyCode.Eight]
struct = "EnumItem"
[Enum.KeyCode.End]
struct = "EnumItem"
[Enum.KeyCode.Equals]
struct = "EnumItem"
[Enum.KeyCode.Escape]
struct = "EnumItem"
[Enum.KeyCode.Euro]
struct = "EnumItem"
[Enum.KeyCode.F]
struct = "EnumItem"
[Enum.KeyCode.F1]
struct = "EnumItem"
[Enum.KeyCode.F10]
struct = "EnumItem"
[Enum.KeyCode.F11]
struct = "EnumItem"
[Enum.KeyCode.F12]
struct = "EnumItem"
[Enum.KeyCode.F13]
struct = "EnumItem"
[Enum.KeyCode.F14]
struct = "EnumItem"
[Enum.KeyCode.F15]
struct = "EnumItem"
[Enum.KeyCode.F2]
struct = "EnumItem"
[Enum.KeyCode.F3]
struct = "EnumItem"
[Enum.KeyCode.F4]
struct = "EnumItem"
[Enum.KeyCode.F5]
struct = "EnumItem"
[Enum.KeyCode.F6]
struct = "EnumItem"
[Enum.KeyCode.F7]
struct = "EnumItem"
[Enum.KeyCode.F8]
struct = "EnumItem"
[Enum.KeyCode.F9]
struct = "EnumItem"
[Enum.KeyCode.Five]
struct = "EnumItem"
[Enum.KeyCode.Four]
struct = "EnumItem"
[Enum.KeyCode.G]
struct = "EnumItem"
[Enum.KeyCode.GetEnumItems]
args = []
method = true
[Enum.KeyCode.GreaterThan]
struct = "EnumItem"
[Enum.KeyCode.H]
struct = "EnumItem"
[Enum.KeyCode.Hash]
struct = "EnumItem"
[Enum.KeyCode.Help]
struct = "EnumItem"
[Enum.KeyCode.Home]
struct = "EnumItem"
[Enum.KeyCode.I]
struct = "EnumItem"
[Enum.KeyCode.Insert]
struct = "EnumItem"
[Enum.KeyCode.J]
struct = "EnumItem"
[Enum.KeyCode.K]
struct = "EnumItem"
[Enum.KeyCode.KeypadDivide]
struct = "EnumItem"
[Enum.KeyCode.KeypadEight]
struct = "EnumItem"
[Enum.KeyCode.KeypadEnter]
struct = "EnumItem"
[Enum.KeyCode.KeypadEquals]
struct = "EnumItem"
[Enum.KeyCode.KeypadFive]
struct = "EnumItem"
[Enum.KeyCode.KeypadFour]
struct = "EnumItem"
[Enum.KeyCode.KeypadMinus]
struct = "EnumItem"
[Enum.KeyCode.KeypadMultiply]
struct = "EnumItem"
[Enum.KeyCode.KeypadNine]
struct = "EnumItem"
[Enum.KeyCode.KeypadOne]
struct = "EnumItem"
[Enum.KeyCode.KeypadPeriod]
struct = "EnumItem"
[Enum.KeyCode.KeypadPlus]
struct = "EnumItem"
[Enum.KeyCode.KeypadSeven]
struct = "EnumItem"
[Enum.KeyCode.KeypadSix]
struct = "EnumItem"
[Enum.KeyCode.KeypadThree]
struct = "EnumItem"
[Enum.KeyCode.KeypadTwo]
struct = "EnumItem"
[Enum.KeyCode.KeypadZero]
struct = "EnumItem"
[Enum.KeyCode.L]
struct = "EnumItem"
[Enum.KeyCode.Left]
struct = "EnumItem"
[Enum.KeyCode.LeftAlt]
struct = "EnumItem"
[Enum.KeyCode.LeftBracket]
struct = "EnumItem"
[Enum.KeyCode.LeftControl]
struct = "EnumItem"
[Enum.KeyCode.LeftCurly]
struct = "EnumItem"
[Enum.KeyCode.LeftMeta]
struct = "EnumItem"
[Enum.KeyCode.LeftParenthesis]
struct = "EnumItem"
[Enum.KeyCode.LeftShift]
struct = "EnumItem"
[Enum.KeyCode.LeftSuper]
struct = "EnumItem"
[Enum.KeyCode.LessThan]
struct = "EnumItem"
[Enum.KeyCode.M]
struct = "EnumItem"
[Enum.KeyCode.Menu]
struct = "EnumItem"
[Enum.KeyCode.Minus]
struct = "EnumItem"
[Enum.KeyCode.Mode]
struct = "EnumItem"
[Enum.KeyCode.N]
struct = "EnumItem"
[Enum.KeyCode.Nine]
struct = "EnumItem"
[Enum.KeyCode.NumLock]
struct = "EnumItem"
[Enum.KeyCode.O]
struct = "EnumItem"
[Enum.KeyCode.One]
struct = "EnumItem"
[Enum.KeyCode.P]
struct = "EnumItem"
[Enum.KeyCode.PageDown]
struct = "EnumItem"
[Enum.KeyCode.PageUp]
struct = "EnumItem"
[Enum.KeyCode.Pause]
struct = "EnumItem"
[Enum.KeyCode.Percent]
struct = "EnumItem"
[Enum.KeyCode.Period]
struct = "EnumItem"
[Enum.KeyCode.Pipe]
struct = "EnumItem"
[Enum.KeyCode.Plus]
struct = "EnumItem"
[Enum.KeyCode.Power]
struct = "EnumItem"
[Enum.KeyCode.Print]
struct = "EnumItem"
[Enum.KeyCode.Q]
struct = "EnumItem"
[Enum.KeyCode.Question]
struct = "EnumItem"
[Enum.KeyCode.Quote]
struct = "EnumItem"
[Enum.KeyCode.QuotedDouble]
struct = "EnumItem"
[Enum.KeyCode.R]
struct = "EnumItem"
[Enum.KeyCode.Return]
struct = "EnumItem"
[Enum.KeyCode.Right]
struct = "EnumItem"
[Enum.KeyCode.RightAlt]
struct = "EnumItem"
[Enum.KeyCode.RightBracket]
struct = "EnumItem"
[Enum.KeyCode.RightControl]
struct = "EnumItem"
[Enum.KeyCode.RightCurly]
struct = "EnumItem"
[Enum.KeyCode.RightMeta]
struct = "EnumItem"
[Enum.KeyCode.RightParenthesis]
struct = "EnumItem"
[Enum.KeyCode.RightShift]
struct = "EnumItem"
[Enum.KeyCode.RightSuper]
struct = "EnumItem"
[Enum.KeyCode.S]
struct = "EnumItem"
[Enum.KeyCode.ScrollLock]
struct = "EnumItem"
[Enum.KeyCode.Semicolon]
struct = "EnumItem"
[Enum.KeyCode.Seven]
struct = "EnumItem"
[Enum.KeyCode.Six]
struct = "EnumItem"
[Enum.KeyCode.Slash]
struct = "EnumItem"
[Enum.KeyCode.Space]
struct = "EnumItem"
[Enum.KeyCode.SysReq]
struct = "EnumItem"
[Enum.KeyCode.T]
struct = "EnumItem"
[Enum.KeyCode.Tab]
struct = "EnumItem"
[Enum.KeyCode.Three]
struct = "EnumItem"
[Enum.KeyCode.Thumbstick1]
struct = "EnumItem"
[Enum.KeyCode.Thumbstick2]
struct = "EnumItem"
[Enum.KeyCode.Tilde]
struct = "EnumItem"
[Enum.KeyCode.Two]
struct = "EnumItem"
[Enum.KeyCode.U]
struct = "EnumItem"
[Enum.KeyCode.Underscore]
struct = "EnumItem"
[Enum.KeyCode.Undo]
struct = "EnumItem"
[Enum.KeyCode.Unknown]
struct = "EnumItem"
[Enum.KeyCode.Up]
struct = "EnumItem"
[Enum.KeyCode.V]
struct = "EnumItem"
[Enum.KeyCode.W]
struct = "EnumItem"
[Enum.KeyCode.World0]
struct = "EnumItem"
[Enum.KeyCode.World1]
struct = "EnumItem"
[Enum.KeyCode.World10]
struct = "EnumItem"
[Enum.KeyCode.World11]
struct = "EnumItem"
[Enum.KeyCode.World12]
struct = "EnumItem"
[Enum.KeyCode.World13]
struct = "EnumItem"
[Enum.KeyCode.World14]
struct = "EnumItem"
[Enum.KeyCode.World15]
struct = "EnumItem"
[Enum.KeyCode.World16]
struct = "EnumItem"
[Enum.KeyCode.World17]
struct = "EnumItem"
[Enum.KeyCode.World18]
struct = "EnumItem"
[Enum.KeyCode.World19]
struct = "EnumItem"
[Enum.KeyCode.World2]
struct = "EnumItem"
[Enum.KeyCode.World20]
struct = "EnumItem"
[Enum.KeyCode.World21]
struct = "EnumItem"
[Enum.KeyCode.World22]
struct = "EnumItem"
[Enum.KeyCode.World23]
struct = "EnumItem"
[Enum.KeyCode.World24]
struct = "EnumItem"
[Enum.KeyCode.World25]
struct = "EnumItem"
[Enum.KeyCode.World26]
struct = "EnumItem"
[Enum.KeyCode.World27]
struct = "EnumItem"
[Enum.KeyCode.World28]
struct = "EnumItem"
[Enum.KeyCode.World29]
struct = "EnumItem"
[Enum.KeyCode.World3]
struct = "EnumItem"
[Enum.KeyCode.World30]
struct = "EnumItem"
[Enum.KeyCode.World31]
struct = "EnumItem"
[Enum.KeyCode.World32]
struct = "EnumItem"
[Enum.KeyCode.World33]
struct = "EnumItem"
[Enum.KeyCode.World34]
struct = "EnumItem"
[Enum.KeyCode.World35]
struct = "EnumItem"
[Enum.KeyCode.World36]
struct = "EnumItem"
[Enum.KeyCode.World37]
struct = "EnumItem"
[Enum.KeyCode.World38]
struct = "EnumItem"
[Enum.KeyCode.World39]
struct = "EnumItem"
[Enum.KeyCode.World4]
struct = "EnumItem"
[Enum.KeyCode.World40]
struct = "EnumItem"
[Enum.KeyCode.World41]
struct = "EnumItem"
[Enum.KeyCode.World42]
struct = "EnumItem"
[Enum.KeyCode.World43]
struct = "EnumItem"
[Enum.KeyCode.World44]
struct = "EnumItem"
[Enum.KeyCode.World45]
struct = "EnumItem"
[Enum.KeyCode.World46]
struct = "EnumItem"
[Enum.KeyCode.World47]
struct = "EnumItem"
[Enum.KeyCode.World48]
struct = "EnumItem"
[Enum.KeyCode.World49]
struct = "EnumItem"
[Enum.KeyCode.World5]
struct = "EnumItem"
[Enum.KeyCode.World50]
struct = "EnumItem"
[Enum.KeyCode.World51]
struct = "EnumItem"
[Enum.KeyCode.World52]
struct = "EnumItem"
[Enum.KeyCode.World53]
struct = "EnumItem"
[Enum.KeyCode.World54]
struct = "EnumItem"
[Enum.KeyCode.World55]
struct = "EnumItem"
[Enum.KeyCode.World56]
struct = "EnumItem"
[Enum.KeyCode.World57]
struct = "EnumItem"
[Enum.KeyCode.World58]
struct = "EnumItem"
[Enum.KeyCode.World59]
struct = "EnumItem"
[Enum.KeyCode.World6]
struct = "EnumItem"
[Enum.KeyCode.World60]
struct = "EnumItem"
[Enum.KeyCode.World61]
struct = "EnumItem"
[Enum.KeyCode.World62]
struct = "EnumItem"
[Enum.KeyCode.World63]
struct = "EnumItem"
[Enum.KeyCode.World64]
struct = "EnumItem"
[Enum.KeyCode.World65]
struct = "EnumItem"
[Enum.KeyCode.World66]
struct = "EnumItem"
[Enum.KeyCode.World67]
struct = "EnumItem"
[Enum.KeyCode.World68]
struct = "EnumItem"
[Enum.KeyCode.World69]
struct = "EnumItem"
[Enum.KeyCode.World7]
struct = "EnumItem"
[Enum.KeyCode.World70]
struct = "EnumItem"
[Enum.KeyCode.World71]
struct = "EnumItem"
[Enum.KeyCode.World72]
struct = "EnumItem"
[Enum.KeyCode.World73]
struct = "EnumItem"
[Enum.KeyCode.World74]
struct = "EnumItem"
[Enum.KeyCode.World75]
struct = "EnumItem"
[Enum.KeyCode.World76]
struct = "EnumItem"
[Enum.KeyCode.World77]
struct = "EnumItem"
[Enum.KeyCode.World78]
struct = "EnumItem"
[Enum.KeyCode.World79]
struct = "EnumItem"
[Enum.KeyCode.World8]
struct = "EnumItem"
[Enum.KeyCode.World80]
struct = "EnumItem"
[Enum.KeyCode.World81]
struct = "EnumItem"
[Enum.KeyCode.World82]
struct = "EnumItem"
[Enum.KeyCode.World83]
struct = "EnumItem"
[Enum.KeyCode.World84]
struct = "EnumItem"
[Enum.KeyCode.World85]
struct = "EnumItem"
[Enum.KeyCode.World86]
struct = "EnumItem"
[Enum.KeyCode.World87]
struct = "EnumItem"
[Enum.KeyCode.World88]
struct = "EnumItem"
[Enum.KeyCode.World89]
struct = "EnumItem"
[Enum.KeyCode.World9]
struct = "EnumItem"
[Enum.KeyCode.World90]
struct = "EnumItem"
[Enum.KeyCode.World91]
struct = "EnumItem"
[Enum.KeyCode.World92]
struct = "EnumItem"
[Enum.KeyCode.World93]
struct = "EnumItem"
[Enum.KeyCode.World94]
struct = "EnumItem"
[Enum.KeyCode.World95]
struct = "EnumItem"
[Enum.KeyCode.X]
struct = "EnumItem"
[Enum.KeyCode.Y]
struct = "EnumItem"
[Enum.KeyCode.Z]
struct = "EnumItem"
[Enum.KeyCode.Zero]
struct = "EnumItem"
[Enum.KeywordFilterType.Exclude]
struct = "EnumItem"
[Enum.KeywordFilterType.GetEnumItems]
args = []
method = true
[Enum.KeywordFilterType.Include]
struct = "EnumItem"
[Enum.Language.Default]
struct = "EnumItem"
[Enum.Language.GetEnumItems]
args = []
method = true
[Enum.LanguagePreference.English]
struct = "EnumItem"
[Enum.LanguagePreference.GetEnumItems]
args = []
method = true
[Enum.LanguagePreference.Korean]
struct = "EnumItem"
[Enum.LanguagePreference.SimplifiedChinese]
struct = "EnumItem"
[Enum.LanguagePreference.SystemDefault]
struct = "EnumItem"
[Enum.LeftRight.Center]
struct = "EnumItem"
[Enum.LeftRight.GetEnumItems]
args = []
method = true
[Enum.LeftRight.Left]
struct = "EnumItem"
[Enum.LeftRight.Right]
struct = "EnumItem"
[Enum.LevelOfDetailSetting.GetEnumItems]
args = []
method = true
[Enum.LevelOfDetailSetting.High]
struct = "EnumItem"
[Enum.LevelOfDetailSetting.Low]
struct = "EnumItem"
[Enum.LevelOfDetailSetting.Medium]
struct = "EnumItem"
[Enum.Limb.GetEnumItems]
args = []
method = true
[Enum.Limb.Head]
struct = "EnumItem"
[Enum.Limb.LeftArm]
struct = "EnumItem"
[Enum.Limb.LeftLeg]
struct = "EnumItem"
[Enum.Limb.RightArm]
struct = "EnumItem"
[Enum.Limb.RightLeg]
struct = "EnumItem"
[Enum.Limb.Torso]
struct = "EnumItem"
[Enum.Limb.Unknown]
struct = "EnumItem"
[Enum.ListDisplayMode.GetEnumItems]
args = []
method = true
[Enum.ListDisplayMode.Horizontal]
struct = "EnumItem"
[Enum.ListDisplayMode.Vertical]
struct = "EnumItem"
[Enum.ListenerType.CFrame]
struct = "EnumItem"
[Enum.ListenerType.Camera]
struct = "EnumItem"
[Enum.ListenerType.GetEnumItems]
args = []
method = true
[Enum.ListenerType.ObjectCFrame]
struct = "EnumItem"
[Enum.ListenerType.ObjectPosition]
struct = "EnumItem"
[Enum.Material.Air]
struct = "EnumItem"
[Enum.Material.Asphalt]
struct = "EnumItem"
[Enum.Material.Basalt]
struct = "EnumItem"
[Enum.Material.Brick]
struct = "EnumItem"
[Enum.Material.Cobblestone]
struct = "EnumItem"
[Enum.Material.Concrete]
struct = "EnumItem"
[Enum.Material.CorrodedMetal]
struct = "EnumItem"
[Enum.Material.CrackedLava]
struct = "EnumItem"
[Enum.Material.DiamondPlate]
struct = "EnumItem"
[Enum.Material.Fabric]
struct = "EnumItem"
[Enum.Material.Foil]
struct = "EnumItem"
[Enum.Material.ForceField]
struct = "EnumItem"
[Enum.Material.GetEnumItems]
args = []
method = true
[Enum.Material.Glacier]
struct = "EnumItem"
[Enum.Material.Glass]
struct = "EnumItem"
[Enum.Material.Granite]
struct = "EnumItem"
[Enum.Material.Grass]
struct = "EnumItem"
[Enum.Material.Ground]
struct = "EnumItem"
[Enum.Material.Ice]
struct = "EnumItem"
[Enum.Material.LeafyGrass]
struct = "EnumItem"
[Enum.Material.Limestone]
struct = "EnumItem"
[Enum.Material.Marble]
struct = "EnumItem"
[Enum.Material.Metal]
struct = "EnumItem"
[Enum.Material.Mud]
struct = "EnumItem"
[Enum.Material.Neon]
struct = "EnumItem"
[Enum.Material.Pavement]
struct = "EnumItem"
[Enum.Material.Pebble]
struct = "EnumItem"
[Enum.Material.Plastic]
struct = "EnumItem"
[Enum.Material.Rock]
struct = "EnumItem"
[Enum.Material.Salt]
struct = "EnumItem"
[Enum.Material.Sand]
struct = "EnumItem"
[Enum.Material.Sandstone]
struct = "EnumItem"
[Enum.Material.Slate]
struct = "EnumItem"
[Enum.Material.SmoothPlastic]
struct = "EnumItem"
[Enum.Material.Snow]
struct = "EnumItem"
[Enum.Material.Water]
struct = "EnumItem"
[Enum.Material.Wood]
struct = "EnumItem"
[Enum.Material.WoodPlanks]
struct = "EnumItem"
[Enum.MembershipType.BuildersClub]
struct = "EnumItem"
[Enum.MembershipType.GetEnumItems]
args = []
method = true
[Enum.MembershipType.None]
struct = "EnumItem"
[Enum.MembershipType.OutrageousBuildersClub]
struct = "EnumItem"
[Enum.MembershipType.Premium]
struct = "EnumItem"
[Enum.MembershipType.TurboBuildersClub]
struct = "EnumItem"
[Enum.MeshType.Brick]
struct = "EnumItem"
[Enum.MeshType.CornerWedge]
struct = "EnumItem"
[Enum.MeshType.Cylinder]
struct = "EnumItem"
[Enum.MeshType.FileMesh]
struct = "EnumItem"
[Enum.MeshType.GetEnumItems]
args = []
method = true
[Enum.MeshType.Head]
struct = "EnumItem"
[Enum.MeshType.ParallelRamp]
struct = "EnumItem"
[Enum.MeshType.Prism]
struct = "EnumItem"
[Enum.MeshType.Pyramid]
struct = "EnumItem"
[Enum.MeshType.RightAngleRamp]
struct = "EnumItem"
[Enum.MeshType.Sphere]
struct = "EnumItem"
[Enum.MeshType.Torso]
struct = "EnumItem"
[Enum.MeshType.Wedge]
struct = "EnumItem"
[Enum.MessageType.GetEnumItems]
args = []
method = true
[Enum.MessageType.MessageError]
struct = "EnumItem"
[Enum.MessageType.MessageInfo]
struct = "EnumItem"
[Enum.MessageType.MessageOutput]
struct = "EnumItem"
[Enum.MessageType.MessageWarning]
struct = "EnumItem"
[Enum.ModifierKey.Alt]
struct = "EnumItem"
[Enum.ModifierKey.Ctrl]
struct = "EnumItem"
[Enum.ModifierKey.GetEnumItems]
args = []
method = true
[Enum.ModifierKey.Meta]
struct = "EnumItem"
[Enum.ModifierKey.Shift]
struct = "EnumItem"
[Enum.MouseBehavior.Default]
struct = "EnumItem"
[Enum.MouseBehavior.GetEnumItems]
args = []
method = true
[Enum.MouseBehavior.LockCenter]
struct = "EnumItem"
[Enum.MouseBehavior.LockCurrentPosition]
struct = "EnumItem"
[Enum.MoveState.AirFree]
struct = "EnumItem"
[Enum.MoveState.Coasting]
struct = "EnumItem"
[Enum.MoveState.GetEnumItems]
args = []
method = true
[Enum.MoveState.Pushing]
struct = "EnumItem"
[Enum.MoveState.Stopped]
struct = "EnumItem"
[Enum.MoveState.Stopping]
struct = "EnumItem"
[Enum.NameOcclusion.EnemyOcclusion]
struct = "EnumItem"
[Enum.NameOcclusion.GetEnumItems]
args = []
method = true
[Enum.NameOcclusion.NoOcclusion]
struct = "EnumItem"
[Enum.NameOcclusion.OccludeAll]
struct = "EnumItem"
[Enum.NetworkOwnership.Automatic]
struct = "EnumItem"
[Enum.NetworkOwnership.GetEnumItems]
args = []
method = true
[Enum.NetworkOwnership.Manual]
struct = "EnumItem"
[Enum.NetworkOwnership.OnContact]
struct = "EnumItem"
[Enum.NormalId.Back]
struct = "EnumItem"
[Enum.NormalId.Bottom]
struct = "EnumItem"
[Enum.NormalId.Front]
struct = "EnumItem"
[Enum.NormalId.GetEnumItems]
args = []
method = true
[Enum.NormalId.Left]
struct = "EnumItem"
[Enum.NormalId.Right]
struct = "EnumItem"
[Enum.NormalId.Top]
struct = "EnumItem"
[Enum.OutputLayoutMode.GetEnumItems]
args = []
method = true
[Enum.OutputLayoutMode.Horizontal]
struct = "EnumItem"
[Enum.OutputLayoutMode.Vertical]
struct = "EnumItem"
[Enum.OverrideMouseIconBehavior.ForceHide]
struct = "EnumItem"
[Enum.OverrideMouseIconBehavior.ForceShow]
struct = "EnumItem"
[Enum.OverrideMouseIconBehavior.GetEnumItems]
args = []
method = true
[Enum.OverrideMouseIconBehavior.None]
struct = "EnumItem"
[Enum.PacketPriority.GetEnumItems]
args = []
method = true
[Enum.PacketPriority.HIGH_PRIORITY]
struct = "EnumItem"
[Enum.PacketPriority.IMMEDIATE_PRIORITY]
struct = "EnumItem"
[Enum.PacketPriority.LOW_PRIORITY]
struct = "EnumItem"
[Enum.PacketPriority.MEDIUM_PRIORITY]
struct = "EnumItem"
[Enum.PartType.Ball]
struct = "EnumItem"
[Enum.PartType.Block]
struct = "EnumItem"
[Enum.PartType.Cylinder]
struct = "EnumItem"
[Enum.PartType.GetEnumItems]
args = []
method = true
[Enum.PathStatus.ClosestNoPath]
struct = "EnumItem"
[Enum.PathStatus.ClosestOutOfRange]
struct = "EnumItem"
[Enum.PathStatus.FailFinishNotEmpty]
struct = "EnumItem"
[Enum.PathStatus.FailStartNotEmpty]
struct = "EnumItem"
[Enum.PathStatus.GetEnumItems]
args = []
method = true
[Enum.PathStatus.NoPath]
struct = "EnumItem"
[Enum.PathStatus.Success]
struct = "EnumItem"
[Enum.PathWaypointAction.GetEnumItems]
args = []
method = true
[Enum.PathWaypointAction.Jump]
struct = "EnumItem"
[Enum.PathWaypointAction.Walk]
struct = "EnumItem"
[Enum.PermissionLevelShown.Game]
struct = "EnumItem"
[Enum.PermissionLevelShown.GetEnumItems]
args = []
method = true
[Enum.PermissionLevelShown.Roblox]
struct = "EnumItem"
[Enum.PermissionLevelShown.RobloxGame]
struct = "EnumItem"
[Enum.PermissionLevelShown.RobloxScript]
struct = "EnumItem"
[Enum.PermissionLevelShown.Studio]
struct = "EnumItem"
[Enum.PhysicsSimulationRate.Fixed120Hz]
struct = "EnumItem"
[Enum.PhysicsSimulationRate.Fixed240Hz]
struct = "EnumItem"
[Enum.PhysicsSimulationRate.Fixed60Hz]
struct = "EnumItem"
[Enum.PhysicsSimulationRate.GetEnumItems]
args = []
method = true
[Enum.Platform.Android]
struct = "EnumItem"
[Enum.Platform.AndroidTV]
struct = "EnumItem"
[Enum.Platform.BeOS]
struct = "EnumItem"
[Enum.Platform.Chromecast]
struct = "EnumItem"
[Enum.Platform.DOS]
struct = "EnumItem"
[Enum.Platform.GetEnumItems]
args = []
method = true
[Enum.Platform.IOS]
struct = "EnumItem"
[Enum.Platform.Linux]
struct = "EnumItem"
[Enum.Platform.NX]
struct = "EnumItem"
[Enum.Platform.None]
struct = "EnumItem"
[Enum.Platform.OSX]
struct = "EnumItem"
[Enum.Platform.Ouya]
struct = "EnumItem"
[Enum.Platform.PS3]
struct = "EnumItem"
[Enum.Platform.PS4]
struct = "EnumItem"
[Enum.Platform.SteamOS]
struct = "EnumItem"
[Enum.Platform.UWP]
struct = "EnumItem"
[Enum.Platform.WebOS]
struct = "EnumItem"
[Enum.Platform.WiiU]
struct = "EnumItem"
[Enum.Platform.Windows]
struct = "EnumItem"
[Enum.Platform.XBox360]
struct = "EnumItem"
[Enum.Platform.XBoxOne]
struct = "EnumItem"
[Enum.PlaybackState.Begin]
struct = "EnumItem"
[Enum.PlaybackState.Cancelled]
struct = "EnumItem"
[Enum.PlaybackState.Completed]
struct = "EnumItem"
[Enum.PlaybackState.Delayed]
struct = "EnumItem"
[Enum.PlaybackState.GetEnumItems]
args = []
method = true
[Enum.PlaybackState.Paused]
struct = "EnumItem"
[Enum.PlaybackState.Playing]
struct = "EnumItem"
[Enum.PlayerActions.CharacterBackward]
struct = "EnumItem"
[Enum.PlayerActions.CharacterForward]
struct = "EnumItem"
[Enum.PlayerActions.CharacterJump]
struct = "EnumItem"
[Enum.PlayerActions.CharacterLeft]
struct = "EnumItem"
[Enum.PlayerActions.CharacterRight]
struct = "EnumItem"
[Enum.PlayerActions.GetEnumItems]
args = []
method = true
[Enum.PlayerChatType.All]
struct = "EnumItem"
[Enum.PlayerChatType.GetEnumItems]
args = []
method = true
[Enum.PlayerChatType.Team]
struct = "EnumItem"
[Enum.PlayerChatType.Whisper]
struct = "EnumItem"
[Enum.PoseEasingDirection.GetEnumItems]
args = []
method = true
[Enum.PoseEasingDirection.In]
struct = "EnumItem"
[Enum.PoseEasingDirection.InOut]
struct = "EnumItem"
[Enum.PoseEasingDirection.Out]
struct = "EnumItem"
[Enum.PoseEasingStyle.Bounce]
struct = "EnumItem"
[Enum.PoseEasingStyle.Constant]
struct = "EnumItem"
[Enum.PoseEasingStyle.Cubic]
struct = "EnumItem"
[Enum.PoseEasingStyle.Elastic]
struct = "EnumItem"
[Enum.PoseEasingStyle.GetEnumItems]
args = []
method = true
[Enum.PoseEasingStyle.Linear]
struct = "EnumItem"
[Enum.PrivilegeType.Admin]
struct = "EnumItem"
[Enum.PrivilegeType.Banned]
struct = "EnumItem"
[Enum.PrivilegeType.GetEnumItems]
args = []
method = true
[Enum.PrivilegeType.Member]
struct = "EnumItem"
[Enum.PrivilegeType.Owner]
struct = "EnumItem"
[Enum.PrivilegeType.Visitor]
struct = "EnumItem"
[Enum.ProductPurchaseDecision.GetEnumItems]
args = []
method = true
[Enum.ProductPurchaseDecision.NotProcessedYet]
struct = "EnumItem"
[Enum.ProductPurchaseDecision.PurchaseGranted]
struct = "EnumItem"
[Enum.QualityLevel.Automatic]
struct = "EnumItem"
[Enum.QualityLevel.GetEnumItems]
args = []
method = true
[Enum.QualityLevel.Level01]
struct = "EnumItem"
[Enum.QualityLevel.Level02]
struct = "EnumItem"
[Enum.QualityLevel.Level03]
struct = "EnumItem"
[Enum.QualityLevel.Level04]
struct = "EnumItem"
[Enum.QualityLevel.Level05]
struct = "EnumItem"
[Enum.QualityLevel.Level06]
struct = "EnumItem"
[Enum.QualityLevel.Level07]
struct = "EnumItem"
[Enum.QualityLevel.Level08]
struct = "EnumItem"
[Enum.QualityLevel.Level09]
struct = "EnumItem"
[Enum.QualityLevel.Level10]
struct = "EnumItem"
[Enum.QualityLevel.Level11]
struct = "EnumItem"
[Enum.QualityLevel.Level12]
struct = "EnumItem"
[Enum.QualityLevel.Level13]
struct = "EnumItem"
[Enum.QualityLevel.Level14]
struct = "EnumItem"
[Enum.QualityLevel.Level15]
struct = "EnumItem"
[Enum.QualityLevel.Level16]
struct = "EnumItem"
[Enum.QualityLevel.Level17]
struct = "EnumItem"
[Enum.QualityLevel.Level18]
struct = "EnumItem"
[Enum.QualityLevel.Level19]
struct = "EnumItem"
[Enum.QualityLevel.Level20]
struct = "EnumItem"
[Enum.QualityLevel.Level21]
struct = "EnumItem"
[Enum.R15CollisionType.GetEnumItems]
args = []
method = true
[Enum.R15CollisionType.InnerBox]
struct = "EnumItem"
[Enum.R15CollisionType.OuterBox]
struct = "EnumItem"
[Enum.RaycastFilterType.Blacklist]
struct = "EnumItem"
[Enum.RaycastFilterType.GetEnumItems]
args = []
method = true
[Enum.RaycastFilterType.Whitelist]
struct = "EnumItem"
[Enum.RenderFidelity.Automatic]
struct = "EnumItem"
[Enum.RenderFidelity.GetEnumItems]
args = []
method = true
[Enum.RenderFidelity.Precise]
struct = "EnumItem"
[Enum.RenderPriority.Camera]
struct = "EnumItem"
[Enum.RenderPriority.Character]
struct = "EnumItem"
[Enum.RenderPriority.First]
struct = "EnumItem"
[Enum.RenderPriority.GetEnumItems]
args = []
method = true
[Enum.RenderPriority.Input]
struct = "EnumItem"
[Enum.RenderPriority.Last]
struct = "EnumItem"
[Enum.RenderingTestComparisonMethod.GetEnumItems]
args = []
method = true
[Enum.RenderingTestComparisonMethod.diff]
struct = "EnumItem"
[Enum.RenderingTestComparisonMethod.psnr]
struct = "EnumItem"
[Enum.ReturnKeyType.Default]
struct = "EnumItem"
[Enum.ReturnKeyType.Done]
struct = "EnumItem"
[Enum.ReturnKeyType.GetEnumItems]
args = []
method = true
[Enum.ReturnKeyType.Go]
struct = "EnumItem"
[Enum.ReturnKeyType.Next]
struct = "EnumItem"
[Enum.ReturnKeyType.Search]
struct = "EnumItem"
[Enum.ReturnKeyType.Send]
struct = "EnumItem"
[Enum.ReverbType.Alley]
struct = "EnumItem"
[Enum.ReverbType.Arena]
struct = "EnumItem"
[Enum.ReverbType.Auditorium]
struct = "EnumItem"
[Enum.ReverbType.Bathroom]
struct = "EnumItem"
[Enum.ReverbType.CarpettedHallway]
struct = "EnumItem"
[Enum.ReverbType.Cave]
struct = "EnumItem"
[Enum.ReverbType.City]
struct = "EnumItem"
[Enum.ReverbType.ConcertHall]
struct = "EnumItem"
[Enum.ReverbType.Forest]
struct = "EnumItem"
[Enum.ReverbType.GenericReverb]
struct = "EnumItem"
[Enum.ReverbType.GetEnumItems]
args = []
method = true
[Enum.ReverbType.Hallway]
struct = "EnumItem"
[Enum.ReverbType.Hangar]
struct = "EnumItem"
[Enum.ReverbType.LivingRoom]
struct = "EnumItem"
[Enum.ReverbType.Mountains]
struct = "EnumItem"
[Enum.ReverbType.NoReverb]
struct = "EnumItem"
[Enum.ReverbType.PaddedCell]
struct = "EnumItem"
[Enum.ReverbType.ParkingLot]
struct = "EnumItem"
[Enum.ReverbType.Plain]
struct = "EnumItem"
[Enum.ReverbType.Quarry]
struct = "EnumItem"
[Enum.ReverbType.Room]
struct = "EnumItem"
[Enum.ReverbType.SewerPipe]
struct = "EnumItem"
[Enum.ReverbType.StoneCorridor]
struct = "EnumItem"
[Enum.ReverbType.StoneRoom]
struct = "EnumItem"
[Enum.ReverbType.UnderWater]
struct = "EnumItem"
[Enum.RibbonTool.ColorPicker]
struct = "EnumItem"
[Enum.RibbonTool.GetEnumItems]
args = []
method = true
[Enum.RibbonTool.Group]
struct = "EnumItem"
[Enum.RibbonTool.MaterialPicker]
struct = "EnumItem"
[Enum.RibbonTool.Move]
struct = "EnumItem"
[Enum.RibbonTool.None]
struct = "EnumItem"
[Enum.RibbonTool.Rotate]
struct = "EnumItem"
[Enum.RibbonTool.Scale]
struct = "EnumItem"
[Enum.RibbonTool.Select]
struct = "EnumItem"
[Enum.RibbonTool.Transform]
struct = "EnumItem"
[Enum.RibbonTool.Ungroup]
struct = "EnumItem"
[Enum.RollOffMode.GetEnumItems]
args = []
method = true
[Enum.RollOffMode.Inverse]
struct = "EnumItem"
[Enum.RollOffMode.InverseTapered]
struct = "EnumItem"
[Enum.RollOffMode.Linear]
struct = "EnumItem"
[Enum.RollOffMode.LinearSquare]
struct = "EnumItem"
[Enum.RotationType.CameraRelative]
struct = "EnumItem"
[Enum.RotationType.GetEnumItems]
args = []
method = true
[Enum.RotationType.MovementRelative]
struct = "EnumItem"
[Enum.RuntimeUndoBehavior.Aggregate]
struct = "EnumItem"
[Enum.RuntimeUndoBehavior.GetEnumItems]
args = []
method = true
[Enum.RuntimeUndoBehavior.Hybrid]
struct = "EnumItem"
[Enum.RuntimeUndoBehavior.Snapshot]
struct = "EnumItem"
[Enum.SaveFilter.GetEnumItems]
args = []
method = true
[Enum.SaveFilter.SaveAll]
struct = "EnumItem"
[Enum.SaveFilter.SaveGame]
struct = "EnumItem"
[Enum.SaveFilter.SaveWorld]
struct = "EnumItem"
[Enum.SavedQualitySetting.Automatic]
struct = "EnumItem"
[Enum.SavedQualitySetting.GetEnumItems]
args = []
method = true
[Enum.SavedQualitySetting.QualityLevel1]
struct = "EnumItem"
[Enum.SavedQualitySetting.QualityLevel10]
struct = "EnumItem"
[Enum.SavedQualitySetting.QualityLevel2]
struct = "EnumItem"
[Enum.SavedQualitySetting.QualityLevel3]
struct = "EnumItem"
[Enum.SavedQualitySetting.QualityLevel4]
struct = "EnumItem"
[Enum.SavedQualitySetting.QualityLevel5]
struct = "EnumItem"
[Enum.SavedQualitySetting.QualityLevel6]
struct = "EnumItem"
[Enum.SavedQualitySetting.QualityLevel7]
struct = "EnumItem"
[Enum.SavedQualitySetting.QualityLevel8]
struct = "EnumItem"
[Enum.SavedQualitySetting.QualityLevel9]
struct = "EnumItem"
[Enum.ScaleType.Crop]
struct = "EnumItem"
[Enum.ScaleType.Fit]
struct = "EnumItem"
[Enum.ScaleType.GetEnumItems]
args = []
method = true
[Enum.ScaleType.Slice]
struct = "EnumItem"
[Enum.ScaleType.Stretch]
struct = "EnumItem"
[Enum.ScaleType.Tile]
struct = "EnumItem"
[Enum.ScreenOrientation.GetEnumItems]
args = []
method = true
[Enum.ScreenOrientation.LandscapeLeft]
struct = "EnumItem"
[Enum.ScreenOrientation.LandscapeRight]
struct = "EnumItem"
[Enum.ScreenOrientation.LandscapeSensor]
struct = "EnumItem"
[Enum.ScreenOrientation.Portrait]
struct = "EnumItem"
[Enum.ScreenOrientation.Sensor]
struct = "EnumItem"
[Enum.ScrollBarInset.Always]
struct = "EnumItem"
[Enum.ScrollBarInset.GetEnumItems]
args = []
method = true
[Enum.ScrollBarInset.None]
struct = "EnumItem"
[Enum.ScrollBarInset.ScrollBar]
struct = "EnumItem"
[Enum.ScrollingDirection.GetEnumItems]
args = []
method = true
[Enum.ScrollingDirection.X]
struct = "EnumItem"
[Enum.ScrollingDirection.XY]
struct = "EnumItem"
[Enum.ScrollingDirection.Y]
struct = "EnumItem"
[Enum.ServerAudioBehavior.Enabled]
struct = "EnumItem"
[Enum.ServerAudioBehavior.GetEnumItems]
args = []
method = true
[Enum.ServerAudioBehavior.Muted]
struct = "EnumItem"
[Enum.ServerAudioBehavior.OnlineGame]
struct = "EnumItem"
[Enum.SizeConstraint.GetEnumItems]
args = []
method = true
[Enum.SizeConstraint.RelativeXX]
struct = "EnumItem"
[Enum.SizeConstraint.RelativeXY]
struct = "EnumItem"
[Enum.SizeConstraint.RelativeYY]
struct = "EnumItem"
[Enum.SkinnedMeshAllowType.Default]
struct = "EnumItem"
[Enum.SkinnedMeshAllowType.Disabled]
struct = "EnumItem"
[Enum.SkinnedMeshAllowType.Enabled]
struct = "EnumItem"
[Enum.SkinnedMeshAllowType.GetEnumItems]
args = []
method = true
[Enum.SortOrder.Custom]
struct = "EnumItem"
[Enum.SortOrder.GetEnumItems]
args = []
method = true
[Enum.SortOrder.LayoutOrder]
struct = "EnumItem"
[Enum.SortOrder.Name]
struct = "EnumItem"
[Enum.SoundType.Boing]
struct = "EnumItem"
[Enum.SoundType.Bomb]
struct = "EnumItem"
[Enum.SoundType.Break]
struct = "EnumItem"
[Enum.SoundType.Click]
struct = "EnumItem"
[Enum.SoundType.Clock]
struct = "EnumItem"
[Enum.SoundType.GetEnumItems]
args = []
method = true
[Enum.SoundType.NoSound]
struct = "EnumItem"
[Enum.SoundType.Page]
struct = "EnumItem"
[Enum.SoundType.Ping]
struct = "EnumItem"
[Enum.SoundType.Slingshot]
struct = "EnumItem"
[Enum.SoundType.Snap]
struct = "EnumItem"
[Enum.SoundType.Splat]
struct = "EnumItem"
[Enum.SoundType.Step]
struct = "EnumItem"
[Enum.SoundType.StepOn]
struct = "EnumItem"
[Enum.SoundType.Swoosh]
struct = "EnumItem"
[Enum.SoundType.Victory]
struct = "EnumItem"
[Enum.SpecialKey.ChatHotkey]
struct = "EnumItem"
[Enum.SpecialKey.End]
struct = "EnumItem"
[Enum.SpecialKey.GetEnumItems]
args = []
method = true
[Enum.SpecialKey.Home]
struct = "EnumItem"
[Enum.SpecialKey.Insert]
struct = "EnumItem"
[Enum.SpecialKey.PageDown]
struct = "EnumItem"
[Enum.SpecialKey.PageUp]
struct = "EnumItem"
[Enum.StartCorner.BottomLeft]
struct = "EnumItem"
[Enum.StartCorner.BottomRight]
struct = "EnumItem"
[Enum.StartCorner.GetEnumItems]
args = []
method = true
[Enum.StartCorner.TopLeft]
struct = "EnumItem"
[Enum.StartCorner.TopRight]
struct = "EnumItem"
[Enum.Status.Confusion]
struct = "EnumItem"
[Enum.Status.GetEnumItems]
args = []
method = true
[Enum.Status.Poison]
struct = "EnumItem"
[Enum.StreamingPauseMode.ClientPhysicsPause]
struct = "EnumItem"
[Enum.StreamingPauseMode.Default]
struct = "EnumItem"
[Enum.StreamingPauseMode.Disabled]
struct = "EnumItem"
[Enum.StreamingPauseMode.GetEnumItems]
args = []
method = true
[Enum.StudioDataModelType.Edit]
struct = "EnumItem"
[Enum.StudioDataModelType.GetEnumItems]
args = []
method = true
[Enum.StudioDataModelType.None]
struct = "EnumItem"
[Enum.StudioDataModelType.PlayClient]
struct = "EnumItem"
[Enum.StudioDataModelType.PlayServer]
struct = "EnumItem"
[Enum.StudioDataModelType.RobloxPlugin]
struct = "EnumItem"
[Enum.StudioDataModelType.UserPlugin]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.Border]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.BrightText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.Button]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ButtonBorder]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ButtonText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.CategoryItem]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ChatIncomingBgColor]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ChatIncomingTextColor]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ChatModeratedMessageColor]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ChatOutgoingBgColor]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ChatOutgoingTextColor]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.CheckedFieldBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.CheckedFieldBorder]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.CheckedFieldIndicator]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ColorPickerFrame]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.CurrentMarker]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.Dark]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DebuggerCurrentLine]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DebuggerErrorLine]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DialogButton]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DialogButtonBorder]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DialogButtonText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DialogMainButton]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DialogMainButtonText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffFilePathBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffFilePathBorder]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffFilePathText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffLineNum]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffLineNumAdditionBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffLineNumDeletionBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffLineNumNoChangeBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffLineNumSeparatorBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffTextAddition]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffTextAdditionBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffTextDeletion]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffTextDeletionBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffTextHunkInfo]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffTextNoChange]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffTextNoChangeBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DiffTextSeparatorBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.DimmedText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.Dropdown]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.EmulatorBar]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.EmulatorDropDown]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ErrorText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.GameSettingsTableItem]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.GameSettingsTooltip]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.GetEnumItems]
args = []
method = true
[Enum.StudioStyleGuideColor.HeaderSection]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.InfoBarWarningBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.InfoBarWarningText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.InfoText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.InputFieldBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.InputFieldBorder]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.Item]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.Light]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.LinkText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.MainBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.MainButton]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.MainText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.Mid]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.Midlight]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.Notification]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.RibbonButton]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.RibbonTab]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.RibbonTabTopBar]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptBuiltInFunction]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptComment]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptEditorCurrentLine]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptError]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptFindSelectionBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptKeyword]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptMatchingWordSelectionBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptNumber]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptOperator]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptPreprocessor]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptRuler]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptSelectionBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptSelectionText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptSideWidget]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptString]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptWarning]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScriptWhitespace]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScrollBar]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ScrollBarBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.SensitiveText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.Separator]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.Shadow]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.StatusBar]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.SubText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.Tab]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.TabBar]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.TableItem]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.Titlebar]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.TitlebarText]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.Tooltip]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.ViewPortBackground]
struct = "EnumItem"
[Enum.StudioStyleGuideColor.WarningText]
struct = "EnumItem"
[Enum.StudioStyleGuideModifier.Default]
struct = "EnumItem"
[Enum.StudioStyleGuideModifier.Disabled]
struct = "EnumItem"
[Enum.StudioStyleGuideModifier.GetEnumItems]
args = []
method = true
[Enum.StudioStyleGuideModifier.Hover]
struct = "EnumItem"
[Enum.StudioStyleGuideModifier.Pressed]
struct = "EnumItem"
[Enum.StudioStyleGuideModifier.Selected]
struct = "EnumItem"
[Enum.Style.AlternatingSupports]
struct = "EnumItem"
[Enum.Style.BridgeStyleSupports]
struct = "EnumItem"
[Enum.Style.GetEnumItems]
args = []
method = true
[Enum.Style.NoSupports]
struct = "EnumItem"
[Enum.SurfaceConstraint.GetEnumItems]
args = []
method = true
[Enum.SurfaceConstraint.Hinge]
struct = "EnumItem"
[Enum.SurfaceConstraint.Motor]
struct = "EnumItem"
[Enum.SurfaceConstraint.None]
struct = "EnumItem"
[Enum.SurfaceConstraint.SteppingMotor]
struct = "EnumItem"
[Enum.SurfaceGuiSizingMode.FixedSize]
struct = "EnumItem"
[Enum.SurfaceGuiSizingMode.GetEnumItems]
args = []
method = true
[Enum.SurfaceGuiSizingMode.PixelsPerStud]
struct = "EnumItem"
[Enum.SurfaceType.GetEnumItems]
args = []
method = true
[Enum.SurfaceType.Glue]
struct = "EnumItem"
[Enum.SurfaceType.Hinge]
struct = "EnumItem"
[Enum.SurfaceType.Inlet]
struct = "EnumItem"
[Enum.SurfaceType.Motor]
struct = "EnumItem"
[Enum.SurfaceType.Smooth]
struct = "EnumItem"
[Enum.SurfaceType.SmoothNoOutlines]
struct = "EnumItem"
[Enum.SurfaceType.SteppingMotor]
struct = "EnumItem"
[Enum.SurfaceType.Studs]
struct = "EnumItem"
[Enum.SurfaceType.Universal]
struct = "EnumItem"
[Enum.SurfaceType.Weld]
struct = "EnumItem"
[Enum.SwipeDirection.Down]
struct = "EnumItem"
[Enum.SwipeDirection.GetEnumItems]
args = []
method = true
[Enum.SwipeDirection.Left]
struct = "EnumItem"
[Enum.SwipeDirection.None]
struct = "EnumItem"
[Enum.SwipeDirection.Right]
struct = "EnumItem"
[Enum.SwipeDirection.Up]
struct = "EnumItem"
[Enum.TableMajorAxis.ColumnMajor]
struct = "EnumItem"
[Enum.TableMajorAxis.GetEnumItems]
args = []
method = true
[Enum.TableMajorAxis.RowMajor]
struct = "EnumItem"
[Enum.Technology.Compatibility]
struct = "EnumItem"
[Enum.Technology.Future]
struct = "EnumItem"
[Enum.Technology.GetEnumItems]
args = []
method = true
[Enum.Technology.Legacy]
struct = "EnumItem"
[Enum.Technology.ShadowMap]
struct = "EnumItem"
[Enum.Technology.Voxel]
struct = "EnumItem"
[Enum.TeleportMethod.GetEnumItems]
args = []
method = true
[Enum.TeleportMethod.TeleportToPartyAsync]
struct = "EnumItem"
[Enum.TeleportMethod.TeleportToPlaceInstance]
struct = "EnumItem"
[Enum.TeleportMethod.TeleportToPrivateServer]
struct = "EnumItem"
[Enum.TeleportMethod.TeleportToSpawnByName]
struct = "EnumItem"
[Enum.TeleportResult.Failure]
struct = "EnumItem"
[Enum.TeleportResult.Flooded]
struct = "EnumItem"
[Enum.TeleportResult.GameEnded]
struct = "EnumItem"
[Enum.TeleportResult.GameFull]
struct = "EnumItem"
[Enum.TeleportResult.GameNotFound]
struct = "EnumItem"
[Enum.TeleportResult.GetEnumItems]
args = []
method = true
[Enum.TeleportResult.IsTeleporting]
struct = "EnumItem"
[Enum.TeleportResult.Success]
struct = "EnumItem"
[Enum.TeleportResult.Unauthorized]
struct = "EnumItem"
[Enum.TeleportState.Failed]
struct = "EnumItem"
[Enum.TeleportState.GetEnumItems]
args = []
method = true
[Enum.TeleportState.InProgress]
struct = "EnumItem"
[Enum.TeleportState.RequestedFromServer]
struct = "EnumItem"
[Enum.TeleportState.Started]
struct = "EnumItem"
[Enum.TeleportState.WaitingForServer]
struct = "EnumItem"
[Enum.TeleportType.GetEnumItems]
args = []
method = true
[Enum.TeleportType.ToInstance]
struct = "EnumItem"
[Enum.TeleportType.ToPlace]
struct = "EnumItem"
[Enum.TeleportType.ToReservedServer]
struct = "EnumItem"
[Enum.TextFilterContext.GetEnumItems]
args = []
method = true
[Enum.TextFilterContext.PrivateChat]
struct = "EnumItem"
[Enum.TextFilterContext.PublicChat]
struct = "EnumItem"
[Enum.TextInputType.Default]
struct = "EnumItem"
[Enum.TextInputType.Email]
struct = "EnumItem"
[Enum.TextInputType.GetEnumItems]
args = []
method = true
[Enum.TextInputType.NoSuggestions]
struct = "EnumItem"
[Enum.TextInputType.Number]
struct = "EnumItem"
[Enum.TextInputType.Password]
struct = "EnumItem"
[Enum.TextInputType.Phone]
struct = "EnumItem"
[Enum.TextTruncate.AtEnd]
struct = "EnumItem"
[Enum.TextTruncate.GetEnumItems]
args = []
method = true
[Enum.TextTruncate.None]
struct = "EnumItem"
[Enum.TextXAlignment.Center]
struct = "EnumItem"
[Enum.TextXAlignment.GetEnumItems]
args = []
method = true
[Enum.TextXAlignment.Left]
struct = "EnumItem"
[Enum.TextXAlignment.Right]
struct = "EnumItem"
[Enum.TextYAlignment.Bottom]
struct = "EnumItem"
[Enum.TextYAlignment.Center]
struct = "EnumItem"
[Enum.TextYAlignment.GetEnumItems]
args = []
method = true
[Enum.TextYAlignment.Top]
struct = "EnumItem"
[Enum.TextureMode.GetEnumItems]
args = []
method = true
[Enum.TextureMode.Static]
struct = "EnumItem"
[Enum.TextureMode.Stretch]
struct = "EnumItem"
[Enum.TextureMode.Wrap]
struct = "EnumItem"
[Enum.TextureQueryType.GetEnumItems]
args = []
method = true
[Enum.TextureQueryType.Humanoid]
struct = "EnumItem"
[Enum.TextureQueryType.HumanoidOrphaned]
struct = "EnumItem"
[Enum.TextureQueryType.NonHumanoid]
struct = "EnumItem"
[Enum.TextureQueryType.NonHumanoidOrphaned]
struct = "EnumItem"
[Enum.ThreadPoolConfig.Auto]
struct = "EnumItem"
[Enum.ThreadPoolConfig.GetEnumItems]
args = []
method = true
[Enum.ThreadPoolConfig.PerCore1]
struct = "EnumItem"
[Enum.ThreadPoolConfig.PerCore2]
struct = "EnumItem"
[Enum.ThreadPoolConfig.PerCore3]
struct = "EnumItem"
[Enum.ThreadPoolConfig.PerCore4]
struct = "EnumItem"
[Enum.ThreadPoolConfig.Threads1]
struct = "EnumItem"
[Enum.ThreadPoolConfig.Threads16]
struct = "EnumItem"
[Enum.ThreadPoolConfig.Threads2]
struct = "EnumItem"
[Enum.ThreadPoolConfig.Threads3]
struct = "EnumItem"
[Enum.ThreadPoolConfig.Threads4]
struct = "EnumItem"
[Enum.ThreadPoolConfig.Threads8]
struct = "EnumItem"
[Enum.ThrottlingPriority.Default]
struct = "EnumItem"
[Enum.ThrottlingPriority.ElevatedOnServer]
struct = "EnumItem"
[Enum.ThrottlingPriority.Extreme]
struct = "EnumItem"
[Enum.ThrottlingPriority.GetEnumItems]
args = []
method = true
[Enum.ThumbnailSize.GetEnumItems]
args = []
method = true
[Enum.ThumbnailSize.Size100x100]
struct = "EnumItem"
[Enum.ThumbnailSize.Size150x150]
struct = "EnumItem"
[Enum.ThumbnailSize.Size180x180]
struct = "EnumItem"
[Enum.ThumbnailSize.Size352x352]
struct = "EnumItem"
[Enum.ThumbnailSize.Size420x420]
struct = "EnumItem"
[Enum.ThumbnailSize.Size48x48]
struct = "EnumItem"
[Enum.ThumbnailSize.Size60x60]
struct = "EnumItem"
[Enum.ThumbnailType.AvatarBust]
struct = "EnumItem"
[Enum.ThumbnailType.AvatarThumbnail]
struct = "EnumItem"
[Enum.ThumbnailType.GetEnumItems]
args = []
method = true
[Enum.ThumbnailType.HeadShot]
struct = "EnumItem"
[Enum.TickCountSampleMethod.Benchmark]
struct = "EnumItem"
[Enum.TickCountSampleMethod.Fast]
struct = "EnumItem"
[Enum.TickCountSampleMethod.GetEnumItems]
args = []
method = true
[Enum.TickCountSampleMethod.Precise]
struct = "EnumItem"
[Enum.TopBottom.Bottom]
struct = "EnumItem"
[Enum.TopBottom.Center]
struct = "EnumItem"
[Enum.TopBottom.GetEnumItems]
args = []
method = true
[Enum.TopBottom.Top]
struct = "EnumItem"
[Enum.TouchCameraMovementMode.Classic]
struct = "EnumItem"
[Enum.TouchCameraMovementMode.Default]
struct = "EnumItem"
[Enum.TouchCameraMovementMode.Follow]
struct = "EnumItem"
[Enum.TouchCameraMovementMode.GetEnumItems]
args = []
method = true
[Enum.TouchCameraMovementMode.Orbital]
struct = "EnumItem"
[Enum.TouchMovementMode.ClickToMove]
struct = "EnumItem"
[Enum.TouchMovementMode.DPad]
struct = "EnumItem"
[Enum.TouchMovementMode.Default]
struct = "EnumItem"
[Enum.TouchMovementMode.DynamicThumbstick]
struct = "EnumItem"
[Enum.TouchMovementMode.GetEnumItems]
args = []
method = true
[Enum.TouchMovementMode.Thumbpad]
struct = "EnumItem"
[Enum.TouchMovementMode.Thumbstick]
struct = "EnumItem"
[Enum.TweenStatus.Canceled]
struct = "EnumItem"
[Enum.TweenStatus.Completed]
struct = "EnumItem"
[Enum.TweenStatus.GetEnumItems]
args = []
method = true
[Enum.UITheme.Dark]
struct = "EnumItem"
[Enum.UITheme.GetEnumItems]
args = []
method = true
[Enum.UITheme.Light]
struct = "EnumItem"
[Enum.UiMessageType.GetEnumItems]
args = []
method = true
[Enum.UiMessageType.UiMessageError]
struct = "EnumItem"
[Enum.UiMessageType.UiMessageInfo]
struct = "EnumItem"
[Enum.UploadSetting.Always]
struct = "EnumItem"
[Enum.UploadSetting.Ask]
struct = "EnumItem"
[Enum.UploadSetting.GetEnumItems]
args = []
method = true
[Enum.UploadSetting.Never]
struct = "EnumItem"
[Enum.UserCFrame.GetEnumItems]
args = []
method = true
[Enum.UserCFrame.Head]
struct = "EnumItem"
[Enum.UserCFrame.LeftHand]
struct = "EnumItem"
[Enum.UserCFrame.RightHand]
struct = "EnumItem"
[Enum.UserInputState.Begin]
struct = "EnumItem"
[Enum.UserInputState.Cancel]
struct = "EnumItem"
[Enum.UserInputState.Change]
struct = "EnumItem"
[Enum.UserInputState.End]
struct = "EnumItem"
[Enum.UserInputState.GetEnumItems]
args = []
method = true
[Enum.UserInputState.None]
struct = "EnumItem"
[Enum.UserInputType.Accelerometer]
struct = "EnumItem"
[Enum.UserInputType.Focus]
struct = "EnumItem"
[Enum.UserInputType.Gamepad1]
struct = "EnumItem"
[Enum.UserInputType.Gamepad2]
struct = "EnumItem"
[Enum.UserInputType.Gamepad3]
struct = "EnumItem"
[Enum.UserInputType.Gamepad4]
struct = "EnumItem"
[Enum.UserInputType.Gamepad5]
struct = "EnumItem"
[Enum.UserInputType.Gamepad6]
struct = "EnumItem"
[Enum.UserInputType.Gamepad7]
struct = "EnumItem"
[Enum.UserInputType.Gamepad8]
struct = "EnumItem"
[Enum.UserInputType.GetEnumItems]
args = []
method = true
[Enum.UserInputType.Gyro]
struct = "EnumItem"
[Enum.UserInputType.InputMethod]
struct = "EnumItem"
[Enum.UserInputType.Keyboard]
struct = "EnumItem"
[Enum.UserInputType.MouseButton1]
struct = "EnumItem"
[Enum.UserInputType.MouseButton2]
struct = "EnumItem"
[Enum.UserInputType.MouseButton3]
struct = "EnumItem"
[Enum.UserInputType.MouseMovement]
struct = "EnumItem"
[Enum.UserInputType.MouseWheel]
struct = "EnumItem"
[Enum.UserInputType.None]
struct = "EnumItem"
[Enum.UserInputType.TextInput]
struct = "EnumItem"
[Enum.UserInputType.Touch]
struct = "EnumItem"
[Enum.VRTouchpad.GetEnumItems]
args = []
method = true
[Enum.VRTouchpad.Left]
struct = "EnumItem"
[Enum.VRTouchpad.Right]
struct = "EnumItem"
[Enum.VRTouchpadMode.ABXY]
struct = "EnumItem"
[Enum.VRTouchpadMode.GetEnumItems]
args = []
method = true
[Enum.VRTouchpadMode.Touch]
struct = "EnumItem"
[Enum.VRTouchpadMode.VirtualThumbstick]
struct = "EnumItem"
[Enum.VerticalAlignment.Bottom]
struct = "EnumItem"
[Enum.VerticalAlignment.Center]
struct = "EnumItem"
[Enum.VerticalAlignment.GetEnumItems]
args = []
method = true
[Enum.VerticalAlignment.Top]
struct = "EnumItem"
[Enum.VerticalScrollBarPosition.GetEnumItems]
args = []
method = true
[Enum.VerticalScrollBarPosition.Left]
struct = "EnumItem"
[Enum.VerticalScrollBarPosition.Right]
struct = "EnumItem"
[Enum.VibrationMotor.GetEnumItems]
args = []
method = true
[Enum.VibrationMotor.Large]
struct = "EnumItem"
[Enum.VibrationMotor.LeftHand]
struct = "EnumItem"
[Enum.VibrationMotor.LeftTrigger]
struct = "EnumItem"
[Enum.VibrationMotor.RightHand]
struct = "EnumItem"
[Enum.VibrationMotor.RightTrigger]
struct = "EnumItem"
[Enum.VibrationMotor.Small]
struct = "EnumItem"
[Enum.VideoQualitySettings.GetEnumItems]
args = []
method = true
[Enum.VideoQualitySettings.HighResolution]
struct = "EnumItem"
[Enum.VideoQualitySettings.LowResolution]
struct = "EnumItem"
[Enum.VideoQualitySettings.MediumResolution]
struct = "EnumItem"
[Enum.VirtualInputMode.GetEnumItems]
args = []
method = true
[Enum.VirtualInputMode.None]
struct = "EnumItem"
[Enum.VirtualInputMode.Playing]
struct = "EnumItem"
[Enum.VirtualInputMode.Recording]
struct = "EnumItem"
[Enum.WaterDirection.GetEnumItems]
args = []
method = true
[Enum.WaterDirection.NegX]
struct = "EnumItem"
[Enum.WaterDirection.NegY]
struct = "EnumItem"
[Enum.WaterDirection.NegZ]
struct = "EnumItem"
[Enum.WaterDirection.X]
struct = "EnumItem"
[Enum.WaterDirection.Y]
struct = "EnumItem"
[Enum.WaterDirection.Z]
struct = "EnumItem"
[Enum.WaterForce.GetEnumItems]
args = []
method = true
[Enum.WaterForce.Max]
struct = "EnumItem"
[Enum.WaterForce.Medium]
struct = "EnumItem"
[Enum.WaterForce.None]
struct = "EnumItem"
[Enum.WaterForce.Small]
struct = "EnumItem"
[Enum.WaterForce.Strong]
struct = "EnumItem"
[Enum.ZIndexBehavior.GetEnumItems]
args = []
method = true
[Enum.ZIndexBehavior.Global]
struct = "EnumItem"
[Enum.ZIndexBehavior.Sibling]
struct = "EnumItem"
[[Faces.new.args]]
type = "..."
[[Instance.new.args]]
type = ["Accoutrement", "Accessory", "Hat", "AdvancedDragger", "Animation", "AnimationController", "Animator", "Atmosphere", "Attachment", "Bone", "Backpack", "HopperBin", "Tool", "Flag", "Beam", "BindableEvent", "BindableFunction", "BodyAngularVelocity", "BodyForce", "BodyGyro", "BodyPosition", "BodyThrust", "BodyVelocity", "RocketPropulsion", "Camera", "BodyColors", "CharacterMesh", "Pants", "Shirt", "ShirtGraphic", "Skin", "ClickDetector", "Configuration", "AlignOrientation", "AlignPosition", "AngularVelocity", "BallSocketConstraint", "HingeConstraint", "LineForce", "RodConstraint", "RopeConstraint", "CylindricalConstraint", "PrismaticConstraint", "SpringConstraint", "Torque", "VectorForce", "HumanoidController", "SkateboardController", "VehicleController", "CustomEvent", "CustomEventReceiver", "BlockMesh", "CylinderMesh", "FileMesh", "SpecialMesh", "DebuggerWatch", "Dialog", "DialogChoice", "Dragger", "Explosion", "Decal", "Texture", "Hole", "MotorFeature", "Fire", "FlyweightService", "CSGDictionaryService", "NonReplicatedCSGDictionaryService", "Folder", "ForceField", "FunctionalTest", "Frame", "ImageButton", "TextButton", "ImageLabel", "TextLabel", "ScrollingFrame", "TextBox", "VideoFrame", "ViewportFrame", "BillboardGui", "ScreenGui", "GuiMain", "SurfaceGui", "FloorWire", "SelectionBox", "BoxHandleAdornment", "ConeHandleAdornment", "CylinderHandleAdornment", "ImageHandleAdornment", "LineHandleAdornment", "SphereHandleAdornment", "ParabolaAdornment", "SelectionSphere", "ArcHandles", "Handles", "SurfaceSelection", "SelectionPartLasso", "SelectionPointLasso", "Humanoid", "HumanoidDescription", "RotateP", "RotateV", "Glue", "ManualGlue", "ManualWeld", "Motor", "Motor6D", "Rotate", "Snap", "VelocityMotor", "Weld", "Keyframe", "KeyframeMarker", "KeyframeSequence", "PointLight", "SpotLight", "SurfaceLight", "LocalizationTable", "Script", "LocalScript", "ModuleScript", "Message", "Hint", "NoCollisionConstraint", "CornerWedgePart", "Part", "FlagStand", "Seat", "SkateboardPlatform", "SpawnLocation", "WedgePart", "MeshPart", "PartOperation", "NegateOperation", "UnionOperation", "TrussPart", "VehicleSeat", "Model", "WorldModel", "PartOperationAsset", "ParticleEmitter", "Player", "PluginAction", "Pose", "BloomEffect", "BlurEffect", "ColorCorrectionEffect", "DepthOfFieldEffect", "SunRaysEffect", "ReflectionMetadata", "ReflectionMetadataCallbacks", "ReflectionMetadataClasses", "ReflectionMetadataEnums", "ReflectionMetadataEvents", "ReflectionMetadataFunctions", "ReflectionMetadataClass", "ReflectionMetadataEnum", "ReflectionMetadataEnumItem", "ReflectionMetadataMember", "ReflectionMetadataProperties", "ReflectionMetadataYieldFunctions", "RemoteEvent", "RemoteFunction", "RenderingTest", "Sky", "Smoke", "Sound", "ChorusSoundEffect", "CompressorSoundEffect", "DistortionSoundEffect", "EchoSoundEffect", "EqualizerSoundEffect", "FlangeSoundEffect", "PitchShiftSoundEffect", "ReverbSoundEffect", "TremoloSoundEffect", "SoundGroup", "Sparkles", "StandalonePluginScripts", "StarterGear", "SurfaceAppearance", "Team", "TerrainRegion", "TestService", "Trail", "Tween", "UIAspectRatioConstraint", "UISizeConstraint", "UITextSizeConstraint", "UICorner", "UIGradient", "UIGridLayout", "UIInlineLayout", "UIListLayout", "UIPageLayout", "UITableLayout", "UIPadding", "UIScale", "BinaryStringValue", "BoolValue", "BrickColorValue", "CFrameValue", "Color3Value", "DoubleConstrainedValue", "IntConstrainedValue", "IntValue", "NumberValue", "ObjectValue", "RayValue", "StringValue", "Vector3Value", "VirtualInputManager", "WeldConstraint"]
[[NumberRange.new.args]]
type = "number"
[[NumberRange.new.args]]
required = false
type = "number"
[[NumberSequence.new.args]]
type = "any"
[[NumberSequence.new.args]]
required = false
type = "number"
[[NumberSequenceKeypoint.new.args]]
type = "number"
[[NumberSequenceKeypoint.new.args]]
type = "number"
[[NumberSequenceKeypoint.new.args]]
required = false
type = "number"
[[PathWaypoint.new.args]]
required = false
[PathWaypoint.new.args.type]
display = "Vector3"
[[PathWaypoint.new.args]]
required = false
[PathWaypoint.new.args.type]
display = "PathWaypointAction"
[[PhysicalProperties.new.args]]
type = "any"
[[PhysicalProperties.new.args]]
required = false
type = "number"
[[PhysicalProperties.new.args]]
required = false
type = "number"
[[PhysicalProperties.new.args]]
required = false
type = "number"
[[PhysicalProperties.new.args]]
required = false
type = "number"
[[Random.new.args]]
required = false
type = "number"
[[Ray.new.args]]
[Ray.new.args.type]
display = "Vector3"
[[Ray.new.args]]
[Ray.new.args.type]
display = "Vector3"
[[Rect.new.args]]
type = "any"
[[Rect.new.args]]
type = "any"
[[Rect.new.args]]
required = false
type = "number"
[[Rect.new.args]]
required = false
type = "number"
[[Region3.new.args]]
[Region3.new.args.type]
display = "Vector3"
[[Region3.new.args]]
[Region3.new.args.type]
display = "Vector3"
[[Region3int16.new.args]]
[Region3int16.new.args.type]
display = "Vector3"
[[Region3int16.new.args]]
[Region3int16.new.args.type]
display = "Vector3"
[[TweenInfo.new.args]]
required = false
type = "number"
[[TweenInfo.new.args]]
required = false
[TweenInfo.new.args.type]
display = "EasingStyle"
[[TweenInfo.new.args]]
required = false
[TweenInfo.new.args.type]
display = "EasingDirection"
[[TweenInfo.new.args]]
required = false
type = "number"
[[TweenInfo.new.args]]
required = false
type = "bool"
[[TweenInfo.new.args]]
required = false
type = "number"
[[UDim.new.args]]
type = "number"
[[UDim.new.args]]
type = "number"
[[UDim2.fromOffset.args]]
type = "number"
[[UDim2.fromOffset.args]]
type = "number"
[[UDim2.fromScale.args]]
type = "number"
[[UDim2.fromScale.args]]
type = "number"
[[UDim2.new.args]]
required = false
type = "any"
[[UDim2.new.args]]
required = false
type = "any"
[[UDim2.new.args]]
required = false
type = "number"
[[UDim2.new.args]]
required = false
type = "number"
[UserSettings]
args = []
[[Vector2.new.args]]
required = false
type = "number"
[[Vector2.new.args]]
required = false
type = "number"
[[Vector2int16.new.args]]
required = false
type = "number"
[[Vector2int16.new.args]]
required = false
type = "number"
[[Vector3.FromAxis.args]]
[Vector3.FromAxis.args.type]
display = "Axis"
[[Vector3.FromNormalId.args]]
[Vector3.FromNormalId.args.type]
display = "NormalId"
[[Vector3.new.args]]
required = false
type = "number"
[[Vector3.new.args]]
required = false
type = "number"
[[Vector3.new.args]]
required = false
type = "number"
[[Vector3int16.new.args]]
required = false
type = "number"
[[Vector3int16.new.args]]
required = false
type = "number"
[[Vector3int16.new.args]]
required = false
type = "number"
[[bit32.arshift.args]]
type = "number"
[[bit32.arshift.args]]
type = "number"
[[bit32.band.args]]
type = "..."
[[bit32.bnot.args]]
type = "number"
[[bit32.bor.args]]
type = "..."
[[bit32.btest.args]]
type = "..."
[[bit32.bxor.args]]
type = "..."
[[bit32.extract.args]]
type = "number"
[[bit32.extract.args]]
type = "number"
[[bit32.extract.args]]
required = false
type = "number"
[[bit32.lrotate.args]]
type = "number"
[[bit32.lrotate.args]]
type = "number"
[[bit32.lshift.args]]
type = "number"
[[bit32.lshift.args]]
type = "number"
[[bit32.replace.args]]
type = "number"
[[bit32.replace.args]]
type = "number"
[[bit32.replace.args]]
required = false
type = "number"
[[bit32.rrotate.args]]
type = "number"
[[bit32.rrotate.args]]
type = "number"
[[bit32.rshift.args]]
type = "number"
[[bit32.rshift.args]]
type = "number"
[[collectgarbage.args]]
type = ["count"]
[coroutine.isyieldable]
args = []
[debug.debug]
removed = true
[debug.getfenv]
removed = true
[debug.gethook]
removed = true
[debug.getinfo]
removed = true
[debug.getlocal]
removed = true
[debug.getmetatable]
removed = true
[debug.getregistry]
removed = true
[debug.getupvalue]
removed = true
[[debug.profilebegin.args]]
type = "string"
[debug.profileend]
args = []
[debug.setfenv]
removed = true
[debug.sethook]
removed = true
[debug.setlocal]
removed = true
[debug.setmetatable]
removed = true
[debug.setupvalue]
removed = true
[[delay.args]]
type = "number"
[[delay.args]]
type = "function"
[dofile]
removed = true
[elapsedTime]
args = []
[game]
struct = "DataModel"
[io]
removed = true
[load]
removed = true
[loadfile]
removed = true
[[math.clamp.args]]
type = "number"
[[math.clamp.args]]
type = "number"
[[math.clamp.args]]
type = "number"
[[math.log.args]]
type = "number"
[[math.log.args]]
required = false
type = "number"
[[math.noise.args]]
type = "number"
[[math.noise.args]]
required = false
type = "number"
[[math.noise.args]]
required = false
type = "number"
[[math.sign.args]]
type = "number"
[module]
removed = true
[os.clock]
removed = true
[os.execute]
removed = true
[os.exit]
removed = true
[os.getenv]
removed = true
[os.remove]
removed = true
[os.rename]
removed = true
[os.setlocale]
removed = true
[os.tmpname]
removed = true
[package]
removed = true
[plugin]
struct = "Plugin"
[[require.args]]
type = "number"
[script]
struct = "Script"
[settings]
args = []
[shared]
property = true
writable = "new-fields"
[[spawn.args]]
type = "function"
[string.dump]
removed = true
[[string.split.args]]
type = "string"
[[string.split.args]]
required = false
type = "string"
[[table.create.args]]
type = "number"
[[table.create.args]]
required = false
type = "any"
[[table.find.args]]
type = "table"
[[table.find.args]]
type = "any"
[[table.find.args]]
required = false
type = "number"
[[table.move.args]]
type = "table"
[[table.move.args]]
type = "number"
[[table.move.args]]
type = "number"
[[table.move.args]]
type = "number"
[[table.move.args]]
required = false
type = "table"
[[table.pack.args]]
type = "..."
[[table.unpack.args]]
type = "table"
[[table.unpack.args]]
required = false
type = "number"
[[table.unpack.args]]
required = false
type = "number"
[tick]
args = []
[time]
args = []
[[typeof.args]]
type = "any"
[[utf8.char.args]]
required = "utf8.char should be used with an argument despite it not throwing"
type = "number"
[[utf8.char.args]]
required = false
type = "..."
[utf8.charpattern]
property = true
[[utf8.codepoint.args]]
type = "string"
[[utf8.codepoint.args]]
required = false
type = "number"
[[utf8.codepoint.args]]
required = false
type = "number"
[[utf8.codes.args]]
type = "string"
[[utf8.graphemes.args]]
type = "string"
[[utf8.graphemes.args]]
required = false
type = "number"
[[utf8.graphemes.args]]
required = false
type = "number"
[[utf8.len.args]]
type = "string"
[[utf8.len.args]]
required = false
type = "number"
[[utf8.len.args]]
required = false
type = "number"
[[utf8.nfcnormalize.args]]
type = "string"
[[utf8.nfdnormalize.args]]
type = "string"
[[utf8.offset.args]]
type = "string"
[[utf8.offset.args]]
required = false
type = "number"
[[utf8.offset.args]]
required = false
type = "number"
[[wait.args]]
required = false
type = "number"
[[warn.args]]
type = "string"
[[warn.args]]
required = false
type = "..."
[workspace]
struct = "Workspace"
================================================
FILE: selene.toml
================================================
std = "roblox"
[config]
empty_if = { comments_count = true }
================================================
FILE: src/Chat/BubbleChat.client.lua
================================================
--[[
// FileName: BubbleChat.lua
// Written by: jeditkacheff, TheGamer101
// Description: Code for rendering bubble chat
]]
--[[ SERVICES ]]
local PlayersService = game:GetService('Players')
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ChatService = game:GetService("Chat")
local TextService = game:GetService("TextService")
--[[ END OF SERVICES ]]
local LocalPlayer = PlayersService.LocalPlayer
while LocalPlayer == nil do
PlayersService.ChildAdded:wait()
LocalPlayer = PlayersService.LocalPlayer
end
local PlayerGui = LocalPlayer:WaitForChild("PlayerGui")
local okShouldClipInGameChat, valueShouldClipInGameChat = pcall(function() return UserSettings():IsUserFeatureEnabled("UserShouldClipInGameChat") end)
local shouldClipInGameChat = okShouldClipInGameChat and valueShouldClipInGameChat
--[[ SCRIPT VARIABLES ]]
local CHAT_BUBBLE_FONT = Enum.Font.SourceSans
local CHAT_BUBBLE_FONT_SIZE = Enum.FontSize.Size24 -- if you change CHAT_BUBBLE_FONT_SIZE_INT please change this to match
local CHAT_BUBBLE_FONT_SIZE_INT = 24 -- if you change CHAT_BUBBLE_FONT_SIZE please change this to match
local CHAT_BUBBLE_LINE_HEIGHT = CHAT_BUBBLE_FONT_SIZE_INT + 10
local CHAT_BUBBLE_TAIL_HEIGHT = 14
local CHAT_BUBBLE_WIDTH_PADDING = 30
local CHAT_BUBBLE_FADE_SPEED = 1.5
local BILLBOARD_MAX_WIDTH = 400
local BILLBOARD_MAX_HEIGHT = 250 --This limits the number of bubble chats that you see above characters
local ELIPSES = "..."
local MaxChatMessageLength = 128 -- max chat message length, including null terminator and elipses.
local MaxChatMessageLengthExclusive = MaxChatMessageLength - string.len(ELIPSES) - 1
local NEAR_BUBBLE_DISTANCE = 65 --previously 45
local MAX_BUBBLE_DISTANCE = 100 --previously 80
--[[ END OF SCRIPT VARIABLES ]]
-- [[ SCRIPT ENUMS ]]
local BubbleColor = { WHITE = "dub",
BLUE = "blu",
GREEN = "gre",
RED = "red" }
--[[ END OF SCRIPT ENUMS ]]
-- This screenGui exists so that the billboardGui is not deleted when the PlayerGui is reset.
local BubbleChatScreenGui = Instance.new("ScreenGui")
BubbleChatScreenGui.Name = "BubbleChat"
BubbleChatScreenGui.ResetOnSpawn = false
BubbleChatScreenGui.Parent = PlayerGui
--[[ FUNCTIONS ]]
local function lerpLength(msg, min, max)
return min + (max-min) * math.min(string.len(msg)/75.0, 1.0)
end
local function createFifo()
local this = {}
this.data = {}
local emptyEvent = Instance.new("BindableEvent")
this.Emptied = emptyEvent.Event
function this:Size()
return #this.data
end
function this:Empty()
return this:Size() <= 0
end
function this:PopFront()
table.remove(this.data, 1)
if this:Empty() then emptyEvent:Fire() end
end
function this:Front()
return this.data[1]
end
function this:Get(index)
return this.data[index]
end
function this:PushBack(value)
table.insert(this.data, value)
end
function this:GetData()
return this.data
end
return this
end
local function createCharacterChats()
local this = {}
this.Fifo = createFifo()
this.BillboardGui = nil
return this
end
local function createMap()
local this = {}
this.data = {}
local count = 0
function this:Size()
return count
end
function this:Erase(key)
if this.data[key] then count = count - 1 end
this.data[key] = nil
end
function this:Set(key, value)
this.data[key] = value
if value then count = count + 1 end
end
function this:Get(key)
if not key then return end
if not this.data[key] then
this.data[key] = createCharacterChats()
local emptiedCon = nil
emptiedCon = this.data[key].Fifo.Emptied:connect(function()
emptiedCon:disconnect()
this:Erase(key)
end)
end
return this.data[key]
end
function this:GetData()
return this.data
end
return this
end
local function createChatLine(message, bubbleColor, isLocalPlayer)
local this = {}
function this:ComputeBubbleLifetime(msg, isSelf)
if isSelf then
return lerpLength(msg,8,15)
else
return lerpLength(msg,12,20)
end
end
this.Origin = nil
this.RenderBubble = nil
this.Message = message
this.BubbleDieDelay = this:ComputeBubbleLifetime(message, isLocalPlayer)
this.BubbleColor = bubbleColor
this.IsLocalPlayer = isLocalPlayer
return this
end
local function createPlayerChatLine(player, message, isLocalPlayer)
local this = createChatLine(message, BubbleColor.WHITE, isLocalPlayer)
if player then
this.User = player.Name
this.Origin = player.Character
end
return this
end
local function createGameChatLine(origin, message, isLocalPlayer, bubbleColor)
local this = createChatLine(message, bubbleColor, isLocalPlayer)
this.Origin = origin
return this
end
function createChatBubbleMain(filePrefix, sliceRect)
local chatBubbleMain = Instance.new("ImageLabel")
chatBubbleMain.Name = "ChatBubble"
chatBubbleMain.ScaleType = Enum.ScaleType.Slice
chatBubbleMain.SliceCenter = sliceRect
chatBubbleMain.Image = "rbxasset://textures/" .. tostring(filePrefix) .. ".png"
chatBubbleMain.BackgroundTransparency = 1
chatBubbleMain.BorderSizePixel = 0
chatBubbleMain.Size = UDim2.new(1.0, 0, 1.0, 0)
chatBubbleMain.Position = UDim2.new(0,0,0,0)
return chatBubbleMain
end
function createChatBubbleTail(position, size)
local chatBubbleTail = Instance.new("ImageLabel")
chatBubbleTail.Name = "ChatBubbleTail"
chatBubbleTail.Image = "rbxasset://textures/ui/dialog_tail.png"
chatBubbleTail.BackgroundTransparency = 1
chatBubbleTail.BorderSizePixel = 0
chatBubbleTail.Position = position
chatBubbleTail.Size = size
return chatBubbleTail
end
function createChatBubbleWithTail(filePrefix, position, size, sliceRect)
local chatBubbleMain = createChatBubbleMain(filePrefix, sliceRect)
local chatBubbleTail = createChatBubbleTail(position, size)
chatBubbleTail.Parent = chatBubbleMain
return chatBubbleMain
end
function createScaledChatBubbleWithTail(filePrefix, frameScaleSize, position, sliceRect)
local chatBubbleMain = createChatBubbleMain(filePrefix, sliceRect)
local frame = Instance.new("Frame")
frame.Name = "ChatBubbleTailFrame"
frame.BackgroundTransparency = 1
frame.SizeConstraint = Enum.SizeConstraint.RelativeXX
frame.Position = UDim2.new(0.5, 0, 1, 0)
frame.Size = UDim2.new(frameScaleSize, 0, frameScaleSize, 0)
frame.Parent = chatBubbleMain
local chatBubbleTail = createChatBubbleTail(position, UDim2.new(1,0,0.5,0))
chatBubbleTail.Parent = frame
return chatBubbleMain
end
function createChatImposter(filePrefix, dotDotDot, yOffset)
local result = Instance.new("ImageLabel")
result.Name = "DialogPlaceholder"
result.Image = "rbxasset://textures/" .. tostring(filePrefix) .. ".png"
result.BackgroundTransparency = 1
result.BorderSizePixel = 0
result.Position = UDim2.new(0, 0, -1.25, 0)
result.Size = UDim2.new(1, 0, 1, 0)
local image = Instance.new("ImageLabel")
image.Name = "DotDotDot"
image.Image = "rbxasset://textures/" .. tostring(dotDotDot) .. ".png"
image.BackgroundTransparency = 1
image.BorderSizePixel = 0
image.Position = UDim2.new(0.001, 0, yOffset, 0)
image.Size = UDim2.new(1, 0, 0.7, 0)
image.Parent = result
return result
end
local this = {}
this.ChatBubble = {}
this.ChatBubbleWithTail = {}
this.ScalingChatBubbleWithTail = {}
this.CharacterSortedMsg = createMap()
-- init chat bubble tables
local function initChatBubbleType(chatBubbleType, fileName, imposterFileName, isInset, sliceRect)
this.ChatBubble[chatBubbleType] = createChatBubbleMain(fileName, sliceRect)
this.ChatBubbleWithTail[chatBubbleType] = createChatBubbleWithTail(fileName, UDim2.new(0.5, -CHAT_BUBBLE_TAIL_HEIGHT, 1, isInset and -1 or 0), UDim2.new(0, 30, 0, CHAT_BUBBLE_TAIL_HEIGHT), sliceRect)
this.ScalingChatBubbleWithTail[chatBubbleType] = createScaledChatBubbleWithTail(fileName, 0.5, UDim2.new(-0.5, 0, 0, isInset and -1 or 0), sliceRect)
end
initChatBubbleType(BubbleColor.WHITE, "ui/dialog_white", "ui/chatBubble_white_notify_bkg", false, Rect.new(5,5,15,15))
initChatBubbleType(BubbleColor.BLUE, "ui/dialog_blue", "ui/chatBubble_blue_notify_bkg", true, Rect.new(7,7,33,33))
initChatBubbleType(BubbleColor.RED, "ui/dialog_red", "ui/chatBubble_red_notify_bkg", true, Rect.new(7,7,33,33))
initChatBubbleType(BubbleColor.GREEN, "ui/dialog_green", "ui/chatBubble_green_notify_bkg", true, Rect.new(7,7,33,33))
function this:SanitizeChatLine(msg)
if string.len(msg) > MaxChatMessageLengthExclusive then
return string.sub(msg, 1, MaxChatMessageLengthExclusive + string.len(ELIPSES))
else
return msg
end
end
local function createBillboardInstance(adornee)
local billboardGui = Instance.new("BillboardGui")
billboardGui.Adornee = adornee
billboardGui.Size = UDim2.new(0,BILLBOARD_MAX_WIDTH,0,BILLBOARD_MAX_HEIGHT)
billboardGui.StudsOffset = Vector3.new(0, 1.5, 2)
billboardGui.Parent = BubbleChatScreenGui
local billboardFrame = Instance.new("Frame")
billboardFrame.Name = "BillboardFrame"
billboardFrame.Size = UDim2.new(1,0,1,0)
billboardFrame.Position = UDim2.new(0,0,-0.5,0)
billboardFrame.BackgroundTransparency = 1
billboardFrame.Parent = billboardGui
local billboardChildRemovedCon = nil
billboardChildRemovedCon = billboardFrame.ChildRemoved:connect(function()
if #billboardFrame:GetChildren() <= 1 then
billboardChildRemovedCon:disconnect()
billboardGui:Destroy()
end
end)
this:CreateSmallTalkBubble(BubbleColor.WHITE).Parent = billboardFrame
return billboardGui
end
function this:CreateBillboardGuiHelper(instance, onlyCharacter)
if instance and not this.CharacterSortedMsg:Get(instance)["BillboardGui"] then
if not onlyCharacter then
if instance:IsA("BasePart") then
-- Create a new billboardGui object attached to this player
local billboardGui = createBillboardInstance(instance)
this.CharacterSortedMsg:Get(instance)["BillboardGui"] = billboardGui
return
end
end
if instance:IsA("Model") then
local head = instance:FindFirstChild("Head")
if head and head:IsA("BasePart") then
-- Create a new billboardGui object attached to this player
local billboardGui = createBillboardInstance(head)
this.CharacterSortedMsg:Get(instance)["BillboardGui"] = billboardGui
end
end
end
end
local function distanceToBubbleOrigin(origin)
if not origin then return 100000 end
return (origin.Position - game.Workspace.CurrentCamera.CoordinateFrame.p).magnitude
end
local function isPartOfLocalPlayer(adornee)
if adornee and PlayersService.LocalPlayer.Character then
return adornee:IsDescendantOf(PlayersService.LocalPlayer.Character)
end
end
function this:SetBillboardLODNear(billboardGui)
local isLocalPlayer = isPartOfLocalPlayer(billboardGui.Adornee)
billboardGui.Size = UDim2.new(0, BILLBOARD_MAX_WIDTH, 0, BILLBOARD_MAX_HEIGHT)
billboardGui.StudsOffset = Vector3.new(0, isLocalPlayer and 1.5 or 2.5, isLocalPlayer and 2 or 0.1)
billboardGui.Enabled = true
local billChildren = billboardGui.BillboardFrame:GetChildren()
for i = 1, #billChildren do
billChildren[i].Visible = true
end
billboardGui.BillboardFrame.SmallTalkBubble.Visible = false
end
function this:SetBillboardLODDistant(billboardGui)
local isLocalPlayer = isPartOfLocalPlayer(billboardGui.Adornee)
billboardGui.Size = UDim2.new(4,0,3,0)
billboardGui.StudsOffset = Vector3.new(0, 3, isLocalPlayer and 2 or 0.1)
billboardGui.Enabled = true
local billChildren = billboardGui.BillboardFrame:GetChildren()
for i = 1, #billChildren do
billChildren[i].Visible = false
end
billboardGui.BillboardFrame.SmallTalkBubble.Visible = true
end
function this:SetBillboardLODVeryFar(billboardGui)
billboardGui.Enabled = false
end
function this:SetBillboardGuiLOD(billboardGui, origin)
if not origin then return end
if origin:IsA("Model") then
local head = origin:FindFirstChild("Head")
if not head then origin = origin.PrimaryPart
else origin = head end
end
local bubbleDistance = distanceToBubbleOrigin(origin)
if bubbleDistance < NEAR_BUBBLE_DISTANCE then
this:SetBillboardLODNear(billboardGui)
elseif bubbleDistance >= NEAR_BUBBLE_DISTANCE and bubbleDistance < MAX_BUBBLE_DISTANCE then
this:SetBillboardLODDistant(billboardGui)
else
this:SetBillboardLODVeryFar(billboardGui)
end
end
function this:CameraCFrameChanged()
for index, value in pairs(this.CharacterSortedMsg:GetData()) do
local playerBillboardGui = value["BillboardGui"]
if playerBillboardGui then this:SetBillboardGuiLOD(playerBillboardGui, index) end
end
end
function this:CreateBubbleText(message)
local bubbleText = Instance.new("TextLabel")
bubbleText.Name = "BubbleText"
bubbleText.BackgroundTransparency = 1
bubbleText.Position = UDim2.new(0,CHAT_BUBBLE_WIDTH_PADDING/2,0,0)
bubbleText.Size = UDim2.new(1,-CHAT_BUBBLE_WIDTH_PADDING,1,0)
bubbleText.Font = CHAT_BUBBLE_FONT
if shouldClipInGameChat then
bubbleText.ClipsDescendants = true
end
bubbleText.TextWrapped = true
bubbleText.FontSize = CHAT_BUBBLE_FONT_SIZE
bubbleText.Text = message
bubbleText.Visible = false
bubbleText.AutoLocalize = false
return bubbleText
end
function this:CreateSmallTalkBubble(chatBubbleType)
local smallTalkBubble = this.ScalingChatBubbleWithTail[chatBubbleType]:Clone()
smallTalkBubble.Name = "SmallTalkBubble"
smallTalkBubble.AnchorPoint = Vector2.new(0, 0.5)
smallTalkBubble.Position = UDim2.new(0,0,0.5,0)
smallTalkBubble.Visible = false
local text = this:CreateBubbleText("...")
text.TextScaled = true
text.TextWrapped = false
text.Visible = true
text.Parent = smallTalkBubble
return smallTalkBubble
end
function this:UpdateChatLinesForOrigin(origin, currentBubbleYPos)
local bubbleQueue = this.CharacterSortedMsg:Get(origin).Fifo
local bubbleQueueSize = bubbleQueue:Size()
local bubbleQueueData = bubbleQueue:GetData()
if #bubbleQueueData <= 1 then return end
for index = (#bubbleQueueData - 1), 1, -1 do
local value = bubbleQueueData[index]
local bubble = value.RenderBubble
if not bubble then return end
local bubblePos = bubbleQueueSize - index + 1
if bubblePos > 1 then
local tail = bubble:FindFirstChild("ChatBubbleTail")
if tail then tail:Destroy() end
local bubbleText = bubble:FindFirstChild("BubbleText")
if bubbleText then bubbleText.TextTransparency = 0.5 end
end
local udimValue = UDim2.new( bubble.Position.X.Scale, bubble.Position.X.Offset,
1, currentBubbleYPos - bubble.Size.Y.Offset - CHAT_BUBBLE_TAIL_HEIGHT )
bubble:TweenPosition(udimValue, Enum.EasingDirection.Out, Enum.EasingStyle.Bounce, 0.1, true)
currentBubbleYPos = currentBubbleYPos - bubble.Size.Y.Offset - CHAT_BUBBLE_TAIL_HEIGHT
end
end
function this:DestroyBubble(bubbleQueue, bubbleToDestroy)
if not bubbleQueue then return end
if bubbleQueue:Empty() then return end
local bubble = bubbleQueue:Front().RenderBubble
if not bubble then
bubbleQueue:PopFront()
return
end
spawn(function()
while bubbleQueue:Front().RenderBubble ~= bubbleToDestroy do
wait()
end
bubble = bubbleQueue:Front().RenderBubble
local timeBetween = 0
local bubbleText = bubble:FindFirstChild("BubbleText")
local bubbleTail = bubble:FindFirstChild("ChatBubbleTail")
while bubble and bubble.ImageTransparency < 1 do
timeBetween = wait()
if bubble then
local fadeAmount = timeBetween * CHAT_BUBBLE_FADE_SPEED
bubble.ImageTransparency = bubble.ImageTransparency + fadeAmount
if bubbleText then bubbleText.TextTransparency = bubbleText.TextTransparency + fadeAmount end
if bubbleTail then bubbleTail.ImageTransparency = bubbleTail.ImageTransparency + fadeAmount end
end
end
if bubble then
bubble:Destroy()
bubbleQueue:PopFront()
end
end)
end
function this:CreateChatLineRender(instance, line, onlyCharacter, fifo)
if not instance then return end
if not this.CharacterSortedMsg:Get(instance)["BillboardGui"] then
this:CreateBillboardGuiHelper(instance, onlyCharacter)
end
local billboardGui = this.CharacterSortedMsg:Get(instance)["BillboardGui"]
if billboardGui then
local chatBubbleRender = this.ChatBubbleWithTail[line.BubbleColor]:Clone()
chatBubbleRender.Visible = false
local bubbleText = this:CreateBubbleText(line.Message)
bubbleText.Parent = chatBubbleRender
chatBubbleRender.Parent = billboardGui.BillboardFrame
line.RenderBubble = chatBubbleRender
local currentTextBounds = TextService:GetTextSize(
bubbleText.Text, CHAT_BUBBLE_FONT_SIZE_INT, CHAT_BUBBLE_FONT,
Vector2.new(BILLBOARD_MAX_WIDTH, BILLBOARD_MAX_HEIGHT))
local bubbleWidthScale = math.max((currentTextBounds.X + CHAT_BUBBLE_WIDTH_PADDING)/BILLBOARD_MAX_WIDTH, 0.1)
local numOflines = (currentTextBounds.Y/CHAT_BUBBLE_FONT_SIZE_INT)
-- prep chat bubble for tween
chatBubbleRender.Size = UDim2.new(0,0,0,0)
chatBubbleRender.Position = UDim2.new(0.5,0,1,0)
local newChatBubbleOffsetSizeY = numOflines * CHAT_BUBBLE_LINE_HEIGHT
chatBubbleRender:TweenSizeAndPosition(UDim2.new(bubbleWidthScale, 0, 0, newChatBubbleOffsetSizeY),
UDim2.new( (1-bubbleWidthScale)/2, 0, 1, -newChatBubbleOffsetSizeY),
Enum.EasingDirection.Out, Enum.EasingStyle.Elastic, 0.1, true,
function() bubbleText.Visible = true end)
-- todo: remove when over max bubbles
this:SetBillboardGuiLOD(billboardGui, line.Origin)
this:UpdateChatLinesForOrigin(line.Origin, -newChatBubbleOffsetSizeY)
delay(line.BubbleDieDelay, function()
this:DestroyBubble(fifo, chatBubbleRender)
end)
end
end
function this:OnPlayerChatMessage(sourcePlayer, message, targetPlayer)
if not this:BubbleChatEnabled() then return end
local localPlayer = PlayersService.LocalPlayer
local fromOthers = localPlayer ~= nil and sourcePlayer ~= localPlayer
local safeMessage = this:SanitizeChatLine(message)
local line = createPlayerChatLine(sourcePlayer, safeMessage, not fromOthers)
if sourcePlayer and line.Origin then
local fifo = this.CharacterSortedMsg:Get(line.Origin).Fifo
fifo:PushBack(line)
--Game chat (badges) won't show up here
this:CreateChatLineRender(sourcePlayer.Character, line, true, fifo)
end
end
function this:OnGameChatMessage(origin, message, color)
local localPlayer = PlayersService.LocalPlayer
local fromOthers = localPlayer ~= nil and (localPlayer.Character ~= origin)
local bubbleColor = BubbleColor.WHITE
if color == Enum.ChatColor.Blue then bubbleColor = BubbleColor.BLUE
elseif color == Enum.ChatColor.Green then bubbleColor = BubbleColor.GREEN
elseif color == Enum.ChatColor.Red then bubbleColor = BubbleColor.RED end
local safeMessage = this:SanitizeChatLine(message)
local line = createGameChatLine(origin, safeMessage, not fromOthers, bubbleColor)
this.CharacterSortedMsg:Get(line.Origin).Fifo:PushBack(line)
this:CreateChatLineRender(origin, line, false, this.CharacterSortedMsg:Get(line.Origin).Fifo)
end
function this:BubbleChatEnabled()
local clientChatModules = ChatService:FindFirstChild("ClientChatModules")
if clientChatModules then
local chatSettings = clientChatModules:FindFirstChild("ChatSettings")
if chatSettings then
local chatSettings = require(chatSettings)
if chatSettings.BubbleChatEnabled ~= nil then
return chatSettings.BubbleChatEnabled
end
end
end
return PlayersService.BubbleChat
end
function this:ShowOwnFilteredMessage()
local clientChatModules = ChatService:FindFirstChild("ClientChatModules")
if clientChatModules then
local chatSettings = clientChatModules:FindFirstChild("ChatSettings")
if chatSettings then
chatSettings = require(chatSettings)
return chatSettings.ShowUserOwnFilteredMessage
end
end
return false
end
function findPlayer(playerName)
for i,v in pairs(PlayersService:GetPlayers()) do
if v.Name == playerName then
return v
end
end
end
ChatService.Chatted:connect(function(origin, message, color) this:OnGameChatMessage(origin, message, color) end)
local cameraChangedCon = nil
if game.Workspace.CurrentCamera then
cameraChangedCon = game.Workspace.CurrentCamera:GetPropertyChangedSignal("CFrame"):connect(function(prop) this:CameraCFrameChanged() end)
end
game.Workspace.Changed:connect(function(prop)
if prop == "CurrentCamera" then
if cameraChangedCon then cameraChangedCon:disconnect() end
if game.Workspace.CurrentCamera then
cameraChangedCon = game.Workspace.CurrentCamera:GetPropertyChangedSignal("CFrame"):connect(function(prop) this:CameraCFrameChanged() end)
end
end
end)
local AllowedMessageTypes = nil
function getAllowedMessageTypes()
if AllowedMessageTypes then
return AllowedMessageTypes
end
local clientChatModules = ChatService:FindFirstChild("ClientChatModules")
if clientChatModules then
local chatSettings = clientChatModules:FindFirstChild("ChatSettings")
if chatSettings then
chatSettings = require(chatSettings)
if chatSettings.BubbleChatMessageTypes then
AllowedMessageTypes = chatSettings.BubbleChatMessageTypes
return AllowedMessageTypes
end
end
local chatConstants = clientChatModules:FindFirstChild("ChatConstants")
if chatConstants then
chatConstants = require(chatConstants)
AllowedMessageTypes = {chatConstants.MessageTypeDefault, chatConstants.MessageTypeWhisper}
end
return AllowedMessageTypes
end
return {"Message", "Whisper"}
end
function checkAllowedMessageType(messageData)
local allowedMessageTypes = getAllowedMessageTypes()
for i = 1, #allowedMessageTypes do
if allowedMessageTypes[i] == messageData.MessageType then
return true
end
end
return false
end
local ChatEvents = ReplicatedStorage:WaitForChild("DefaultChatSystemChatEvents")
local OnMessageDoneFiltering = ChatEvents:WaitForChild("OnMessageDoneFiltering")
local OnNewMessage = ChatEvents:WaitForChild("OnNewMessage")
OnNewMessage.OnClientEvent:connect(function(messageData, channelName)
if not checkAllowedMessageType(messageData) then
return
end
local sender = findPlayer(messageData.FromSpeaker)
if not sender then
return
end
if not messageData.IsFiltered or messageData.FromSpeaker == LocalPlayer.Name then
if messageData.FromSpeaker ~= LocalPlayer.Name or this:ShowOwnFilteredMessage() then
return
end
end
this:OnPlayerChatMessage(sender, messageData.Message, nil)
end)
OnMessageDoneFiltering.OnClientEvent:connect(function(messageData, channelName)
if not checkAllowedMessageType(messageData) then
return
end
local sender = findPlayer(messageData.FromSpeaker)
if not sender then
return
end
if messageData.FromSpeaker == LocalPlayer.Name and not this:ShowOwnFilteredMessage() then
return
end
this:OnPlayerChatMessage(sender, messageData.Message, nil)
end)
================================================
FILE: src/Chat/ChatModules/ChatCommandsTeller.lua
================================================
-- // FileName: ChatCommandsTeller.lua
-- // Written by: Xsitsu
-- // Description: Module that provides information on default chat commands to players.
local Chat = game:GetService("Chat")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = { Get = function(key,default) return default end } end
local function Run(ChatService)
local function ShowJoinAndLeaveCommands()
if ChatSettings.ShowJoinAndLeaveHelpText ~= nil then
return ChatSettings.ShowJoinAndLeaveHelpText
end
return false
end
local function ProcessCommandsFunction(fromSpeaker, message, channel)
if (message:lower() == "/?" or message:lower() == "/help") then
local speaker = ChatService:GetSpeaker(fromSpeaker)
--[[
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_Desc","Vesteria chat commands:"), channel)
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_MeCommand","/me : roleplaying command for doing actions."), channel)
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_WhisperCommand","/whisper or /w : open private message channel with speaker."), channel)
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_MuteCommand","/mute : mute a speaker."), channel)
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_UnMuteCommand","/unmute : unmute a speaker."), channel)
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_ToggleCommand","/toggle : toggles chat tags or chat color."), channel)
]]
speaker:SendSystemMessage("Vesteria chat commands:", channel)
speaker:SendSystemMessage(" /me :: roleplaying command for doing actions.", channel)
speaker:SendSystemMessage(" /whisper (/w) :: whisper a private message to a player.", channel)
speaker:SendSystemMessage(" /mute :: stop seeing chats from a player.", channel)
speaker:SendSystemMessage(" /unmute :: unmute a muted player.", channel)
speaker:SendSystemMessage(" /party (/p) :: send a message to party members.", channel)
speaker:SendSystemMessage(" /invite (/i) :: invite a player to your party.", channel)
speaker:SendSystemMessage(" /duel (/d) :: challenge a player to a duel.", channel)
speaker:SendSystemMessage(" /trade (/i) :: request a trade with a player.", channel)
local player = speaker:GetPlayer()
if player and player.Team then
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_TeamCommand","/team or /t : send a team chat to players on your team."), channel)
end
return true
end
return false
end
ChatService:RegisterProcessCommandsFunction("chat_commands_inquiry", ProcessCommandsFunction, ChatConstants.StandardPriority)
if ChatSettings.GeneralChannelName then
local allChannel = ChatService:GetChannel(ChatSettings.GeneralChannelName)
if (allChannel) then
-- allChannel.WelcomeMessage = ""
end
end
end
return Run
================================================
FILE: src/Chat/ChatModules/ChatFloodDetector.lua
================================================
-- // FileName: ChatFloodDetector.lua
-- // Written by: Xsitsu
-- // Description: Module that limits the number of messages a speaker can send in a given period of time.
local Chat = game:GetService("Chat")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local doFloodCheckByChannel = true
local informSpeakersOfWaitTimes = true
local chatBotsBypassFloodCheck = true
local numberMessagesAllowed = 7
local decayTimePeriod = 15
local floodCheckTable = {}
local whitelistedSpeakers = {}
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
local function EnterTimeIntoLog(tbl)
table.insert(tbl, tick() + decayTimePeriod)
end
local function Run(ChatService)
local function FloodDetectionProcessCommandsFunction(speakerName, message, channel)
if (whitelistedSpeakers[speakerName]) then return false end
local speakerObj = ChatService:GetSpeaker(speakerName)
if (not speakerObj) then return false end
if (chatBotsBypassFloodCheck and not speakerObj:GetPlayer()) then return false end
if (not floodCheckTable[speakerName]) then
floodCheckTable[speakerName] = {}
end
local t = nil
if (doFloodCheckByChannel) then
if (not floodCheckTable[speakerName][channel]) then
floodCheckTable[speakerName][channel] = {}
end
t = floodCheckTable[speakerName][channel]
else
t = floodCheckTable[speakerName]
end
local now = tick()
while (#t > 0 and t[1] < now) do
table.remove(t, 1)
end
if (#t < numberMessagesAllowed) then
EnterTimeIntoLog(t)
return false
else
local timeDiff = math.ceil(t[1] - now)
if (informSpeakersOfWaitTimes) then
local msg = string.gsub(
ChatLocalization:Get(
"GameChat_ChatFloodDetector_MessageDisplaySeconds",
string.format("You must wait %d %s before sending another message!", timeDiff, (timeDiff > 1) and "seconds" or "second")
),
"{RBX_NUMBER}",
tostring(timeDiff)
)
speakerObj:SendSystemMessage(msg, channel)
else
speakerObj:SendSystemMessage(
ChatLocalization:Get(
"GameChat_ChatFloodDetector_Message",
"You must wait before sending another message!"
)
,channel)
end
return true
end
end
ChatService:RegisterProcessCommandsFunction("flood_detection", FloodDetectionProcessCommandsFunction, ChatConstants.LowPriority)
ChatService.SpeakerRemoved:connect(function(speakerName)
floodCheckTable[speakerName] = nil
end)
end
return Run
================================================
FILE: src/Chat/ChatModules/ChatMessageValidator.lua
================================================
-- // FileName: ChatMessageValidator.lua
-- // Written by: TheGamer101
-- // Description: Validate things such as no disallowed whitespace and chat message length on the server.
local Chat = game:GetService("Chat")
local RunService = game:GetService("RunService")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
local DISALLOWED_WHITESPACE = {"\n", "\r", "\t", "\v", "\f"}
if ChatSettings.DisallowedWhiteSpace then
DISALLOWED_WHITESPACE = ChatSettings.DisallowedWhiteSpace
end
local function Run(ChatService)
local function CanUserChat(playerObj)
if RunService:IsStudio() then
return true
end
local success, canChat = pcall(function()
return Chat:CanUserChatAsync(playerObj.UserId)
end)
return success and canChat
end
local function ValidateChatFunction(speakerName, message, channel)
local speakerObj = ChatService:GetSpeaker(speakerName)
local playerObj = speakerObj:GetPlayer()
if not speakerObj then return false end
if not playerObj then return false end
if not RunService:IsStudio() and playerObj.UserId < 1 then
return true
end
if not CanUserChat(playerObj) then
speakerObj:SendSystemMessage(ChatLocalization:Get("GameChat_ChatMessageValidator_SettingsError","Your chat settings prevent you from sending messages."), channel)
return true
end
if message:len() > ChatSettings.MaximumMessageLength + 1 then
speakerObj:SendSystemMessage(ChatLocalization:Get("GameChat_ChatMessageValidator_MaxLengthError","Your message exceeds the maximum message length."), channel)
return true
end
for i = 1, #DISALLOWED_WHITESPACE do
if string.find(message, DISALLOWED_WHITESPACE[i]) then
speakerObj:SendSystemMessage(ChatLocalization:Get("GameChat_ChatMessageValidator_WhitespaceError","Your message contains whitespace that is not allowed."), channel)
return true
end
end
return false
end
ChatService:RegisterProcessCommandsFunction("message_validation", ValidateChatFunction, ChatSettings.LowPriority)
end
return Run
================================================
FILE: src/Chat/ChatModules/CustomCommands.lua
================================================
function matches(newText, pattern)
newText = string.lower(newText)
pattern = string.lower(pattern)
if string.sub(newText, 1, pattern:len()) == pattern then
return true
end
end
function isCommand(newText)
if matches(newText, "/invite ") or matches(newText, "/i ") then
return "invite"
elseif matches(newText, "/duel ") or matches(newText, "/d ") then
return "duel"
elseif matches(newText, "/trade ") or matches(newText, "/t ") then
return "trade"
end
end
local function Run(ChatService)
local function commandRecieved(speakerName, message, channelName)
local speaker = ChatService:GetSpeaker(speakerName)
local player = speaker:GetPlayer()
if isCommand(message) then
local targetPlayerName = string.gsub(message, "^[^%s]+ ", "")
end
return false
end
ChatService:RegisterProcessCommandsFunction("customCommands", commandRecieved)
end
return Run
================================================
FILE: src/Chat/ChatModules/ExtraDataInitializer.lua
================================================
-- // FileName: ExtraDataInitializer.lua
-- // Written by: Xsitsu
-- // Description: Module that sets some basic ExtraData such as name color, and chat color.
--
-- // Editted by: Nicholas_Foreman
-- // ^ I'm not a Roblox admin nor an intern. I'm just a bored guy who has OCD and likes a neat chat. :)
local stackTags = false
--[[
Value Effect Example
true A player can have multiple tags [Owner] [Admin] [God] [Nicholas_Foreman]: hi
false A player can only have one tag [Owner] [Nicholas_Foreman]: hi
--]]
local groupRankComparison = ">="
--[[
Value Effect
">=" If the player's rank is greater than or equal to the GroupId Rank
">" If the player's rank is greater than to the GroupId Rank
"<" If the player's rank is less than the GroupId Rank
">=" If the player's rank is less than or equal to the GroupId Rank
--]]
--[[
This is where you put all possible tags and chat colors. So, the index, like in my examples, is the name of the tag that you will later refer to.
Priority is the order in which the tags are added. I have random values for examples.
The higher the number, the more it will be seen first.
Example given what is below, if you have all the tags:
[Owner] [Developer] [Moderator] [Tester] [VIP] [Roblox Staff] [Roblox Star] [Roblox QA] [Roblox DevForum] [Nicholas_Foreman]: hi
Let's just say, it's reaaaaally long.
--]]
local possibleTags = {
["Developer"] = {
TagText = "Developer",
TagColor = Color3.fromRGB(255, 174, 11),
Priority = 9,
},
["Moderator"] = {
TagText = "Moderator",
TagColor = Color3.fromRGB(0, 170, 0),
Priority = 7,
},
["Contributor"] = {
TagText = "Contributor",
TagColor = Color3.fromRGB(255, 0, 100),
Priority = 8,
},
["Tester"] = {
TagText = "Tester",
TagColor = Color3.fromRGB(225, 203, 255),
Priority = 7,
},
["Roblox Staff"] = {
TagText = "Roblox",
TagColor = Color3.fromRGB(255, 85, 85),
Priority = 6,
},
["Roblox Star"] = {
TagText = "Star",
TagColor = Color3.fromRGB(255, 230, 101),
Priority = 5,
},
["Alpha"] = {
TagText = "α",
TagColor = Color3.fromRGB(234, 55, 58),
Priority = 2,
},
["Beta"] = {
TagText = "β",
TagColor = Color3.fromRGB(140,140,140),
Priority = 1,
}
}
local possibleChatColors = {
["Dev Orange"] = {
ChatColor = Color3.fromRGB(255, 154, 103),
Priority = 5,
},
["Admin Yellow"] = {
ChatColor = Color3.fromRGB(255, 215, 0),
Priority = 4,
},
["Contributor Blue"] = {
ChatColor = Color3.fromRGB(156, 247, 255),
Priority = 3,
},
["Tester Purple"] = {
ChatColor = Color3.fromRGB(225, 203, 255),
Priority = 2,
},
["Intern Blue"] = {
ChatColor = Color3.fromRGB(175, 221, 255),
Priority = 1,
}
}
-- Set the value of Chat Colors
local SpecialChatColors = {
Gamepasses = {
--[[{
--- VIP Gamepass
GamepassId = 718077,
ChatColor = possibleChatColors["Admin Yellow"],
},]]
},
Badges = {
--[[{
--- Tester Badge
BadgeId = 336132652,
ChatColor = possibleChatColors["Admin Yellow"],
},]]
},
Teams = {
--[[{
--- Example Team
Team = "Example",
ChatColor = possibleChatColors["Admin Yellow"],
},]]
},
Groups = {
--"Contributor Blue"
{
---Devs
GroupId = 4238824,
Rank = 254,
ChatColor = possibleChatColors["Dev Orange"],
},
{
---Contributors
GroupId = 4238824,
Rank = 5,
ChatColor = possibleChatColors["Contributor Blue"],
},
{
---Contributors
GroupId = 4238824,
Rank = 3,
ChatColor = possibleChatColors["Tester Purple"],
},
{
--- Roblox Admins group
GroupId = 1200769,
ChatColor = possibleChatColors["Admin Yellow"],
},
{
--- Roblox Interns group
GroupId = 2868472,
Rank = 100,
ChatColor = possibleChatColors["Intern Blue"],
},
{
--- Roblox Stars group
GroupId = 4199740,
ChatColor = possibleChatColors["Intern Blue"],
},
},
Players = {
{
--- Player Id -1, useful for testing.
UserId = -1,
ChatColor = possibleChatColors["Admin Yellow"],
},
}
}
-- Here is where you refer to the actual tags that you listed above. This saves time and simplicity. Also helps with mass editting. :?
local SpecialTags = {
Gamepasses = {
--[[{
--- VIP Gamepass
GamepassId = 718077,
Tags = {"VIP"}
},]]
},
Badges = {
{
--- Tester Badge?
BadgeId = 2124445897,
Tags = {"Alpha"}
},
{
--- Tester Badge?
BadgeId = 2124469268,
Tags = {"Beta"}
},
},
Teams = {
--[[{
--- Example Team
Team = "Example",
Tags = {"Owner"}
},]]
},
Groups = {
{
--- Roblox Admins group
GroupId = 1200769,
Tags = {"Roblox Staff"}
},
{
--- Roblox Stars group
GroupId = 4199740,
Tags = {"Roblox Star"}
},
{
---Devs
GroupId = 4238824,
Rank = 254,
Tags = {"Developer"}
},
{
---Contributors
GroupId = 4238824,
Rank = 5,
Tags = {"Contributor"}
},
{
---Tester
GroupId = 4238824,
Rank = 3,
Tags = {"Tester"}
},
},
Players = {
{
--- Nicholas_Foreman, editor and adder of chat tags.
UserId = 9221415,
Tags = {"Contributor"}
},
{
--- Player Id -1, useful for testing.
UserId = -1,
Tags = {"Contributor", "VIP"}
},
}
}
local function MakeIsInGroup(groupId, requiredRank)
assert(type(requiredRank) == "nil" or type(requiredRank) == "number", "requiredRank must be a number or nil")
return function(player)
if player and player.UserId then
local userId = player.UserId
local inGroup = false
local success, err = pcall(function() -- Many things can error is the IsInGroup check
if requiredRank then
if groupRankComparison == ">=" then
inGroup = player:GetRankInGroup(groupId) >= requiredRank
elseif groupRankComparison == ">" then
inGroup = player:GetRankInGroup(groupId) > requiredRank
elseif groupRankComparison == "<" then
inGroup = player:GetRankInGroup(groupId) < requiredRank
elseif groupRankComparison == "<=" then
inGroup = player:GetRankInGroup(groupId) <= requiredRank
end
else
inGroup = player:IsInGroup(groupId)
end
end)
if not success and err then
print("Error checking in group: " ..err)
end
return inGroup
end
return false
end
end
local function ConstructIsInGroups()
if SpecialChatColors.Groups then
for _, group in pairs(SpecialChatColors.Groups) do
group.IsInGroup = MakeIsInGroup(group.GroupId, group.Rank)
end
end
if SpecialTags.Groups then
for _, group in pairs(SpecialTags.Groups) do
group.IsInGroup = MakeIsInGroup(group.GroupId, group.Rank)
end
end
end
ConstructIsInGroups()
local Players = game:GetService("Players")
local devColors = {
["berezaa"] = {Color3.fromRGB(255, 0, 64), Color3.fromRGB(255, 130, 147)},
["lmaginationBurst"] = {Color3.fromRGB(106, 165, 125), Color3.fromRGB(165, 112, 96)},
["Rocky28447"] = {Color3.fromRGB(0, 255, 255), Color3.fromRGB(240, 150, 255)},
["Legitmanp"] = {Color3.fromRGB( 33, 94, 253), Color3.fromRGB(103, 192, 253)},
["10_MinuteAdRevenue"] = {Color3.fromRGB(0, 255, 149), Color3.fromRGB(0, 244, 214)},
[""] = {Color3.fromRGB(0, 0, 0), Color3.fromRGB(0, 0, 0)},
[""] = {Color3.fromRGB(0, 0, 0), Color3.fromRGB(0, 0, 0)},
[""] = {Color3.fromRGB(0, 0, 0), Color3.fromRGB(0, 0, 0)},
[""] = {Color3.fromRGB(0, 0, 0), Color3.fromRGB(0, 0, 0)},
}
--[[
THIS IS A MESS. IF YOU WANT TO CLEAN IT UP, GO FOR IT. SORRY FOR ALL OF YOU SCRIPTERS OUT THERE. LMAO.
--]]
function GetSpecialChatColor(speakerName)
if devColors[speakerName] then
return devColors[speakerName][2]
end
local chatColor = Color3.new(1,1,1)
local currentPriority = 0
if SpecialChatColors.Players then
local playerFromSpeaker = Players:FindFirstChild(speakerName)
if playerFromSpeaker then
for _, player in pairs(SpecialChatColors.Players) do
if playerFromSpeaker.UserId == player.UserId then
if player["ChatColor"]["Priority"] > currentPriority then
currentPriority = player["ChatColor"]["Priority"]
chatColor = player["ChatColor"]["ChatColor"]
end
end
end
end
end
if SpecialChatColors.Groups then
for _, group in pairs(SpecialChatColors.Groups) do
if group.IsInGroup(Players:FindFirstChild(speakerName)) then
if group["ChatColor"]["Priority"] > currentPriority then
currentPriority = group["ChatColor"]["Priority"]
chatColor = group["ChatColor"]["ChatColor"]
end
end
end
end
if SpecialChatColors.Teams then
local playerFromSpeaker = Players:FindFirstChild(speakerName)
if playerFromSpeaker then
for _, team in pairs(SpecialChatColors.Teams) do
local actualTeam = game:GetService("Teams"):FindFirstChild(team.Team)
if playerFromSpeaker.Team == actualTeam then
if team["ChatColor"]["Priority"] > currentPriority then
currentPriority = team["ChatColor"]["Priority"]
chatColor = team["ChatColor"]["ChatColor"]
end
end
end
end
end
if SpecialChatColors.Gamepasses then
for _, gamepass in pairs(SpecialChatColors.Gamepasses) do
local playerFromSpeaker = Players:FindFirstChild(speakerName)
if game:GetService("MarketplaceService"):UserOwnsGamePassAsync(playerFromSpeaker.UserId, gamepass.GamepassId) then
if gamepass["ChatColor"]["Priority"] > currentPriority then
currentPriority = gamepass["ChatColor"]["Priority"]
chatColor = gamepass["ChatColor"]["ChatColor"]
end
end
end
end
if SpecialChatColors.Badges then
for _, badge in pairs(SpecialChatColors.Badges) do
local playerFromSpeaker = Players:FindFirstChild(speakerName)
if game:GetService("BadgeService"):UserHasBadgeAsync(playerFromSpeaker.UserId, badge.BadgeId) then
if badge["ChatColor"]["Priority"] > currentPriority then
currentPriority = badge["ChatColor"]["Priority"]
chatColor = badge["ChatColor"]["ChatColor"]
end
end
end
end
return chatColor
end
function GetSpecialTags(speakerName)
if devColors[speakerName] then
local tag = {
TagText = "Developer",
TagColor = devColors[speakerName][1],
Priority = 10,
}
return {tag}
end
local tags = {}
local currentPriority = 0
if SpecialTags.Players then
local playerFromSpeaker = Players:FindFirstChild(speakerName)
if playerFromSpeaker then
for _, player in pairs(SpecialTags.Players) do
if playerFromSpeaker.UserId == player.UserId then
for possibleTagName,possibleTagValue in pairs(possibleTags) do
for i,playerTagName in pairs(player.Tags) do
if playerTagName == possibleTagName then
if stackTags then
table.insert(tags,possibleTagValue)
if possibleTagValue["Priority"] > currentPriority then
currentPriority = possibleTagValue["Priority"]
end
else
if possibleTagValue["Priority"] > currentPriority then
tags = {possibleTagValue}
currentPriority = possibleTagValue["Priority"]
end
end
end
end
end
end
end
end
end
if SpecialTags.Groups then
for _, group in pairs(SpecialTags.Groups) do
if group.IsInGroup(Players:FindFirstChild(speakerName)) then
for possibleTagName,possibleTagValue in pairs(possibleTags) do
for i,groupTagName in pairs(group.Tags) do
if groupTagName == possibleTagName then
if stackTags then
table.insert(tags,possibleTagValue)
if possibleTagValue["Priority"] > currentPriority then
currentPriority = possibleTagValue["Priority"]
end
else
if possibleTagValue["Priority"] > currentPriority then
tags = {possibleTagValue}
currentPriority = possibleTagValue["Priority"]
end
end
end
end
end
end
end
end
if SpecialTags.Teams then
local playerFromSpeaker = Players:FindFirstChild(speakerName)
if playerFromSpeaker then
for _, team in pairs(SpecialTags.Teams) do
local actualTeam = game:GetService("Teams"):FindFirstChild(team.Team)
if playerFromSpeaker.Team == actualTeam then
for possibleTagName,possibleTagValue in pairs(possibleTags) do
for i,playerTagName in pairs(team.Tags) do
if playerTagName == possibleTagName then
if stackTags then
table.insert(tags,possibleTagValue)
if possibleTagValue["Priority"] > currentPriority then
currentPriority = possibleTagValue["Priority"]
end
else
if possibleTagValue["Priority"] > currentPriority then
tags = {possibleTagValue}
currentPriority = possibleTagValue["Priority"]
end
end
end
end
end
end
end
end
end
if SpecialTags.Gamepasses then
for _, gamepass in pairs(SpecialTags.Gamepasses) do
local playerFromSpeaker = Players:FindFirstChild(speakerName)
if game:GetService("MarketplaceService"):UserOwnsGamePassAsync(playerFromSpeaker.UserId, gamepass.GamepassId) then
local playerTags = {}
for possibleTagName,possibleTagValue in pairs(possibleTags) do
for i,gamepassTagName in pairs(gamepass.Tags) do
if gamepassTagName == possibleTagName then
if stackTags then
table.insert(tags,possibleTagValue)
if possibleTagValue["Priority"] > currentPriority then
currentPriority = possibleTagValue["Priority"]
end
else
if possibleTagValue["Priority"] > currentPriority then
tags = {possibleTagValue}
currentPriority = possibleTagValue["Priority"]
end
end
end
end
end
end
end
end
if SpecialTags.Badges then
for _, badge in pairs(SpecialTags.Badges) do
local playerFromSpeaker = Players:FindFirstChild(speakerName)
if game:GetService("BadgeService"):UserHasBadgeAsync(playerFromSpeaker.UserId, badge.BadgeId) then
local playerTags = {}
for possibleTagName,possibleTagValue in pairs(possibleTags) do
for i,badgeTagName in pairs(badge.Tags) do
if badgeTagName == possibleTagName then
if stackTags then
table.insert(tags,possibleTagValue)
if possibleTagValue["Priority"] > currentPriority then
currentPriority = possibleTagValue["Priority"]
end
else
if possibleTagValue["Priority"] > currentPriority then
tags = {possibleTagValue}
currentPriority = possibleTagValue["Priority"]
end
end
end
end
end
end
end
end
local returnTags = {}
if #tags > 1 then
for i = currentPriority, 1, -1 do
for tagIndex,tagValue in pairs(tags) do
if tagValue["Priority"] == i then
table.insert(returnTags, tagValue)
end
end
end
else
returnTags = tags
end
return returnTags
end
local function Run(ChatService)
local NAME_COLORS =
{
Color3.new(253/255, 41/255, 67/255), -- BrickColor.new("Bright red").Color,
Color3.new(1/255, 162/255, 255/255), -- BrickColor.new("Bright blue").Color,
Color3.new(2/255, 184/255, 87/255), -- BrickColor.new("Earth green").Color,
BrickColor.new("Bright violet").Color,
BrickColor.new("Bright orange").Color,
BrickColor.new("Bright yellow").Color,
BrickColor.new("Light reddish violet").Color,
BrickColor.new("Brick yellow").Color,
}
local function GetNameValue(pName)
local value = 0
for index = 1, #pName do
local cValue = string.byte(string.sub(pName, index, index))
local reverseIndex = #pName - index + 1
if #pName%2 == 1 then
reverseIndex = reverseIndex - 1
end
if reverseIndex%4 >= 2 then
cValue = -cValue
end
value = value + cValue
end
return value
end
local color_offset = 0
local function ComputeNameColor(pName)
return NAME_COLORS[((GetNameValue(pName) + color_offset) % #NAME_COLORS) + 1]
end
local function GetNameColor(speaker)
local player = speaker:GetPlayer()
if player then
if player.Team ~= nil then
return player.TeamColor.Color
end
end
return ComputeNameColor(speaker.Name)
end
ChatService.SpeakerAdded:connect(function(speakerName)
local speaker = ChatService:GetSpeaker(speakerName)
if not speaker:GetExtraData("NameColor") then
speaker:SetExtraData("NameColor", GetNameColor(speaker))
end
if not speaker:GetExtraData("ChatColor") then
local specialChatColor = GetSpecialChatColor(speakerName)
if specialChatColor then
speaker:SetExtraData("ChatColor", specialChatColor)
end
end
if not speaker:GetExtraData("Tags") then
local specialTags = GetSpecialTags(speakerName)
speaker:SetExtraData("Tags", specialTags)
end
end)
local PlayerChangedConnections = {}
Players.PlayerAdded:connect(function(player)
local changedConn = player.Changed:connect(function(property)
local speaker = ChatService:GetSpeaker(player.Name)
if speaker then
if property == "TeamColor" or property == "Neutral" or property == "Team" then
speaker:SetExtraData("NameColor", GetNameColor(speaker))
local specialTags = GetSpecialTags(player.Name)
speaker:SetExtraData("Tags", specialTags)
end
end
end)
PlayerChangedConnections[player] = changedConn
end)
Players.PlayerRemoving:connect(function(player)
local changedConn = PlayerChangedConnections[player]
if changedConn then
changedConn:Disconnect()
end
PlayerChangedConnections[player] = nil
end)
local DefaultChatSystemChatEvents = game:GetService("ReplicatedStorage"):WaitForChild("DefaultChatSystemChatEvents");
local event = Instance.new("RemoteEvent");
event.Name = "Toggle"
event.Parent = DefaultChatSystemChatEvents
event.OnServerEvent:Connect(function(player, eventType)
local speaker = ChatService:GetSpeaker(player.Name)
if eventType == "Tags" then
if not speaker:GetExtraData("Tags") or not speaker:GetExtraData("Tags")[1] then
local specialTags = GetSpecialTags(player.Name)
speaker:SetExtraData("Tags", specialTags)
else
speaker:SetExtraData("Tags", {})
end
elseif eventType == "Color" then
if not speaker:GetExtraData("ChatColor") then
local specialChatColor = GetSpecialChatColor(player.Name)
if specialChatColor then
speaker:SetExtraData("ChatColor", specialChatColor)
end
else
speaker:SetExtraData("ChatColor", false)
end
end
end)
end
return Run
================================================
FILE: src/Chat/ChatModules/FriendJoinNotifier.lua
================================================
-- // FileName: FriendJoinNotifer.lua
-- // Written by: TheGamer101
-- // Description: Module that adds a message to the chat whenever a friend joins the game.
local Chat = game:GetService("Chat")
local Players = game:GetService("Players")
local FriendService = game:GetService("FriendService")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
local FriendMessageTextColor = Color3.fromRGB(255, 255, 255)
local FriendMessageExtraData = {ChatColor = FriendMessageTextColor}
local function Run(ChatService)
local function ShowFriendJoinNotification()
if ChatSettings.ShowFriendJoinNotification ~= nil then
return ChatSettings.ShowFriendJoinNotification
end
return false
end
local function SendFriendJoinNotification(player, joinedFriend)
local speakerObj = ChatService:GetSpeaker(player.Name)
if speakerObj then
speakerObj:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_FriendChatNotifier_JoinMessage",
string.format("Your friend %s has joined the game.", joinedFriend.Name)
),
"{RBX_NAME}",
joinedFriend.Name
),
"System",
FriendMessageExtraData
)
end
end
local function TrySendFriendNotification(player, joinedPlayer)
if player ~= joinedPlayer then
coroutine.wrap(function()
if player:IsFriendsWith(joinedPlayer.UserId) then
SendFriendJoinNotification(player, joinedPlayer)
end
end)()
end
end
if ShowFriendJoinNotification() then
Players.PlayerAdded:connect(function(player)
local possibleFriends = Players:GetPlayers()
for i = 1, #possibleFriends do
TrySendFriendNotification(possibleFriends[i], player)
end
end)
end
end
return Run
================================================
FILE: src/Chat/ChatModules/GuildChat.lua
================================================
-- // FileName: GuildChat.lua
-- // Written by: Xsitsu
-- // Description: Module that handles all guild chat.
local Chat = game:GetService("Chat")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
local errorTextColor = ChatSettings.ErrorMessageTextColor or Color3.fromRGB(245, 50, 50)
local errorExtraData = {ChatColor = errorTextColor}
local function Run(ChatService)
local Players = game:GetService("Players")
local channel = ChatService:AddChannel("Guild")
channel.Joinable = false
channel.Leavable = false
channel.AutoJoin = false
channel.Private = true
local function GuildChatReplicationFunction(fromSpeaker, message, channelName)
local speakerObj = ChatService:GetSpeaker(fromSpeaker)
local channelObj = ChatService:GetChannel(channelName)
if channelName == "Guild" then
if (speakerObj and channelObj) then
local player = speakerObj:GetPlayer()
if (player) then
local success, reason = network:invoke("sendGuildChat", player, message)
return success, reason
--[[
local speakerGuild = network:invoke("getGuildDataByPlayer", player)
if speakerGuild and speakerGuild.members and #speakerGuild.members > 0 then
for i, guildMember in pairs(speakerGuild.members) do
if guildMember and guildMember.player then
local speakerName = guildMember.player.Name
local otherSpeaker = ChatService:GetSpeaker(speakerName)
if (otherSpeaker) then
local otherPlayer = otherSpeaker:GetPlayer()
if (otherPlayer) then
local extraData = {
ChannelColor = Color3.fromRGB(0, 240, 244);
ChatColor = Color3.fromRGB(0, 240, 244)
}
otherSpeaker:SendMessage(message, channelName, fromSpeaker, extraData)
end
end
end
end
end
]]
end
end
return true
end
end
channel:RegisterProcessCommandsFunction("replication_function_guild", GuildChatReplicationFunction, ChatConstants.LowPriority)
local function DoGuildCommand(fromSpeaker, message, channel)
if message == nil then
message = ""
end
local speaker = ChatService:GetSpeaker(fromSpeaker)
if speaker then
local player = speaker:GetPlayer()
if player then
local speakerGuildId = player:FindFirstChild("guildId") and player.guildId.Value
if speakerGuildId == nil or speakerGuildId == "" then
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_GuildChat_CannotGuildChatIfNotInGuild","You cannot guild chat if you are not on a guild!"), channel, errorExtraData)
return
end
local channelObj = ChatService:GetChannel("Guild")
if channelObj then
if not speaker:IsInChannel(channelObj.Name) then
speaker:JoinChannel(channelObj.Name)
end
if message and string.len(message) > 0 then
speaker:SayMessage(message, channelObj.Name)
end
speaker:SetMainChannel(channelObj.Name)
end
end
end
end
local function GuildCommandsFunction(fromSpeaker, message, channel)
local processedCommand = false
if message == nil then
error("Message is nil")
end
if channel == "Guild" then
return false
end
if string.sub(message, 1, 6):lower() == "/guild " or message:lower() == "/guild" then
DoGuildCommand(fromSpeaker, string.sub(message, 7), channel)
processedCommand = true
elseif string.sub(message, 1, 3):lower() == "/g " or message:lower() == "/g" then
DoGuildCommand(fromSpeaker, string.sub(message, 4), channel)
processedCommand = true
end
return processedCommand
end
ChatService:RegisterProcessCommandsFunction("guild_commands", GuildCommandsFunction, ChatConstants.StandardPriority)
local function GetDefaultChannelNameColor()
return Color3.fromRGB(145, 71, 255)
end
local function PutSpeakerInCorrectGuildChatState(speakerObj, playerObj)
speakerObj:UpdateChannelNameColor(channel.Name, GetDefaultChannelNameColor())
if not speakerObj:IsInChannel(channel.Name) then
speakerObj:JoinChannel(channel.Name)
end
end
ChatService.SpeakerAdded:connect(function(speakerName)
local speakerObj = ChatService:GetSpeaker(speakerName)
if speakerObj then
local player = speakerObj:GetPlayer()
if player then
PutSpeakerInCorrectGuildChatState(speakerObj, player)
end
end
end)
local PlayerChangedConnections = {}
network:connect("playerDataLoaded", "Event", function(player)
local speakerObj = ChatService:GetSpeaker(player.Name)
if speakerObj then
PutSpeakerInCorrectGuildChatState(speakerObj, player)
end
PlayerChangedConnections[player] = nil
end)
Players.PlayerRemoving:connect(function(player)
local changedConn = PlayerChangedConnections[player]
if changedConn then
changedConn:Disconnect()
end
PlayerChangedConnections[player] = nil
end)
end
return Run
================================================
FILE: src/Chat/ChatModules/MeCommand.lua
================================================
-- // FileName: MeCommand.lua
-- // Written by: TheGamer101
-- // Description: Sets the type of /me messages.
local Chat = game:GetService("Chat")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local function Run(ChatService)
local function MeCommandFilterFunction(speakerName, messageObj, channelName)
local message = messageObj.Message
if message and string.sub(message, 1, 4):lower() == "/me " then
-- Set a different message type so that clients can render the message differently.
messageObj.MessageType = ChatConstants.MessageTypeMeCommand
end
end
ChatService:RegisterFilterMessageFunction("me_command", MeCommandFilterFunction)
end
return Run
================================================
FILE: src/Chat/ChatModules/MuteSpeaker.lua
================================================
-- // FileName: MuteSpeaker.lua
-- // Written by: TheGamer101
-- // Description: Module that handles all the mute and unmute commands.
local Chat = game:GetService("Chat")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
local errorTextColor = ChatSettings.ErrorMessageTextColor or Color3.fromRGB(245, 50, 50)
local errorExtraData = {ChatColor = errorTextColor}
local function Run(ChatService)
local function GetSpeakerNameFromMessage(message)
local speakerName = message
if string.sub(message, 1, 1) == "\"" then
local pos = string.find(message, "\"", 2)
if pos then
speakerName = string.sub(message, 2, pos - 1)
end
else
local first = string.match(message, "^[^%s]+")
if first then
speakerName = first
end
end
return speakerName
end
local function DoMuteCommand(speakerName, message, channel)
local muteSpeakerName = GetSpeakerNameFromMessage(message)
local speaker = ChatService:GetSpeaker(speakerName)
if speaker then
if muteSpeakerName:lower() == speakerName:lower() then
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_DoMuteCommand_CannotMuteSelf","You cannot mute yourself."), channel, errorExtraData)
return
end
local muteSpeaker = ChatService:GetSpeaker(muteSpeakerName)
if muteSpeaker then
speaker:AddMutedSpeaker(muteSpeaker.Name)
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatMain_SpeakerHasBeenMuted",
string.format("Speaker '%s' has been muted.", muteSpeaker.Name)
),
"{RBX_NAME}",muteSpeaker.Name
),
channel
)
else
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_MuteSpeaker_SpeakerDoesNotExist",
string.format("Speaker '%s' does not exist.", tostring(muteSpeakerName))
),
"{RBX_NAME}",tostring(muteSpeakerName)
),
channel,
errorExtraData
)
end
end
end
local function DoUnmuteCommand(speakerName, message, channel)
local unmuteSpeakerName = GetSpeakerNameFromMessage(message)
local speaker = ChatService:GetSpeaker(speakerName)
if speaker then
if unmuteSpeakerName:lower() == speakerName:lower() then
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_DoMuteCommand_CannotMuteSelf","You cannot mute yourself."), channel, errorExtraData)
return
end
local unmuteSpeaker = ChatService:GetSpeaker(unmuteSpeakerName)
if unmuteSpeaker then
speaker:RemoveMutedSpeaker(unmuteSpeaker.Name)
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatMain_SpeakerHasBeenUnMuted",
string.format("Speaker '%s' has been unmuted.", unmuteSpeaker.Name)
),
"{RBX_NAME}",unmuteSpeaker.Name
),
channel
)
else
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_MuteSpeaker_SpeakerDoesNotExist",
string.format("Speaker '%s' does not exist.", tostring(unmuteSpeakerName))
),
"{RBX_NAME}",tostring(unmuteSpeakerName)
),
channel,
errorExtraData
)
end
end
end
local function MuteCommandsFunction(fromSpeaker, message, channel)
local processedCommand = false
if string.sub(message, 1, 6):lower() == "/mute " then
DoMuteCommand(fromSpeaker, string.sub(message, 7), channel)
processedCommand = true
elseif string.sub(message, 1, 8):lower() == "/unmute " then
DoUnmuteCommand(fromSpeaker, string.sub(message, 9), channel)
processedCommand = true
end
return processedCommand
end
ChatService:RegisterProcessCommandsFunction("mute_commands", MuteCommandsFunction, ChatConstants.StandardPriority)
end
return Run
================================================
FILE: src/Chat/ChatModules/PartyChat.lua
================================================
-- // FileName: PartyChat.lua
-- // Written by: Xsitsu
-- // Description: Module that handles all party chat.
local Chat = game:GetService("Chat")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
local errorTextColor = ChatSettings.ErrorMessageTextColor or Color3.fromRGB(245, 50, 50)
local errorExtraData = {ChatColor = errorTextColor}
local function Run(ChatService)
local Players = game:GetService("Players")
local channel = ChatService:AddChannel("Party")
channel.Joinable = false
channel.Leavable = false
channel.AutoJoin = false
channel.Private = true
local function PartyChatReplicationFunction(fromSpeaker, message, channelName)
local speakerObj = ChatService:GetSpeaker(fromSpeaker)
local channelObj = ChatService:GetChannel(channelName)
if channelName == "Party" then
if (speakerObj and channelObj) then
local player = speakerObj:GetPlayer()
if (player) then
local speakerParty = network:invoke("getPartyDataByPlayer", player)
if speakerParty and speakerParty.members and #speakerParty.members > 0 then
for i, partyMember in pairs(speakerParty.members) do
if partyMember and partyMember.player then
local speakerName = partyMember.player.Name
local otherSpeaker = ChatService:GetSpeaker(speakerName)
if (otherSpeaker) then
local otherPlayer = otherSpeaker:GetPlayer()
if (otherPlayer) then
local extraData = {
ChannelColor = Color3.fromRGB(0, 240, 244);
ChatColor = Color3.fromRGB(0, 240, 244)
}
otherSpeaker:SendMessage(message, channelName, fromSpeaker, extraData)
end
end
end
end
end
end
end
return true
end
end
channel:RegisterProcessCommandsFunction("replication_function_party", PartyChatReplicationFunction, ChatConstants.LowPriority)
local function DoPartyCommand(fromSpeaker, message, channel)
if message == nil then
message = ""
end
local speaker = ChatService:GetSpeaker(fromSpeaker)
if speaker then
local player = speaker:GetPlayer()
if player then
local speakerParty = network:invoke("getPartyDataByPlayer", player)
if speakerParty == nil or speakerParty.members == nil then
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_PartyChat_CannotPartyChatIfNotInParty","You cannot party chat if you are not on a party!"), channel, errorExtraData)
return
end
local channelObj = ChatService:GetChannel("Party")
if channelObj then
if not speaker:IsInChannel(channelObj.Name) then
speaker:JoinChannel(channelObj.Name)
end
if message and string.len(message) > 0 then
speaker:SayMessage(message, channelObj.Name)
end
speaker:SetMainChannel(channelObj.Name)
end
end
end
end
local function PartyCommandsFunction(fromSpeaker, message, channel)
local processedCommand = false
if message == nil then
error("Message is nil")
end
if channel == "Party" then
return false
end
if string.sub(message, 1, 6):lower() == "/party " or message:lower() == "/party" then
DoPartyCommand(fromSpeaker, string.sub(message, 7), channel)
processedCommand = true
elseif string.sub(message, 1, 3):lower() == "/p " or message:lower() == "/p" then
DoPartyCommand(fromSpeaker, string.sub(message, 4), channel)
processedCommand = true
end
return processedCommand
end
ChatService:RegisterProcessCommandsFunction("party_commands", PartyCommandsFunction, ChatConstants.StandardPriority)
local function GetDefaultChannelNameColor()
return Color3.fromRGB(0, 240, 244)
end
local function PutSpeakerInCorrectPartyChatState(speakerObj, playerObj)
speakerObj:UpdateChannelNameColor(channel.Name, GetDefaultChannelNameColor())
if not speakerObj:IsInChannel(channel.Name) then
speakerObj:JoinChannel(channel.Name)
end
end
ChatService.SpeakerAdded:connect(function(speakerName)
local speakerObj = ChatService:GetSpeaker(speakerName)
if speakerObj then
local player = speakerObj:GetPlayer()
if player then
PutSpeakerInCorrectPartyChatState(speakerObj, player)
end
end
end)
local PlayerChangedConnections = {}
network:connect("playerDataLoaded", "Event", function(player)
local speakerObj = ChatService:GetSpeaker(player.Name)
if speakerObj then
PutSpeakerInCorrectPartyChatState(speakerObj, player)
end
PlayerChangedConnections[player] = nil
end)
Players.PlayerRemoving:connect(function(player)
local changedConn = PlayerChangedConnections[player]
if changedConn then
changedConn:Disconnect()
end
PlayerChangedConnections[player] = nil
end)
end
return Run
================================================
FILE: src/Chat/ChatModules/PrivateMessaging.lua
================================================
-- // FileName: PrivateMessaging.lua
-- // Written by: Xsitsu
-- // Description: Module that handles all private messaging.
local Chat = game:GetService("Chat")
local RunService = game:GetService("RunService")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
local errorTextColor = ChatSettings.ErrorMessageTextColor or Color3.fromRGB(245, 50, 50)
local errorExtraData = {ChatColor = errorTextColor}
function GetWhisperChannelPrefix()
if ChatConstants.WhisperChannelPrefix then
return ChatConstants.WhisperChannelPrefix
end
return "To "
end
local function Run(ChatService)
local function CanCommunicate(fromSpeaker, toSpeaker)
if RunService:IsStudio() then
return true
end
local fromPlayer = fromSpeaker:GetPlayer()
local toPlayer = toSpeaker:GetPlayer()
if fromPlayer and toPlayer then
local success, canChat = pcall(function()
return Chat:CanUsersChatAsync(fromPlayer.UserId, toPlayer.UserId)
end)
return success and canChat
end
return false
end
local function DoWhisperCommand(fromSpeaker, message, channel)
local otherSpeakerName = message
local sendMessage = nil
if (string.sub(message, 1, 1) == "\"") then
local pos = string.find(message, "\"", 2)
if (pos) then
otherSpeakerName = string.sub(message, 2, pos - 1)
sendMessage = string.sub(message, pos + 2)
end
else
local first = string.match(message, "^[^%s]+")
if (first) then
otherSpeakerName = first
sendMessage = string.sub(message, string.len(otherSpeakerName) + 2)
end
end
local speaker = ChatService:GetSpeaker(fromSpeaker)
local otherSpeaker = ChatService:GetSpeaker(otherSpeakerName)
local channelObj = ChatService:GetChannel(GetWhisperChannelPrefix() .. otherSpeakerName)
if channelObj and otherSpeaker then
if not CanCommunicate(speaker, otherSpeaker) then
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_PrivateMessaging_CannotChat","You are not able to chat with this player."), channel, errorExtraData)
return
end
if (channelObj.Name == GetWhisperChannelPrefix() .. speaker.Name) then
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_PrivateMessaging_CannotWhisperToSelf","You cannot whisper to yourself."), channel, errorExtraData)
else
if (not speaker:IsInChannel(channelObj.Name)) then
speaker:JoinChannel(channelObj.Name)
end
if (sendMessage and (string.len(sendMessage) > 0) ) then
speaker:SayMessage(sendMessage, channelObj.Name)
end
speaker:SetMainChannel(channelObj.Name)
end
else
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_MuteSpeaker_SpeakerDoesNotExist",
string.format("Speaker '%s' does not exist.", tostring(otherSpeakerName))
),
"{RBX_NAME}",tostring(otherSpeakerName)
),
channel,
errorExtraData
)
end
end
local function WhisperCommandsFunction(fromSpeaker, message, channel)
local processedCommand = false
if (string.sub(message, 1, 3):lower() == "/w ") then
DoWhisperCommand(fromSpeaker, string.sub(message, 4), channel)
processedCommand = true
elseif (string.sub(message, 1, 9):lower() == "/whisper ") then
DoWhisperCommand(fromSpeaker, string.sub(message, 10), channel)
processedCommand = true
end
return processedCommand
end
local function PrivateMessageReplicationFunction(fromSpeaker, message, channelName)
local sendingSpeaker = ChatService:GetSpeaker(fromSpeaker)
local extraData = sendingSpeaker.ExtraData
sendingSpeaker:SendMessage(message, channelName, fromSpeaker, extraData)
local toSpeaker = ChatService:GetSpeaker(string.sub(channelName, 4))
if (toSpeaker) then
if (not toSpeaker:IsInChannel(GetWhisperChannelPrefix() .. fromSpeaker)) then
toSpeaker:JoinChannel(GetWhisperChannelPrefix() .. fromSpeaker)
end
toSpeaker:SendMessage(message, GetWhisperChannelPrefix() .. fromSpeaker, fromSpeaker, extraData)
end
return true
end
local function PrivateMessageAddTypeFunction(speakerName, messageObj, channelName)
if ChatConstants.MessageTypeWhisper then
messageObj.MessageType = ChatConstants.MessageTypeWhisper
end
end
ChatService:RegisterProcessCommandsFunction("whisper_commands", WhisperCommandsFunction, ChatConstants.StandardPriority)
local function GetWhisperChanneNameColor()
if ChatSettings.WhisperChannelNameColor then
return ChatSettings.WhisperChannelNameColor
end
return Color3.fromRGB(102, 14, 102)
end
ChatService.SpeakerAdded:connect(function(speakerName)
if (ChatService:GetChannel(GetWhisperChannelPrefix() .. speakerName)) then
ChatService:RemoveChannel(GetWhisperChannelPrefix() .. speakerName)
end
local channel = ChatService:AddChannel(GetWhisperChannelPrefix() .. speakerName)
channel.Joinable = false
channel.Leavable = true
channel.AutoJoin = false
channel.Private = true
channel.WelcomeMessage = string.gsub(
ChatLocalization:Get(
"GameChat_PrivateMessaging_NowChattingWith",
"You are now privately chatting with " .. speakerName .. "."
),
"{RBX_NAME}",tostring(speakerName)
)
channel.ChannelNameColor = GetWhisperChanneNameColor()
channel:RegisterProcessCommandsFunction("replication_function", PrivateMessageReplicationFunction, ChatConstants.LowPriority)
channel:RegisterFilterMessageFunction("message_type_function", PrivateMessageAddTypeFunction)
end)
ChatService.SpeakerRemoved:connect(function(speakerName)
if (ChatService:GetChannel(GetWhisperChannelPrefix() .. speakerName)) then
ChatService:RemoveChannel(GetWhisperChannelPrefix() .. speakerName)
end
end)
end
return Run
================================================
FILE: src/Chat/ChatModules/TeamChat.lua
================================================
-- // FileName: TeamChat.lua
-- // Written by: Xsitsu
-- // Description: Module that handles all team chat.
local Chat = game:GetService("Chat")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
local errorTextColor = ChatSettings.ErrorMessageTextColor or Color3.fromRGB(245, 50, 50)
local errorExtraData = {ChatColor = errorTextColor}
local function Run(ChatService)
local Players = game:GetService("Players")
local channel = ChatService:AddChannel("Team")
channel.WelcomeMessage = ChatLocalization:Get("GameChat_TeamChat_WelcomeMessage","This is a private channel between you and your team members.")
channel.Joinable = false
channel.Leavable = false
channel.AutoJoin = false
channel.Private = true
local function TeamChatReplicationFunction(fromSpeaker, message, channelName)
local speakerObj = ChatService:GetSpeaker(fromSpeaker)
local channelObj = ChatService:GetChannel(channelName)
if channelName == "Team" then
if (speakerObj and channelObj) then
local player = speakerObj:GetPlayer()
if (player) then
for i, speakerName in pairs(channelObj:GetSpeakerList()) do
local otherSpeaker = ChatService:GetSpeaker(speakerName)
if (otherSpeaker) then
local otherPlayer = otherSpeaker:GetPlayer()
if (otherPlayer) then
if (player.Team == otherPlayer.Team) then
local extraData = {
NameColor = player.TeamColor.Color,
ChannelColor = player.TeamColor.Color
}
otherSpeaker:SendMessage(message, channelName, fromSpeaker, extraData)
else
--// Could use this line to obfuscate message for cool effects
--otherSpeaker:SendMessage(message, channelName, fromSpeaker)
end
end
end
end
end
end
return true
end
end
channel:RegisterProcessCommandsFunction("replication_function", TeamChatReplicationFunction, ChatConstants.LowPriority)
local function DoTeamCommand(fromSpeaker, message, channel)
if message == nil then
message = ""
end
local speaker = ChatService:GetSpeaker(fromSpeaker)
if speaker then
local player = speaker:GetPlayer()
if player then
if player.Team == nil then
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_TeamChat_CannotTeamChatIfNotInTeam","You cannot team chat if you are not on a team!"), channel, errorExtraData)
return
end
local channelObj = ChatService:GetChannel("Team")
if channelObj then
if not speaker:IsInChannel(channelObj.Name) then
speaker:JoinChannel(channelObj.Name)
end
if message and string.len(message) > 0 then
speaker:SayMessage(message, channelObj.Name)
end
speaker:SetMainChannel(channelObj.Name)
end
end
end
end
local function TeamCommandsFunction(fromSpeaker, message, channel)
local processedCommand = false
if message == nil then
error("Message is nil")
end
if channel == "Team" then
return false
end
if string.sub(message, 1, 6):lower() == "/team " or message:lower() == "/team" then
DoTeamCommand(fromSpeaker, string.sub(message, 7), channel)
processedCommand = true
elseif string.sub(message, 1, 3):lower() == "/t " or message:lower() == "/t" then
DoTeamCommand(fromSpeaker, string.sub(message, 4), channel)
processedCommand = true
--[[ elseif string.sub(message, 1, 2):lower() == "% " or message:lower() == "%" then
DoTeamCommand(fromSpeaker, string.sub(message, 3), channel)
processedCommand = true ]]
end
return processedCommand
end
ChatService:RegisterProcessCommandsFunction("team_commands", TeamCommandsFunction, ChatConstants.StandardPriority)
local function GetDefaultChannelNameColor()
if ChatSettings.DefaultChannelNameColor then
return ChatSettings.DefaultChannelNameColor
end
return Color3.fromRGB(35, 76, 142)
end
local function PutSpeakerInCorrectTeamChatState(speakerObj, playerObj)
if playerObj.Neutral or playerObj.Team == nil then
speakerObj:UpdateChannelNameColor(channel.Name, GetDefaultChannelNameColor())
if speakerObj:IsInChannel(channel.Name) then
speakerObj:LeaveChannel(channel.Name)
end
elseif not playerObj.Neutral and playerObj.Team then
speakerObj:UpdateChannelNameColor(channel.Name, playerObj.Team.TeamColor.Color)
if not speakerObj:IsInChannel(channel.Name) then
speakerObj:JoinChannel(channel.Name)
end
end
end
ChatService.SpeakerAdded:connect(function(speakerName)
local speakerObj = ChatService:GetSpeaker(speakerName)
if speakerObj then
local player = speakerObj:GetPlayer()
if player then
PutSpeakerInCorrectTeamChatState(speakerObj, player)
end
end
end)
local PlayerChangedConnections = {}
Players.PlayerAdded:connect(function(player)
local changedConn = player.Changed:connect(function(property)
local speakerObj = ChatService:GetSpeaker(player.Name)
if speakerObj then
if property == "Neutral" then
PutSpeakerInCorrectTeamChatState(speakerObj, player)
elseif property == "Team" then
PutSpeakerInCorrectTeamChatState(speakerObj, player)
if speakerObj:IsInChannel(channel.Name) then
speakerObj:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_TeamChat_NowInTeam",
string.format("You are now on the '%s' team.", player.Team.Name)
),
"{RBX_NAME}",player.Team.Name
),
channel.Name
)
end
end
end
end)
PlayerChangedConnections[player] = changedConn
end)
Players.PlayerRemoving:connect(function(player)
local changedConn = PlayerChangedConnections[player]
if changedConn then
changedConn:Disconnect()
end
PlayerChangedConnections[player] = nil
end)
end
return Run
================================================
FILE: src/Chat/ChatModules/Toggle.lua
================================================
--[[-- // FileName: Toggle.lua
-- // Written by: Nicholas_Foreman
-- // Description: Allows for toggling chat tags/chat color.
local Chat = game:GetService("Chat")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = { Get = function(key,default) return default end } end
local cache = {}
local function Run(ChatService)
local function ProcessCommandsFunction(fromSpeaker, message, channel)
local speaker = ChatService:GetSpeaker(fromSpeaker)
if string.sub(message, 1, 7) == "/toggle" then
if not cache[fromSpeaker] then
cache[fromSpeaker] = {
Active = true,
Tags = speaker:GetExtraData("Tags"),
Color = speaker:GetExtraData("ChatColor")
}
end
if message:lower() == "/toggle" then
speaker:SendSystemMessage(ChatLocalization:Get("ToggleSuccess","/toggle : toggles chat tags or chat color."), channel)
return true
elseif message:lower() == "/toggle tags" then
if cache[fromSpeaker]["Active"] then
end
speaker:SendSystemMessage(ChatLocalization:Get("ToggleTagsSuccess","Successfully toggled chat tags."), channel)
return true
elseif message:lower() == "/toggle color" then
speaker:SendSystemMessage(ChatLocalization:Get("ToggleChatSuccess","Successfully toggled chat tags."), channel)
return true
end
end
return false
end
ChatService:RegisterProcessCommandsFunction("chat_toggler", ProcessCommandsFunction, ChatConstants.StandardPriority)
end
return Run]]
local function Run(ChatService)
end
return Run;
================================================
FILE: src/Chat/ChatModules/init.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/Chat/ChatScript/ChatMain/ChannelsBar.lua
================================================
-- // FileName: ChannelsBar.lua
-- // Written by: Xsitsu
-- // Description: Manages creating, destroying, and displaying ChannelTabs.
local module = {}
local PlayerGui = game:GetService("Players").LocalPlayer:WaitForChild("PlayerGui")
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local modulesFolder = script.Parent
local moduleChannelsTab = require(modulesFolder:WaitForChild("ChannelsTab"))
local MessageSender = require(modulesFolder:WaitForChild("MessageSender"))
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:CreateGuiObjects(targetParent)
local BaseFrame = Instance.new("Frame")
BaseFrame.Selectable = false
BaseFrame.Size = UDim2.new(1, 0, 1, 0)
BaseFrame.BackgroundTransparency = 1
BaseFrame.Parent = targetParent
local ScrollingBase = Instance.new("Frame")
ScrollingBase.Selectable = false
ScrollingBase.Name = "ScrollingBase"
ScrollingBase.BackgroundTransparency = 1
ScrollingBase.ClipsDescendants = true
ScrollingBase.Size = UDim2.new(1, 0, 1, 0)
ScrollingBase.Position = UDim2.new(0, 0, 0, 0)
ScrollingBase.Parent = BaseFrame
local ScrollerSizer = Instance.new("Frame")
ScrollerSizer.Selectable = false
ScrollerSizer.Name = "ScrollerSizer"
ScrollerSizer.BackgroundTransparency = 1
ScrollerSizer.Size = UDim2.new(1, 0, 1, 0)
ScrollerSizer.Position = UDim2.new(0, 0, 0, 0)
ScrollerSizer.Parent = ScrollingBase
local ScrollerFrame = Instance.new("Frame")
ScrollerFrame.Selectable = false
ScrollerFrame.Name = "ScrollerFrame"
ScrollerFrame.BackgroundTransparency = 1
ScrollerFrame.Size = UDim2.new(1, 0, 1, 0)
ScrollerFrame.Position = UDim2.new(0, 0, 0, 0)
ScrollerFrame.Parent = ScrollerSizer
local LeaveConfirmationFrameBase = Instance.new("Frame")
LeaveConfirmationFrameBase.Selectable = false
LeaveConfirmationFrameBase.Size = UDim2.new(1, 0, 1, 0)
LeaveConfirmationFrameBase.Position = UDim2.new(0, 0, 0, 0)
LeaveConfirmationFrameBase.ClipsDescendants = true
LeaveConfirmationFrameBase.BackgroundTransparency = 1
LeaveConfirmationFrameBase.Parent = BaseFrame
local LeaveConfirmationFrame = Instance.new("Frame")
LeaveConfirmationFrame.Selectable = false
LeaveConfirmationFrame.Name = "LeaveConfirmationFrame"
LeaveConfirmationFrame.Size = UDim2.new(1, 0, 1, 0)
LeaveConfirmationFrame.Position = UDim2.new(0, 0, 1, 0)
LeaveConfirmationFrame.BackgroundTransparency = 0.6
LeaveConfirmationFrame.BorderSizePixel = 0
LeaveConfirmationFrame.BackgroundColor3 = Color3.new(0, 0, 0)
LeaveConfirmationFrame.Parent = LeaveConfirmationFrameBase
local InputBlocker = Instance.new("TextButton")
InputBlocker.Selectable = false
InputBlocker.Size = UDim2.new(1, 0, 1, 0)
InputBlocker.BackgroundTransparency = 1
InputBlocker.Text = ""
InputBlocker.Parent = LeaveConfirmationFrame
local LeaveConfirmationButtonYes = Instance.new("TextButton")
LeaveConfirmationButtonYes.Selectable = false
LeaveConfirmationButtonYes.Size = UDim2.new(0.25, 0, 1, 0)
LeaveConfirmationButtonYes.BackgroundTransparency = 1
LeaveConfirmationButtonYes.Font = ChatSettings.DefaultFont
LeaveConfirmationButtonYes.TextSize = 18
LeaveConfirmationButtonYes.TextStrokeTransparency = 0.75
LeaveConfirmationButtonYes.Position = UDim2.new(0, 0, 0, 0)
LeaveConfirmationButtonYes.TextColor3 = Color3.new(0, 1, 0)
LeaveConfirmationButtonYes.Text = "Confirm"
LeaveConfirmationButtonYes.Parent = LeaveConfirmationFrame
local LeaveConfirmationButtonNo = LeaveConfirmationButtonYes:Clone()
LeaveConfirmationButtonNo.Parent = LeaveConfirmationFrame
LeaveConfirmationButtonNo.Position = UDim2.new(0.75, 0, 0, 0)
LeaveConfirmationButtonNo.TextColor3 = Color3.new(1, 0, 0)
LeaveConfirmationButtonNo.Text = "Cancel"
local LeaveConfirmationNotice = Instance.new("TextLabel")
LeaveConfirmationNotice.Selectable = false
LeaveConfirmationNotice.Size = UDim2.new(0.5, 0, 1, 0)
LeaveConfirmationNotice.Position = UDim2.new(0.25, 0, 0, 0)
LeaveConfirmationNotice.BackgroundTransparency = 1
LeaveConfirmationNotice.TextColor3 = Color3.new(1, 1, 1)
LeaveConfirmationNotice.TextStrokeTransparency = 0.75
LeaveConfirmationNotice.Text = "Leave channel ?"
LeaveConfirmationNotice.Font = ChatSettings.DefaultFont
LeaveConfirmationNotice.TextSize = 18
LeaveConfirmationNotice.Parent = LeaveConfirmationFrame
local LeaveTarget = Instance.new("StringValue")
LeaveTarget.Name = "LeaveTarget"
LeaveTarget.Parent = LeaveConfirmationFrame
local outPos = LeaveConfirmationFrame.Position
LeaveConfirmationButtonYes.MouseButton1Click:connect(function()
MessageSender:SendMessage(string.format("/leave %s", LeaveTarget.Value), nil)
LeaveConfirmationFrame:TweenPosition(outPos, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.2, true)
end)
LeaveConfirmationButtonNo.MouseButton1Click:connect(function()
LeaveConfirmationFrame:TweenPosition(outPos, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.2, true)
end)
local scale = 0.7
local scaleOther = (1 - scale) / 2
local pageButtonImage = "rbxasset://textures/ui/Chat/TabArrowBackground.png"
local pageButtonArrowImage = "rbxasset://textures/ui/Chat/TabArrow.png"
--// ToDo: Remove these lines when the assets are put into trunk.
--// These grab unchanging versions hosted on the site, and not from the content folder.
pageButtonImage = "rbxassetid://471630199"
pageButtonArrowImage = "rbxassetid://471630112"
local PageLeftButton = Instance.new("ImageButton", BaseFrame)
PageLeftButton.Selectable = ChatSettings.GamepadNavigationEnabled
PageLeftButton.Name = "PageLeftButton"
PageLeftButton.SizeConstraint = Enum.SizeConstraint.RelativeYY
PageLeftButton.Size = UDim2.new(scale, 0, scale, 0)
PageLeftButton.BackgroundTransparency = 1
PageLeftButton.Position = UDim2.new(0, 4, scaleOther, 0)
PageLeftButton.Visible = false
PageLeftButton.Image = pageButtonImage
local ArrowLabel = Instance.new("ImageLabel", PageLeftButton)
ArrowLabel.Name = "ArrowLabel"
ArrowLabel.BackgroundTransparency = 1
ArrowLabel.Size = UDim2.new(0.4, 0, 0.4, 0)
ArrowLabel.Image = pageButtonArrowImage
local PageRightButtonPositionalHelper = Instance.new("Frame", BaseFrame)
PageRightButtonPositionalHelper.Selectable = false
PageRightButtonPositionalHelper.BackgroundTransparency = 1
PageRightButtonPositionalHelper.Name = "PositionalHelper"
PageRightButtonPositionalHelper.Size = PageLeftButton.Size
PageRightButtonPositionalHelper.SizeConstraint = PageLeftButton.SizeConstraint
PageRightButtonPositionalHelper.Position = UDim2.new(1, 0, scaleOther, 0)
local PageRightButton = PageLeftButton:Clone()
PageRightButton.Parent = PageRightButtonPositionalHelper
PageRightButton.Name = "PageRightButton"
PageRightButton.Size = UDim2.new(1, 0, 1, 0)
PageRightButton.SizeConstraint = Enum.SizeConstraint.RelativeXY
PageRightButton.Position = UDim2.new(-1, -4, 0, 0)
local positionOffset = UDim2.new(0.05, 0, 0, 0)
PageRightButton.ArrowLabel.Position = UDim2.new(0.3, 0, 0.3, 0) + positionOffset
PageLeftButton.ArrowLabel.Position = UDim2.new(0.3, 0, 0.3, 0) - positionOffset
PageLeftButton.ArrowLabel.Rotation = 180
self.GuiObject = BaseFrame
self.GuiObjects.BaseFrame = BaseFrame
self.GuiObjects.ScrollerSizer = ScrollerSizer
self.GuiObjects.ScrollerFrame = ScrollerFrame
self.GuiObjects.PageLeftButton = PageLeftButton
self.GuiObjects.PageRightButton = PageRightButton
self.GuiObjects.LeaveConfirmationFrame = LeaveConfirmationFrame
self.GuiObjects.LeaveConfirmationNotice = LeaveConfirmationNotice
self.GuiObjects.PageLeftButtonArrow = PageLeftButton.ArrowLabel
self.GuiObjects.PageRightButtonArrow = PageRightButton.ArrowLabel
self:AnimGuiObjects()
PageLeftButton.MouseButton1Click:connect(function() self:ScrollChannelsFrame(-1) end)
PageRightButton.MouseButton1Click:connect(function() self:ScrollChannelsFrame(1) end)
self:ScrollChannelsFrame(0)
end
function methods:UpdateMessagePostedInChannel(channelName)
local tab = self:GetChannelTab(channelName)
if (tab) then
tab:UpdateMessagePostedInChannel()
else
warn("ChannelsTab '" .. channelName .. "' does not exist!")
end
end
function methods:AddChannelTab(channelName)
if (self:GetChannelTab(channelName)) then
error("Channel tab '" .. channelName .. "'already exists!")
end
local tab = moduleChannelsTab.new(channelName)
tab.GuiObject.Parent = self.GuiObjects.ScrollerFrame
self.ChannelTabs[channelName:lower()] = tab
self.NumTabs = self.NumTabs + 1
self:OrganizeChannelTabs()
if (ChatSettings.RightClickToLeaveChannelEnabled) then
tab.NameTag.MouseButton2Click:connect(function()
self.LeaveConfirmationNotice.Text = string.format("Leave channel %s?", tab.ChannelName)
self.LeaveConfirmationFrame.LeaveTarget.Value = tab.ChannelName
self.LeaveConfirmationFrame:TweenPosition(UDim2.new(0, 0, 0, 0), Enum.EasingDirection.In, Enum.EasingStyle.Quad, 0.2, true)
end)
end
return tab
end
function methods:RemoveChannelTab(channelName)
if (not self:GetChannelTab(channelName)) then
error("Channel tab '" .. channelName .. "'does not exist!")
end
local indexName = channelName:lower()
self.ChannelTabs[indexName]:Destroy()
self.ChannelTabs[indexName] = nil
self.NumTabs = self.NumTabs - 1
self:OrganizeChannelTabs()
end
function methods:GetChannelTab(channelName)
return self.ChannelTabs[channelName:lower()]
end
function methods:OrganizeChannelTabs()
local order = {}
table.insert(order, self:GetChannelTab(ChatSettings.GeneralChannelName))
table.insert(order, self:GetChannelTab("System"))
for tabIndexName, tab in pairs(self.ChannelTabs) do
if (tab.ChannelName ~= ChatSettings.GeneralChannelName and tab.ChannelName ~= "System") then
table.insert(order, tab)
end
end
for index, tab in pairs(order) do
tab.GuiObject.Position = UDim2.new(index - 1, 0, 0, 0)
end
--// Dynamic tab resizing
self.GuiObjects.ScrollerSizer.Size = UDim2.new(1 / math.max(1, math.min(ChatSettings.ChannelsBarFullTabSize, self.NumTabs)), 0, 1, 0)
self:ScrollChannelsFrame(0)
end
function methods:ResizeChannelTabText(textSize)
for i, tab in pairs(self.ChannelTabs) do
tab:SetTextSize(textSize)
end
end
function methods:ScrollChannelsFrame(dir)
if (self.ScrollChannelsFrameLock) then return end
self.ScrollChannelsFrameLock = true
local tabNumber = ChatSettings.ChannelsBarFullTabSize
local newPageNum = self.CurPageNum + dir
if (newPageNum < 0) then
newPageNum = 0
elseif (newPageNum > 0 and newPageNum + tabNumber > self.NumTabs) then
newPageNum = self.NumTabs - tabNumber
end
self.CurPageNum = newPageNum
local tweenTime = 0.15
local endPos = UDim2.new(-self.CurPageNum, 0, 0, 0)
self.GuiObjects.PageLeftButton.Visible = (self.CurPageNum > 0)
self.GuiObjects.PageRightButton.Visible = (self.CurPageNum + tabNumber < self.NumTabs)
if dir == 0 then
self.ScrollChannelsFrameLock = false
return
end
local function UnlockFunc()
self.ScrollChannelsFrameLock = false
end
self:WaitUntilParentedCorrectly()
self.GuiObjects.ScrollerFrame:TweenPosition(endPos, Enum.EasingDirection.InOut, Enum.EasingStyle.Quad, tweenTime, true, UnlockFunc)
end
function methods:FadeOutBackground(duration)
for channelName, channelObj in pairs(self.ChannelTabs) do
channelObj:FadeOutBackground(duration)
end
self.AnimParams.Background_TargetTransparency = 1
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeInBackground(duration)
for channelName, channelObj in pairs(self.ChannelTabs) do
channelObj:FadeInBackground(duration)
end
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeOutText(duration)
for channelName, channelObj in pairs(self.ChannelTabs) do
channelObj:FadeOutText(duration)
end
end
function methods:FadeInText(duration)
for channelName, channelObj in pairs(self.ChannelTabs) do
channelObj:FadeInText(duration)
end
end
function methods:AnimGuiObjects()
self.GuiObjects.PageLeftButton.ImageTransparency = self.AnimParams.Background_CurrentTransparency
self.GuiObjects.PageRightButton.ImageTransparency = self.AnimParams.Background_CurrentTransparency
self.GuiObjects.PageLeftButtonArrow.ImageTransparency = self.AnimParams.Background_CurrentTransparency
self.GuiObjects.PageRightButtonArrow.ImageTransparency = self.AnimParams.Background_CurrentTransparency
end
function methods:InitializeAnimParams()
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_CurrentTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
end
function methods:Update(dtScale)
for channelName, channelObj in pairs(self.ChannelTabs) do
channelObj:Update(dtScale)
end
self.AnimParams.Background_CurrentTransparency = CurveUtil:Expt(
self.AnimParams.Background_CurrentTransparency,
self.AnimParams.Background_TargetTransparency,
self.AnimParams.Background_NormalizedExptValue,
dtScale
)
self:AnimGuiObjects()
end
--// ToDo: Move to common modules
function methods:WaitUntilParentedCorrectly()
while (not self.GuiObject:IsDescendantOf(game:GetService("Players").LocalPlayer)) do
self.GuiObject.AncestryChanged:wait()
end
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new()
local obj = setmetatable({}, methods)
obj.GuiObject = nil
obj.GuiObjects = {}
obj.ChannelTabs = {}
obj.NumTabs = 0
obj.CurPageNum = 0
obj.ScrollChannelsFrameLock = false
obj.AnimParams = {}
obj:InitializeAnimParams()
ChatSettings.SettingsChanged:connect(function(setting, value)
if (setting == "ChatChannelsTabTextSize") then
obj:ResizeChannelTabText(value)
end
end)
return obj
end
return module
================================================
FILE: src/Chat/ChatScript/ChatMain/ChannelsTab.lua
================================================
-- // FileName: ChannelsTab.lua
-- // Written by: Xsitsu
-- // Description: Channel tab button for selecting current channel and also displaying if currently selected.
local module = {}
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local modulesFolder = script.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
local function CreateGuiObjects()
local BaseFrame = Instance.new("Frame")
BaseFrame.Selectable = false
BaseFrame.Size = UDim2.new(1, 0, 1, 0)
BaseFrame.BackgroundTransparency = 1
local gapOffsetX = 1
local gapOffsetY = 1
local BackgroundFrame = Instance.new("Frame")
BackgroundFrame.Selectable = false
BackgroundFrame.Name = "BackgroundFrame"
BackgroundFrame.Size = UDim2.new(1, -gapOffsetX * 2, 1, -gapOffsetY * 2)
BackgroundFrame.Position = UDim2.new(0, gapOffsetX, 0, gapOffsetY)
BackgroundFrame.BackgroundTransparency = 1
BackgroundFrame.Parent = BaseFrame
local UnselectedFrame = Instance.new("Frame")
UnselectedFrame.Selectable = false
UnselectedFrame.Name = "UnselectedFrame"
UnselectedFrame.Size = UDim2.new(1, 0, 1, 0)
UnselectedFrame.Position = UDim2.new(0, 0, 0, 0)
UnselectedFrame.BorderSizePixel = 0
UnselectedFrame.BackgroundColor3 = ChatSettings.ChannelsTabUnselectedColor
UnselectedFrame.BackgroundTransparency = 0.6
UnselectedFrame.Parent = BackgroundFrame
local SelectedFrame = Instance.new("Frame")
SelectedFrame.Selectable = false
SelectedFrame.Name = "SelectedFrame"
SelectedFrame.Size = UDim2.new(1, 0, 1, 0)
SelectedFrame.Position = UDim2.new(0, 0, 0, 0)
SelectedFrame.BorderSizePixel = 0
SelectedFrame.BackgroundColor3 = ChatSettings.ChannelsTabSelectedColor
SelectedFrame.BackgroundTransparency = 1
SelectedFrame.Parent = BackgroundFrame
local SelectedFrameBackgroundImage = Instance.new("ImageLabel")
SelectedFrameBackgroundImage.Selectable = false
SelectedFrameBackgroundImage.Name = "BackgroundImage"
SelectedFrameBackgroundImage.BackgroundTransparency = 1
SelectedFrameBackgroundImage.BorderSizePixel = 0
SelectedFrameBackgroundImage.Size = UDim2.new(1, 0, 1, 0)
SelectedFrameBackgroundImage.Position = UDim2.new(0, 0, 0, 0)
SelectedFrameBackgroundImage.ScaleType = Enum.ScaleType.Slice
SelectedFrameBackgroundImage.Parent = SelectedFrame
SelectedFrameBackgroundImage.BackgroundTransparency = 0.6 - 1
local rate = 1.2 * 1
SelectedFrameBackgroundImage.BackgroundColor3 = Color3.fromRGB(78 * rate, 84 * rate, 96 * rate)
local borderXOffset = 2
local blueBarYSize = 4
local BlueBarLeft = Instance.new("ImageLabel")
BlueBarLeft.Selectable = false
BlueBarLeft.Size = UDim2.new(0.5, -borderXOffset, 0, blueBarYSize)
BlueBarLeft.BackgroundTransparency = 1
BlueBarLeft.ScaleType = Enum.ScaleType.Slice
BlueBarLeft.SliceCenter = Rect.new(3,3,32,21)
BlueBarLeft.Parent = SelectedFrame
local BlueBarRight = BlueBarLeft:Clone()
BlueBarRight.Parent = SelectedFrame
BlueBarLeft.Position = UDim2.new(0, borderXOffset, 1, -blueBarYSize)
BlueBarRight.Position = UDim2.new(0.5, 0, 1, -blueBarYSize)
BlueBarLeft.Image = "rbxasset://textures/ui/Settings/Slider/SelectedBarLeft.png"
BlueBarRight.Image = "rbxasset://textures/ui/Settings/Slider/SelectedBarRight.png"
BlueBarLeft.Name = "BlueBarLeft"
BlueBarRight.Name = "BlueBarRight"
local NameTag = Instance.new("TextButton")
NameTag.Selectable = ChatSettings.GamepadNavigationEnabled
NameTag.Size = UDim2.new(1, 0, 1, 0)
NameTag.Position = UDim2.new(0, 0, 0, 0)
NameTag.BackgroundTransparency = 1
NameTag.Font = ChatSettings.DefaultFont
NameTag.TextSize = ChatSettings.ChatChannelsTabTextSize
NameTag.TextColor3 = Color3.new(1, 1, 1)
NameTag.TextStrokeTransparency = 0.75
NameTag.Parent = BackgroundFrame
local NameTagNonSelect = NameTag:Clone()
local NameTagSelect = NameTag:Clone()
NameTagNonSelect.Parent = UnselectedFrame
NameTagSelect.Parent = SelectedFrame
NameTagNonSelect.Font = Enum.Font.SourceSans
NameTagNonSelect.Active = false
NameTagSelect.Active = false
local NewMessageIconFrame = Instance.new("Frame")
NewMessageIconFrame.Selectable = false
NewMessageIconFrame.Size = UDim2.new(0, 18, 0, 18)
NewMessageIconFrame.Position = UDim2.new(0.8, -9, 0.5, -9)
NewMessageIconFrame.BackgroundTransparency = 1
NewMessageIconFrame.Parent = BackgroundFrame
local NewMessageIcon = Instance.new("ImageLabel")
NewMessageIcon.Selectable = false
NewMessageIcon.Size = UDim2.new(1, 0, 1, 0)
NewMessageIcon.BackgroundTransparency = 1
NewMessageIcon.Image = "rbxasset://textures/ui/Chat/MessageCounter.png"
NewMessageIcon.Visible = false
NewMessageIcon.Parent = NewMessageIconFrame
local NewMessageIconText = Instance.new("TextLabel")
NewMessageIconText.Selectable = false
NewMessageIconText.BackgroundTransparency = 1
NewMessageIconText.Size = UDim2.new(0, 13, 0, 9)
NewMessageIconText.Position = UDim2.new(0.5, -7, 0.5, -7)
NewMessageIconText.Font = ChatSettings.DefaultFont
NewMessageIconText.TextSize = 14
NewMessageIconText.TextColor3 = Color3.new(1, 1, 1)
NewMessageIconText.Text = ""
NewMessageIconText.Parent = NewMessageIcon
return BaseFrame, NameTag, NameTagNonSelect, NameTagSelect, NewMessageIcon, UnselectedFrame, SelectedFrame
end
function methods:Destroy()
self.GuiObject:Destroy()
end
function methods:UpdateMessagePostedInChannel(ignoreActive)
if (self.Active and (ignoreActive ~= true)) then return end
local count = self.UnreadMessageCount + 1
self.UnreadMessageCount = count
local label = self.NewMessageIcon
label.Visible = true
label.TextLabel.Text = (count < 100) and tostring(count) or "!"
local tweenTime = 0.15
local tweenPosOffset = UDim2.new(0, 0, -0.1, 0)
local curPos = label.Position
local outPos = curPos + tweenPosOffset
local easingDirection = Enum.EasingDirection.Out
local easingStyle = Enum.EasingStyle.Quad
label.Position = UDim2.new(0, 0, -0.15, 0)
label:TweenPosition(UDim2.new(0, 0, 0, 0), easingDirection, easingStyle, tweenTime, true)
end
function methods:SetActive(active)
self.Active = active
self.UnselectedFrame.Visible = not active
self.SelectedFrame.Visible = active
if (active) then
self.UnreadMessageCount = 0
self.NewMessageIcon.Visible = false
self.NameTag.Font = Enum.Font.SourceSansBold
else
self.NameTag.Font = Enum.Font.SourceSans
end
end
function methods:SetTextSize(textSize)
self.NameTag.TextSize = textSize
end
function methods:FadeOutBackground(duration)
self.AnimParams.Background_TargetTransparency = 1
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeInBackground(duration)
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeOutText(duration)
self.AnimParams.Text_TargetTransparency = 1
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
self.AnimParams.TextStroke_TargetTransparency = 1
self.AnimParams.TextStroke_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeInText(duration)
self.AnimParams.Text_TargetTransparency = 0
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
self.AnimParams.TextStroke_TargetTransparency = 0.75
self.AnimParams.TextStroke_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:AnimGuiObjects()
self.UnselectedFrame.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
self.SelectedFrame.BackgroundImage.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
self.SelectedFrame.BlueBarLeft.ImageTransparency = self.AnimParams.Background_CurrentTransparency
self.SelectedFrame.BlueBarRight.ImageTransparency = self.AnimParams.Background_CurrentTransparency
self.NameTagNonSelect.TextTransparency = self.AnimParams.Background_CurrentTransparency
self.NameTagNonSelect.TextStrokeTransparency = self.AnimParams.Background_CurrentTransparency
self.NameTag.TextTransparency = self.AnimParams.Text_CurrentTransparency
self.NewMessageIcon.ImageTransparency = self.AnimParams.Text_CurrentTransparency
self.WhiteTextNewMessageNotification.TextTransparency = self.AnimParams.Text_CurrentTransparency
self.NameTagSelect.TextTransparency = self.AnimParams.Text_CurrentTransparency
self.NameTag.TextStrokeTransparency = self.AnimParams.TextStroke_CurrentTransparency
self.WhiteTextNewMessageNotification.TextStrokeTransparency = self.AnimParams.TextStroke_CurrentTransparency
self.NameTagSelect.TextStrokeTransparency = self.AnimParams.TextStroke_CurrentTransparency
end
function methods:InitializeAnimParams()
self.AnimParams.Text_TargetTransparency = 0
self.AnimParams.Text_CurrentTransparency = 0
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
self.AnimParams.TextStroke_TargetTransparency = 0.75
self.AnimParams.TextStroke_CurrentTransparency = 0.75
self.AnimParams.TextStroke_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_CurrentTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
end
function methods:Update(dtScale)
self.AnimParams.Background_CurrentTransparency = CurveUtil:Expt(
self.AnimParams.Background_CurrentTransparency,
self.AnimParams.Background_TargetTransparency,
self.AnimParams.Background_NormalizedExptValue,
dtScale
)
self.AnimParams.Text_CurrentTransparency = CurveUtil:Expt(
self.AnimParams.Text_CurrentTransparency,
self.AnimParams.Text_TargetTransparency,
self.AnimParams.Text_NormalizedExptValue,
dtScale
)
self.AnimParams.TextStroke_CurrentTransparency = CurveUtil:Expt(
self.AnimParams.TextStroke_CurrentTransparency,
self.AnimParams.TextStroke_TargetTransparency,
self.AnimParams.TextStroke_NormalizedExptValue,
dtScale
)
self:AnimGuiObjects()
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new(channelName)
local obj = setmetatable({}, methods)
local BaseFrame, NameTag, NameTagNonSelect, NameTagSelect, NewMessageIcon, UnselectedFrame, SelectedFrame = CreateGuiObjects()
obj.GuiObject = BaseFrame
obj.NameTag = NameTag
obj.NameTagNonSelect = NameTagNonSelect
obj.NameTagSelect = NameTagSelect
obj.NewMessageIcon = NewMessageIcon
obj.UnselectedFrame = UnselectedFrame
obj.SelectedFrame = SelectedFrame
obj.BlueBarLeft = SelectedFrame.BlueBarLeft
obj.BlueBarRight = SelectedFrame.BlueBarRight
obj.BackgroundImage = SelectedFrame.BackgroundImage
obj.WhiteTextNewMessageNotification = obj.NewMessageIcon.TextLabel
obj.ChannelName = channelName
obj.UnreadMessageCount = 0
obj.Active = false
obj.GuiObject.Name = "Frame_" .. obj.ChannelName
if (string.len(channelName) > ChatSettings.MaxChannelNameLength) then
channelName = string.sub(channelName, 1, ChatSettings.MaxChannelNameLength - 3) .. "..."
end
--obj.NameTag.Text = channelName
obj.NameTag.Text = ""
obj.NameTagNonSelect.Text = channelName
obj.NameTagSelect.Text = channelName
obj.AnimParams = {}
obj:InitializeAnimParams()
obj:AnimGuiObjects()
obj:SetActive(false)
return obj
end
return module
================================================
FILE: src/Chat/ChatScript/ChatMain/ChatBar.lua
================================================
-- // FileName: ChatBar.lua
-- // Written by: Xsitsu
-- // Description: Manages text typing and typing state.
local module = {}
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local TextService = game:GetService("TextService")
local LocalPlayer = Players.LocalPlayer
while not LocalPlayer do
Players.PlayerAdded:wait()
LocalPlayer = Players.LocalPlayer
end
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local modulesFolder = script.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
local commandModules = clientChatModules:WaitForChild("CommandModules")
local WhisperModule = require(commandModules:WaitForChild("Whisper"))
local MessageSender = require(modulesFolder:WaitForChild("MessageSender"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:CreateGuiObjects(targetParent)
self.ChatBarParentFrame = targetParent
local backgroundImagePixelOffset = 7
local textBoxPixelOffset = 5
local BaseFrame = Instance.new("Frame")
BaseFrame.Selectable = false
BaseFrame.Size = UDim2.new(1, 0, 1, 0)
BaseFrame.BackgroundTransparency = 0.6
BaseFrame.BorderSizePixel = 0
BaseFrame.BackgroundColor3 = ChatSettings.ChatBarBackGroundColor
BaseFrame.Parent = targetParent
local BoxFrame = Instance.new("Frame")
BoxFrame.Selectable = false
BoxFrame.Name = "BoxFrame"
BoxFrame.BackgroundTransparency = 0.6
BoxFrame.BorderSizePixel = 0
BoxFrame.BackgroundColor3 = ChatSettings.ChatBarBoxColor
BoxFrame.Size = UDim2.new(1, -backgroundImagePixelOffset * 2, 1, -backgroundImagePixelOffset * 2)
BoxFrame.Position = UDim2.new(0, backgroundImagePixelOffset, 0, backgroundImagePixelOffset)
BoxFrame.Parent = BaseFrame
local TextBoxHolderFrame = Instance.new("Frame")
TextBoxHolderFrame.BackgroundTransparency = 1
TextBoxHolderFrame.Size = UDim2.new(1, -textBoxPixelOffset * 2, 1, -textBoxPixelOffset * 2)
TextBoxHolderFrame.Position = UDim2.new(0, textBoxPixelOffset, 0, textBoxPixelOffset)
TextBoxHolderFrame.Parent = BoxFrame
local TextBox = Instance.new("TextBox")
TextBox.Selectable = ChatSettings.GamepadNavigationEnabled
TextBox.Name = "ChatBar"
TextBox.BackgroundTransparency = 1
TextBox.Size = UDim2.new(1, 0, 1, 0)
TextBox.Position = UDim2.new(0, 0, 0, 0)
TextBox.TextSize = ChatSettings.ChatBarTextSize
TextBox.Font = ChatSettings.ChatBarFont
TextBox.TextColor3 = ChatSettings.ChatBarTextColor
TextBox.TextTransparency = 0.4
TextBox.TextStrokeTransparency = 1
TextBox.ClearTextOnFocus = false
TextBox.TextXAlignment = Enum.TextXAlignment.Left
TextBox.TextYAlignment = Enum.TextYAlignment.Top
TextBox.TextWrapped = true
TextBox.Text = ""
TextBox.Parent = TextBoxHolderFrame
local MessageModeTextButton = Instance.new("TextButton")
MessageModeTextButton.Selectable = false
MessageModeTextButton.Name = "MessageMode"
MessageModeTextButton.BackgroundTransparency = 1
MessageModeTextButton.Position = UDim2.new(0, 0, 0, 0)
MessageModeTextButton.TextSize = ChatSettings.ChatBarTextSize
MessageModeTextButton.Font = ChatSettings.ChatBarFont
MessageModeTextButton.TextXAlignment = Enum.TextXAlignment.Left
MessageModeTextButton.TextWrapped = true
MessageModeTextButton.Text = ""
MessageModeTextButton.Size = UDim2.new(0, 0, 0, 0)
MessageModeTextButton.TextYAlignment = Enum.TextYAlignment.Center
MessageModeTextButton.TextColor3 = self:GetDefaultChannelNameColor()
MessageModeTextButton.Visible = true
MessageModeTextButton.Parent = TextBoxHolderFrame
local TextLabel = Instance.new("TextLabel")
TextLabel.Selectable = false
TextLabel.TextWrapped = true
TextLabel.BackgroundTransparency = 1
TextLabel.Size = TextBox.Size
TextLabel.Position = TextBox.Position
TextLabel.TextSize = TextBox.TextSize
TextLabel.Font = TextBox.Font
TextLabel.TextColor3 = TextBox.TextColor3
TextLabel.TextTransparency = TextBox.TextTransparency
TextLabel.TextStrokeTransparency = TextBox.TextStrokeTransparency
TextLabel.TextXAlignment = TextBox.TextXAlignment
TextLabel.TextYAlignment = TextBox.TextYAlignment
TextLabel.Text = "..."
TextLabel.Parent = TextBoxHolderFrame
self.GuiObject = BaseFrame
self.TextBox = TextBox
self.TextLabel = TextLabel
self.GuiObjects.BaseFrame = BaseFrame
self.GuiObjects.TextBoxFrame = BoxFrame
self.GuiObjects.TextBox = TextBox
self.GuiObjects.TextLabel = TextLabel
self.GuiObjects.MessageModeTextButton = MessageModeTextButton
self:AnimGuiObjects()
self:SetUpTextBoxEvents(TextBox, TextLabel, MessageModeTextButton)
if self.UserHasChatOff then
self:DoLockChatBar()
end
self.eGuiObjectsChanged:Fire()
end
-- Used to lock the chat bar when the user has chat turned off.
function methods:DoLockChatBar()
if self.TextLabel then
if LocalPlayer.UserId > 0 then
self.TextLabel.Text = ChatLocalization:Get(
"GameChat_ChatMessageValidator_SettingsError",
"To chat in game, turn on chat in your Privacy Settings."
)
else
self.TextLabel.Text = ChatLocalization:Get(
"GameChat_SwallowGuestChat_Message",
"Sign up to chat in game."
)
end
self:CalculateSize()
end
if self.TextBox then
self.TextBox.Active = false
self.TextBox.Focused:connect(function()
self.TextBox:ReleaseFocus()
end)
end
end
function methods:SetUpTextBoxEvents(TextBox, TextLabel, MessageModeTextButton)
-- Clean up events from a previous setup.
for name, conn in pairs(self.TextBoxConnections) do
conn:disconnect()
self.TextBoxConnections[name] = nil
end
--// Code for getting back into general channel from other target channel when pressing backspace.
self.TextBoxConnections.UserInputBegan = UserInputService.InputBegan:connect(function(inputObj, gpe)
if (inputObj.KeyCode == Enum.KeyCode.Backspace) then
if (self:IsFocused() and TextBox.Text == "") then
self:SetChannelTarget(ChatSettings.GeneralChannelName)
end
end
end)
self.TextBoxConnections.TextBoxChanged = TextBox.Changed:connect(function(prop)
if prop == "AbsoluteSize" then
self:CalculateSize()
return
end
if prop ~= "Text" then
return
end
self:CalculateSize()
if (string.len(TextBox.Text) > ChatSettings.MaximumMessageLength) then
TextBox.Text = string.sub(TextBox.Text, 1, ChatSettings.MaximumMessageLength)
return
end
if not self.InCustomState then
local customState = self.CommandProcessor:ProcessInProgressChatMessage(TextBox.Text, self.ChatWindow, self)
if customState then
self.InCustomState = true
self.CustomState = customState
end
else
self.CustomState:TextUpdated()
end
end)
local function UpdateOnFocusStatusChanged(isFocused)
if isFocused or TextBox.Text ~= "" then
TextLabel.Visible = false
else
TextLabel.Visible = true
end
end
self.TextBoxConnections.MessageModeClick = MessageModeTextButton.MouseButton1Click:connect(function()
if MessageModeTextButton.Text ~= "" then
self:SetChannelTarget(ChatSettings.GeneralChannelName)
end
end)
self.TextBoxConnections.TextBoxFocused = TextBox.Focused:connect(function()
if not self.UserHasChatOff then
self:CalculateSize()
UpdateOnFocusStatusChanged(true)
end
end)
self.TextBoxConnections.TextBoxFocusLost = TextBox.FocusLost:connect(function(enterPressed, inputObject)
self:CalculateSize()
if (inputObject and inputObject.KeyCode == Enum.KeyCode.Escape) then
TextBox.Text = ""
end
UpdateOnFocusStatusChanged(false)
end)
end
function methods:GetTextBox()
return self.TextBox
end
function methods:GetMessageModeTextButton()
return self.GuiObjects.MessageModeTextButton
end
-- Deprecated in favour of GetMessageModeTextButton
-- Retained for compatibility reasons.
function methods:GetMessageModeTextLabel()
return self:GetMessageModeTextButton()
end
function methods:IsFocused()
if self.UserHasChatOff then
return false
end
return self:GetTextBox():IsFocused()
end
function methods:GetVisible()
return self.GuiObject.Visible
end
function methods:CaptureFocus()
if not self.UserHasChatOff then
self:GetTextBox():CaptureFocus()
end
end
function methods:ReleaseFocus(didRelease)
self:GetTextBox():ReleaseFocus(didRelease)
end
function methods:ResetText()
self:GetTextBox().Text = ""
end
function methods:SetText(text)
self:GetTextBox().Text = text
end
function methods:GetEnabled()
return self.GuiObject.Visible
end
function methods:SetEnabled(enabled)
if self.UserHasChatOff then
-- The chat bar can not be removed if a user has chat turned off so that
-- the chat bar can display a message explaining that chat is turned off.
self.GuiObject.Visible = true
else
self.GuiObject.Visible = enabled
end
end
function methods:SetTextLabelText(text)
if not self.UserHasChatOff then
self.TextLabel.Text = text
end
end
function methods:SetTextBoxText(text)
self.TextBox.Text = text
end
function methods:GetTextBoxText()
return self.TextBox.Text
end
function methods:ResetSize()
self.TargetYSize = 0
self:TweenToTargetYSize()
end
local function measureSize(textObj)
return TextService:GetTextSize(
textObj.Text,
textObj.TextSize,
textObj.Font,
Vector2.new(textObj.AbsoluteSize.X, 10000)
)
end
function methods:CalculateSize()
if self.CalculatingSizeLock then
return
end
self.CalculatingSizeLock = true
local textSize = nil
local bounds = nil
if self:IsFocused() or self.TextBox.Text ~= "" then
textSize = self.TextBox.TextSize
bounds = measureSize(self.TextBox).Y
else
textSize = self.TextLabel.TextSize
bounds = measureSize(self.TextLabel).Y
end
local newTargetYSize = bounds - textSize
if (self.TargetYSize ~= newTargetYSize) then
self.TargetYSize = newTargetYSize
self:TweenToTargetYSize()
end
self.CalculatingSizeLock = false
end
function methods:TweenToTargetYSize()
local endSize = UDim2.new(1, 0, 1, self.TargetYSize)
local curSize = self.GuiObject.Size
local curAbsoluteSizeY = self.GuiObject.AbsoluteSize.Y
self.GuiObject.Size = endSize
local endAbsoluteSizeY = self.GuiObject.AbsoluteSize.Y
self.GuiObject.Size = curSize
local pixelDistance = math.abs(endAbsoluteSizeY - curAbsoluteSizeY)
local tweeningTime = math.min(1, (pixelDistance * (1 / self.TweenPixelsPerSecond))) -- pixelDistance * (seconds per pixels)
local success = pcall(function() self.GuiObject:TweenSize(endSize, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, tweeningTime, true) end)
if (not success) then
self.GuiObject.Size = endSize
end
end
function methods:SetTextSize(textSize)
if not self:IsInCustomState() then
if self.TextBox then
self.TextBox.TextSize = textSize
end
if self.TextLabel then
self.TextLabel.TextSize = textSize
end
end
end
function methods:GetDefaultChannelNameColor()
if ChatSettings.DefaultChannelNameColor then
return ChatSettings.DefaultChannelNameColor
end
return Color3.fromRGB(35, 76, 142)
end
function methods:SetChannelTarget(targetChannel)
local messageModeTextButton = self.GuiObjects.MessageModeTextButton
local textBox = self.TextBox
local textLabel = self.TextLabel
self.TargetChannel = targetChannel
if not self:IsInCustomState() then
if targetChannel ~= ChatSettings.GeneralChannelName then
messageModeTextButton.Size = UDim2.new(0, 1000, 1, 0)
messageModeTextButton.Text = string.format("[%s] ", targetChannel)
local channelNameColor = self:GetChannelNameColor(targetChannel)
if channelNameColor then
messageModeTextButton.TextColor3 = channelNameColor
else
messageModeTextButton.TextColor3 = self:GetDefaultChannelNameColor()
end
local xSize = messageModeTextButton.TextBounds.X
messageModeTextButton.Size = UDim2.new(0, xSize, 1, 0)
textBox.Size = UDim2.new(1, -xSize, 1, 0)
textBox.Position = UDim2.new(0, xSize, 0, 0)
textLabel.Size = UDim2.new(1, -xSize, 1, 0)
textLabel.Position = UDim2.new(0, xSize, 0, 0)
else
messageModeTextButton.Text = ""
messageModeTextButton.Size = UDim2.new(0, 0, 0, 0)
textBox.Size = UDim2.new(1, 0, 1, 0)
textBox.Position = UDim2.new(0, 0, 0, 0)
textLabel.Size = UDim2.new(1, 0, 1, 0)
textLabel.Position = UDim2.new(0, 0, 0, 0)
end
end
end
function methods:IsInCustomState()
return self.InCustomState
end
function methods:ResetCustomState()
if self.InCustomState then
self.CustomState:Destroy()
self.CustomState = nil
self.InCustomState = false
self.ChatBarParentFrame:ClearAllChildren()
self:CreateGuiObjects(self.ChatBarParentFrame)
self:SetTextLabelText(
ChatLocalization:Get(
"GameChat_ChatMain_ChatBarText",
'To chat click here or press "/" key'
)
)
end
end
function methods:EnterWhisperState(player)
self:ResetCustomState()
self:CaptureFocus()
if WhisperModule.CustomStateCreator then
self.CustomState = WhisperModule.CustomStateCreator(
player,
self.ChatWindow,
self,
ChatSettings
)
self.InCustomState = true
else
self:SetText("/w " .. player.Name)
end
end
function methods:GetCustomMessage()
if self.InCustomState then
return self.CustomState:GetMessage()
end
return nil
end
function methods:CustomStateProcessCompletedMessage(message)
if self.InCustomState then
return self.CustomState:ProcessCompletedMessage()
end
return false
end
function methods:FadeOutBackground(duration)
self.AnimParams.Background_TargetTransparency = 1
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
self:FadeOutText(duration)
end
function methods:FadeInBackground(duration)
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
self:FadeInText(duration)
end
function methods:FadeOutText(duration)
self.AnimParams.Text_TargetTransparency = 1
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeInText(duration)
self.AnimParams.Text_TargetTransparency = 0.4
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:AnimGuiObjects()
self.GuiObject.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
self.GuiObjects.TextBoxFrame.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
self.GuiObjects.TextLabel.TextTransparency = self.AnimParams.Text_CurrentTransparency
self.GuiObjects.TextBox.TextTransparency = self.AnimParams.Text_CurrentTransparency
self.GuiObjects.MessageModeTextButton.TextTransparency = self.AnimParams.Text_CurrentTransparency
end
function methods:InitializeAnimParams()
self.AnimParams.Text_TargetTransparency = 0.4
self.AnimParams.Text_CurrentTransparency = 0.4
self.AnimParams.Text_NormalizedExptValue = 1
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_CurrentTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = 1
end
function methods:Update(dtScale)
self.AnimParams.Text_CurrentTransparency = CurveUtil:Expt(
self.AnimParams.Text_CurrentTransparency,
self.AnimParams.Text_TargetTransparency,
self.AnimParams.Text_NormalizedExptValue,
dtScale
)
self.AnimParams.Background_CurrentTransparency = CurveUtil:Expt(
self.AnimParams.Background_CurrentTransparency,
self.AnimParams.Background_TargetTransparency,
self.AnimParams.Background_NormalizedExptValue,
dtScale
)
self:AnimGuiObjects()
end
function methods:SetChannelNameColor(channelName, channelNameColor)
self.ChannelNameColors[channelName] = channelNameColor
if self.GuiObjects.MessageModeTextButton.Text == channelName then
self.GuiObjects.MessageModeTextButton.TextColor3 = channelNameColor
end
end
function methods:GetChannelNameColor(channelName)
return self.ChannelNameColors[channelName]
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new(CommandProcessor, ChatWindow)
local obj = setmetatable({}, methods)
obj.GuiObject = nil
obj.ChatBarParentFrame = nil
obj.TextBox = nil
obj.TextLabel = nil
obj.GuiObjects = {}
obj.eGuiObjectsChanged = Instance.new("BindableEvent")
obj.GuiObjectsChanged = obj.eGuiObjectsChanged.Event
obj.TextBoxConnections = {}
obj.InCustomState = false
obj.CustomState = nil
obj.TargetChannel = nil
obj.CommandProcessor = CommandProcessor
obj.ChatWindow = ChatWindow
obj.TweenPixelsPerSecond = 500
obj.TargetYSize = 0
obj.AnimParams = {}
obj.CalculatingSizeLock = false
obj.ChannelNameColors = {}
obj.UserHasChatOff = false
obj:InitializeAnimParams()
ChatSettings.SettingsChanged:connect(function(setting, value)
if (setting == "ChatBarTextSize") then
obj:SetTextSize(value)
end
end)
coroutine.wrap(function()
local success, canLocalUserChat = pcall(function()
return Chat:CanUserChatAsync(LocalPlayer.UserId)
end)
local canChat = success and (RunService:IsStudio() or canLocalUserChat)
if canChat == false then
obj.UserHasChatOff = true
obj:DoLockChatBar()
end
end)()
return obj
end
return module
================================================
FILE: src/Chat/ChatScript/ChatMain/ChatChannel.lua
================================================
-- // FileName: ChatChannel.lua
-- // Written by: Xsitsu
-- // Description: ChatChannel class for handling messages being added and removed from the chat channel.
local module = {}
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local modulesFolder = script.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:Destroy()
self.Destroyed = true
end
function methods:SetActive(active)
if active == self.Active then
return
end
if active == false then
self.MessageLogDisplay:Clear()
else
self.MessageLogDisplay:SetCurrentChannelName(self.Name)
for i = 1, #self.MessageLog do
self.MessageLogDisplay:AddMessage(self.MessageLog[i])
end
end
self.Active = active
end
function methods:UpdateMessageFiltered(messageData)
local searchIndex = 1
local searchTable = self.MessageLog
local messageObj = nil
while (#searchTable >= searchIndex) do
local obj = searchTable[searchIndex]
if (obj.ID == messageData.ID) then
messageObj = obj
break
end
searchIndex = searchIndex + 1
end
if messageObj then
messageObj.Message = messageData.Message
messageObj.IsFiltered = true
if self.Active then
self.MessageLogDisplay:UpdateMessageFiltered(messageObj)
end
else
-- We have not seen this filtered message before, but we should still add it to our log.
self:AddMessageToChannelByTimeStamp(messageData)
end
end
function methods:AddMessageToChannel(messageData)
table.insert(self.MessageLog, messageData)
if self.Active then
self.MessageLogDisplay:AddMessage(messageData)
end
if #self.MessageLog > ChatSettings.MessageHistoryLengthPerChannel then
self:RemoveLastMessageFromChannel()
end
end
function methods:InternalAddMessageAtTimeStamp(messageData)
for i = 1, #self.MessageLog do
if messageData.Time < self.MessageLog[i].Time then
table.insert(self.MessageLog, i, messageData)
return
end
end
table.insert(self.MessageLog, messageData)
end
function methods:AddMessagesToChannelByTimeStamp(messageLog, startIndex)
for i = startIndex, #messageLog do
self:InternalAddMessageAtTimeStamp(messageLog[i])
end
while #self.MessageLog > ChatSettings.MessageHistoryLengthPerChannel do
table.remove(self.MessageLog, 1)
end
if self.Active then
self.MessageLogDisplay:Clear()
for i = 1, #self.MessageLog do
self.MessageLogDisplay:AddMessage(self.MessageLog[i])
end
end
end
function methods:AddMessageToChannelByTimeStamp(messageData)
if #self.MessageLog >= 1 then
-- These are the fast cases to evalutate.
if self.MessageLog[1].Time > messageData.Time then
return
elseif messageData.Time >= self.MessageLog[#self.MessageLog].Time then
self:AddMessageToChannel(messageData)
return
end
for i = 1, #self.MessageLog do
if messageData.Time < self.MessageLog[i].Time then
table.insert(self.MessageLog, i, messageData)
if #self.MessageLog > ChatSettings.MessageHistoryLengthPerChannel then
self:RemoveLastMessageFromChannel()
end
if self.Active then
self.MessageLogDisplay:AddMessageAtIndex(messageData, i)
end
return
end
end
else
self:AddMessageToChannel(messageData)
end
end
function methods:RemoveLastMessageFromChannel()
table.remove(self.MessageLog, 1)
if self.Active then
self.MessageLogDisplay:RemoveLastMessage()
end
end
function methods:ClearMessageLog()
self.MessageLog = {}
if self.Active then
self.MessageLogDisplay:Clear()
end
end
function methods:RegisterChannelTab(tab)
self.ChannelTab = tab
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new(channelName, messageLogDisplay)
local obj = setmetatable({}, methods)
obj.Destroyed = false
obj.Active = false
obj.MessageLog = {}
obj.MessageLogDisplay = messageLogDisplay
obj.ChannelTab = nil
obj.Name = channelName
return obj
end
return module
================================================
FILE: src/Chat/ChatScript/ChatMain/ChatWindow.lua
================================================
-- // FileName: ChatWindow.lua
-- // Written by: Xsitsu
-- // Description: Main GUI window piece. Manages ChatBar, ChannelsBar, and ChatChannels.
local module = {}
local Players = game:GetService("Players")
local Chat = game:GetService("Chat")
local LocalPlayer = Players.LocalPlayer
local PlayerGui = LocalPlayer:WaitForChild("PlayerGui")
local PHONE_SCREEN_WIDTH = 640
local TABLET_SCREEN_WIDTH = 1024
local DEVICE_PHONE = 1
local DEVICE_TABLET = 2
local DEVICE_DESKTOP = 3
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local modulesFolder = script.Parent
local moduleChatChannel = require(modulesFolder:WaitForChild("ChatChannel"))
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function getClassicChatEnabled()
if ChatSettings.ClassicChatEnabled ~= nil then
return ChatSettings.ClassicChatEnabled
end
return Players.ClassicChat
end
function getBubbleChatEnabled()
if ChatSettings.BubbleChatEnabled ~= nil then
return ChatSettings.BubbleChatEnabled
end
return Players.BubbleChat
end
function bubbleChatOnly()
return not getClassicChatEnabled() and getBubbleChatEnabled()
end
-- only merge property defined on target
function mergeProps(source, target)
if not source or not target then return end
for prop, value in pairs(source) do
if target[prop] ~= nil then
target[prop] = value
end
end
end
function methods:CreateGuiObjects(targetParent)
local userDefinedChatWindowStyle
pcall(function()
userDefinedChatWindowStyle= Chat:InvokeChatCallback(Enum.ChatCallbackType.OnCreatingChatWindow, nil)
end)
-- merge the userdefined settings with the ChatSettings
mergeProps(userDefinedChatWindowStyle, ChatSettings)
local BaseFrame = Instance.new("Frame")
BaseFrame.BackgroundTransparency = 1
BaseFrame.Active = ChatSettings.WindowDraggable
BaseFrame.Parent = targetParent
BaseFrame.AutoLocalize = false
local ChatBarParentFrame = Instance.new("Frame")
ChatBarParentFrame.Selectable = false
ChatBarParentFrame.Name = "ChatBarParentFrame"
ChatBarParentFrame.BackgroundTransparency = 1
ChatBarParentFrame.Parent = BaseFrame
local ChannelsBarParentFrame = Instance.new("Frame")
ChannelsBarParentFrame.Selectable = false
ChannelsBarParentFrame.Name = "ChannelsBarParentFrame"
ChannelsBarParentFrame.BackgroundTransparency = 1
ChannelsBarParentFrame.Position = UDim2.new(0, 0, 0, 0)
ChannelsBarParentFrame.Parent = BaseFrame
local ChatChannelParentFrame = Instance.new("Frame")
ChatChannelParentFrame.Selectable = false
ChatChannelParentFrame.Name = "ChatChannelParentFrame"
ChatChannelParentFrame.BackgroundTransparency = 1
ChatChannelParentFrame.BackgroundColor3 = ChatSettings.BackGroundColor
ChatChannelParentFrame.BackgroundTransparency = 0.6
ChatChannelParentFrame.BorderSizePixel = 0
ChatChannelParentFrame.Parent = BaseFrame
local ChatResizerFrame = Instance.new("ImageButton")
ChatResizerFrame.Selectable = false
ChatResizerFrame.Image = ""
ChatResizerFrame.BackgroundTransparency = 0.6
ChatResizerFrame.BorderSizePixel = 0
ChatResizerFrame.Visible = false
ChatResizerFrame.BackgroundColor3 = ChatSettings.BackGroundColor
ChatResizerFrame.Active = true
if bubbleChatOnly() then
ChatResizerFrame.Position = UDim2.new(1, -ChatResizerFrame.AbsoluteSize.X, 0, 0)
else
ChatResizerFrame.Position = UDim2.new(1, -ChatResizerFrame.AbsoluteSize.X, 1, -ChatResizerFrame.AbsoluteSize.Y)
end
ChatResizerFrame.Parent = BaseFrame
local ResizeIcon = Instance.new("ImageLabel")
ResizeIcon.Selectable = false
ResizeIcon.Size = UDim2.new(0.8, 0, 0.8, 0)
ResizeIcon.Position = UDim2.new(0.2, 0, 0.2, 0)
ResizeIcon.BackgroundTransparency = 1
ResizeIcon.Image = "rbxassetid://261880743"
ResizeIcon.Parent = ChatResizerFrame
local function GetScreenGuiParent()
--// Travel up parent list until you find the ScreenGui that the chat window is parented to
local screenGuiParent = BaseFrame
while (screenGuiParent and not screenGuiParent:IsA("ScreenGui")) do
screenGuiParent = screenGuiParent.Parent
end
return screenGuiParent
end
local deviceType = DEVICE_DESKTOP
local screenGuiParent = GetScreenGuiParent()
if (screenGuiParent.AbsoluteSize.X <= PHONE_SCREEN_WIDTH) then
deviceType = DEVICE_PHONE
elseif (screenGuiParent.AbsoluteSize.X <= TABLET_SCREEN_WIDTH) then
deviceType = DEVICE_TABLET
end
local checkSizeLock = false
local function doCheckSizeBounds()
if (checkSizeLock) then return end
checkSizeLock = true
if (not BaseFrame:IsDescendantOf(PlayerGui)) then return end
local screenGuiParent = GetScreenGuiParent()
local minWinSize = ChatSettings.MinimumWindowSize
local maxWinSize = ChatSettings.MaximumWindowSize
local forceMinY = ChannelsBarParentFrame.AbsoluteSize.Y + ChatBarParentFrame.AbsoluteSize.Y
local minSizePixelX = (minWinSize.X.Scale * screenGuiParent.AbsoluteSize.X) + minWinSize.X.Offset
local minSizePixelY = math.max((minWinSize.Y.Scale * screenGuiParent.AbsoluteSize.Y) + minWinSize.Y.Offset, forceMinY)
local maxSizePixelX = (maxWinSize.X.Scale * screenGuiParent.AbsoluteSize.X) + maxWinSize.X.Offset
local maxSizePixelY = (maxWinSize.Y.Scale * screenGuiParent.AbsoluteSize.Y) + maxWinSize.Y.Offset
local absSizeX = BaseFrame.AbsoluteSize.X
local absSizeY = BaseFrame.AbsoluteSize.Y
if (absSizeX < minSizePixelX) then
local offset = UDim2.new(0, minSizePixelX - absSizeX, 0, 0)
BaseFrame.Size = BaseFrame.Size + offset
elseif (absSizeX > maxSizePixelX) then
local offset = UDim2.new(0, maxSizePixelX - absSizeX, 0, 0)
BaseFrame.Size = BaseFrame.Size + offset
end
if (absSizeY < minSizePixelY) then
local offset = UDim2.new(0, 0, 0, minSizePixelY - absSizeY)
BaseFrame.Size = BaseFrame.Size + offset
elseif (absSizeY > maxSizePixelY) then
local offset = UDim2.new(0, 0, 0, maxSizePixelY - absSizeY)
BaseFrame.Size = BaseFrame.Size + offset
end
local xScale = BaseFrame.AbsoluteSize.X / screenGuiParent.AbsoluteSize.X
local yScale = BaseFrame.AbsoluteSize.Y / screenGuiParent.AbsoluteSize.Y
BaseFrame.Size = UDim2.new(xScale, 0, yScale, 0)
checkSizeLock = false
end
BaseFrame.Changed:connect(function(prop)
if (prop == "AbsoluteSize") then
doCheckSizeBounds()
end
end)
ChatResizerFrame.DragBegin:connect(function(startUdim)
BaseFrame.Draggable = false
end)
local function UpdatePositionFromDrag(atPos)
if ChatSettings.WindowDraggable == false and ChatSettings.WindowResizable == false then
return
end
local newSize = atPos - BaseFrame.AbsolutePosition + ChatResizerFrame.AbsoluteSize
BaseFrame.Size = UDim2.new(0, newSize.X, 0, newSize.Y)
if bubbleChatOnly() then
ChatResizerFrame.Position = UDim2.new(1, -ChatResizerFrame.AbsoluteSize.X, 0, 0)
else
ChatResizerFrame.Position = UDim2.new(1, -ChatResizerFrame.AbsoluteSize.X, 1, -ChatResizerFrame.AbsoluteSize.Y)
end
end
ChatResizerFrame.DragStopped:connect(function(endX, endY)
BaseFrame.Draggable = ChatSettings.WindowDraggable
--UpdatePositionFromDrag(Vector2.new(endX, endY))
end)
local resizeLock = false
ChatResizerFrame.Changed:connect(function(prop)
if (prop == "AbsolutePosition" and not BaseFrame.Draggable) then
if (resizeLock) then return end
resizeLock = true
UpdatePositionFromDrag(ChatResizerFrame.AbsolutePosition)
resizeLock = false
end
end)
local function CalculateChannelsBarPixelSize(textSize)
if (deviceType == DEVICE_PHONE) then
textSize = textSize or ChatSettings.ChatChannelsTabTextSizePhone
else
textSize = textSize or ChatSettings.ChatChannelsTabTextSize
end
local channelsBarTextYSize = textSize
local chatChannelYSize = math.max(32, channelsBarTextYSize + 8) + 2
return chatChannelYSize
end
local function CalculateChatBarPixelSize(textSize)
if (deviceType == DEVICE_PHONE) then
textSize = textSize or ChatSettings.ChatBarTextSizePhone
else
textSize = textSize or ChatSettings.ChatBarTextSize
end
local chatBarTextSizeY = textSize
local chatBarYSize = chatBarTextSizeY + (7 * 2) + (5 * 2)
return chatBarYSize
end
if bubbleChatOnly() then
ChatBarParentFrame.Position = UDim2.new(0, 0, 0, 0)
ChannelsBarParentFrame.Visible = false
ChannelsBarParentFrame.Active = false
ChatChannelParentFrame.Visible = false
ChatChannelParentFrame.Active = false
local useXScale = 0
local useXOffset = 0
local screenGuiParent = GetScreenGuiParent()
if (deviceType == DEVICE_PHONE) then
useXScale = ChatSettings.DefaultWindowSizePhone.X.Scale
useXOffset = ChatSettings.DefaultWindowSizePhone.X.Offset
elseif (deviceType == DEVICE_TABLET) then
useXScale = ChatSettings.DefaultWindowSizeTablet.X.Scale
useXOffset = ChatSettings.DefaultWindowSizeTablet.X.Offset
else
useXScale = ChatSettings.DefaultWindowSizeTablet.X.Scale
useXOffset = ChatSettings.DefaultWindowSizeTablet.X.Offset
end
local chatBarYSize = CalculateChatBarPixelSize()
BaseFrame.Size = UDim2.new(useXScale, useXOffset, 0, chatBarYSize)
BaseFrame.Position = ChatSettings.DefaultWindowPosition
else
local screenGuiParent = GetScreenGuiParent()
if (deviceType == DEVICE_PHONE) then
BaseFrame.Size = ChatSettings.DefaultWindowSizePhone
elseif (deviceType == DEVICE_TABLET) then
BaseFrame.Size = ChatSettings.DefaultWindowSizeTablet
else
BaseFrame.Size = ChatSettings.DefaultWindowSizeDesktop
end
BaseFrame.Position = ChatSettings.DefaultWindowPosition
end
if (deviceType == DEVICE_PHONE) then
ChatSettings.ChatWindowTextSize = ChatSettings.ChatWindowTextSizePhone
ChatSettings.ChatChannelsTabTextSize = ChatSettings.ChatChannelsTabTextSizePhone
ChatSettings.ChatBarTextSize = ChatSettings.ChatBarTextSizePhone
end
local function UpdateDraggable(enabled)
BaseFrame.Active = enabled
BaseFrame.Draggable = enabled
end
local function UpdateResizable(enabled)
ChatResizerFrame.Visible = enabled
ChatResizerFrame.Draggable = enabled
local frameSizeY = ChatBarParentFrame.Size.Y.Offset
if (enabled) then
ChatBarParentFrame.Size = UDim2.new(1, -frameSizeY - 2, 0, frameSizeY)
if not bubbleChatOnly() then
ChatBarParentFrame.Position = UDim2.new(0, 0, 1, -frameSizeY)
end
else
ChatBarParentFrame.Size = UDim2.new(1, 0, 0, frameSizeY)
if not bubbleChatOnly() then
ChatBarParentFrame.Position = UDim2.new(0, 0, 1, -frameSizeY)
end
end
end
local function UpdateChatChannelParentFrameSize()
local channelsBarSize = CalculateChannelsBarPixelSize()
local chatBarSize = CalculateChatBarPixelSize()
if (ChatSettings.ShowChannelsBar) then
ChatChannelParentFrame.Size = UDim2.new(1, 0, 1, -(channelsBarSize + chatBarSize + 2 + 2))
ChatChannelParentFrame.Position = UDim2.new(0, 0, 0, channelsBarSize + 2)
else
ChatChannelParentFrame.Size = UDim2.new(1, 0, 1, -(chatBarSize + 2 + 2))
ChatChannelParentFrame.Position = UDim2.new(0, 0, 0, 2)
end
end
local function UpdateChatChannelsTabTextSize(size)
local channelsBarSize = CalculateChannelsBarPixelSize(size)
ChannelsBarParentFrame.Size = UDim2.new(1, 0, 0, channelsBarSize)
UpdateChatChannelParentFrameSize()
end
local function UpdateChatBarTextSize(size)
local chatBarSize = CalculateChatBarPixelSize(size)
ChatBarParentFrame.Size = UDim2.new(1, 0, 0, chatBarSize)
if not bubbleChatOnly() then
ChatBarParentFrame.Position = UDim2.new(0, 0, 1, -chatBarSize)
end
ChatResizerFrame.Size = UDim2.new(0, chatBarSize, 0, chatBarSize)
ChatResizerFrame.Position = UDim2.new(1, -chatBarSize, 1, -chatBarSize)
UpdateChatChannelParentFrameSize()
UpdateResizable(ChatSettings.WindowResizable)
end
local function UpdateShowChannelsBar(enabled)
ChannelsBarParentFrame.Visible = enabled
UpdateChatChannelParentFrameSize()
end
UpdateChatChannelsTabTextSize(ChatSettings.ChatChannelsTabTextSize)
UpdateChatBarTextSize(ChatSettings.ChatBarTextSize)
UpdateDraggable(ChatSettings.WindowDraggable)
UpdateResizable(ChatSettings.WindowResizable)
UpdateShowChannelsBar(ChatSettings.ShowChannelsBar)
ChatSettings.SettingsChanged:connect(function(setting, value)
if (setting == "WindowDraggable") then
UpdateDraggable(value)
elseif (setting == "WindowResizable") then
UpdateResizable(value)
elseif (setting == "ChatChannelsTabTextSize") then
UpdateChatChannelsTabTextSize(value)
elseif (setting == "ChatBarTextSize") then
UpdateChatBarTextSize(value)
elseif (setting == "ShowChannelsBar") then
UpdateShowChannelsBar(value)
end
end)
self.GuiObject = BaseFrame
self.GuiObjects.BaseFrame = BaseFrame
self.GuiObjects.ChatBarParentFrame = ChatBarParentFrame
self.GuiObjects.ChannelsBarParentFrame = ChannelsBarParentFrame
self.GuiObjects.ChatChannelParentFrame = ChatChannelParentFrame
self.GuiObjects.ChatResizerFrame = ChatResizerFrame
self.GuiObjects.ResizeIcon = ResizeIcon
self:AnimGuiObjects()
end
function methods:GetChatBar()
return self.ChatBar
end
function methods:RegisterChatBar(ChatBar)
self.ChatBar = ChatBar
self.ChatBar:CreateGuiObjects(self.GuiObjects.ChatBarParentFrame)
end
function methods:RegisterChannelsBar(ChannelsBar)
self.ChannelsBar = ChannelsBar
self.ChannelsBar:CreateGuiObjects(self.GuiObjects.ChannelsBarParentFrame)
end
function methods:RegisterMessageLogDisplay(MessageLogDisplay)
self.MessageLogDisplay = MessageLogDisplay
self.MessageLogDisplay.GuiObject.Parent = self.GuiObjects.ChatChannelParentFrame
end
function methods:AddChannel(channelName)
if (self:GetChannel(channelName)) then
error("Channel '" .. channelName .. "' already exists!")
return
end
local channel = moduleChatChannel.new(channelName, self.MessageLogDisplay)
self.Channels[channelName:lower()] = channel
channel:SetActive(false)
local tab = self.ChannelsBar:AddChannelTab(channelName)
tab.NameTag.MouseButton1Click:connect(function()
self:SwitchCurrentChannel(channelName)
end)
channel:RegisterChannelTab(tab)
return channel
end
function methods:GetFirstChannel()
--// Channels are not indexed numerically, so this function is necessary.
--// Grabs and returns the first channel it happens to, or nil if none exist.
for i, v in pairs(self.Channels) do
return v
end
return nil
end
function methods:RemoveChannel(channelName)
if (not self:GetChannel(channelName)) then
error("Channel '" .. channelName .. "' does not exist!")
end
local indexName = channelName:lower()
local needsChannelSwitch = false
if (self.Channels[indexName] == self:GetCurrentChannel()) then
needsChannelSwitch = true
self:SwitchCurrentChannel(nil)
end
self.Channels[indexName]:Destroy()
self.Channels[indexName] = nil
self.ChannelsBar:RemoveChannelTab(channelName)
if (needsChannelSwitch) then
local generalChannelExists = (self:GetChannel(ChatSettings.GeneralChannelName) ~= nil)
local removingGeneralChannel = (indexName == ChatSettings.GeneralChannelName:lower())
local targetSwitchChannel = nil
if (generalChannelExists and not removingGeneralChannel) then
targetSwitchChannel = ChatSettings.GeneralChannelName
else
local firstChannel = self:GetFirstChannel()
targetSwitchChannel = (firstChannel and firstChannel.Name or nil)
end
self:SwitchCurrentChannel(targetSwitchChannel)
end
if not ChatSettings.ShowChannelsBar then
if self.ChatBar.TargetChannel == channelName then
self.ChatBar:SetChannelTarget(ChatSettings.GeneralChannelName)
end
end
end
function methods:GetChannel(channelName)
return channelName and self.Channels[channelName:lower()] or nil
end
function methods:GetTargetMessageChannel()
if (not ChatSettings.ShowChannelsBar) then
return self.ChatBar.TargetChannel
else
local curChannel = self:GetCurrentChannel()
return curChannel and curChannel.Name
end
end
function methods:GetCurrentChannel()
return self.CurrentChannel
end
function methods:SwitchCurrentChannel(channelName)
if (not ChatSettings.ShowChannelsBar) then
local targ = self:GetChannel(channelName)
if (targ) then
self.ChatBar:SetChannelTarget(targ.Name)
end
channelName = ChatSettings.GeneralChannelName
end
local cur = self:GetCurrentChannel()
local new = self:GetChannel(channelName)
if new == nil then
error(string.format("Channel '%s' does not exist.", channelName))
end
if (new ~= cur) then
if (cur) then
cur:SetActive(false)
local tab = self.ChannelsBar:GetChannelTab(cur.Name)
tab:SetActive(false)
end
if (new) then
new:SetActive(true)
local tab = self.ChannelsBar:GetChannelTab(new.Name)
tab:SetActive(true)
end
self.CurrentChannel = new
end
end
function methods:UpdateFrameVisibility()
self.GuiObject.Visible = (self.Visible and self.CoreGuiEnabled)
end
function methods:GetVisible()
return self.Visible
end
function methods:SetVisible(visible)
self.Visible = visible
self:UpdateFrameVisibility()
end
function methods:GetCoreGuiEnabled()
return self.CoreGuiEnabled
end
function methods:SetCoreGuiEnabled(enabled)
self.CoreGuiEnabled = enabled
self:UpdateFrameVisibility()
end
function methods:EnableResizable()
self.GuiObjects.ChatResizerFrame.Active = true
end
function methods:DisableResizable()
self.GuiObjects.ChatResizerFrame.Active = false
end
function methods:FadeOutBackground(duration)
self.ChannelsBar:FadeOutBackground(duration)
self.MessageLogDisplay:FadeOutBackground(duration)
self.ChatBar:FadeOutBackground(duration)
self.AnimParams.Background_TargetTransparency = 1
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeInBackground(duration)
self.ChannelsBar:FadeInBackground(duration)
self.MessageLogDisplay:FadeInBackground(duration)
self.ChatBar:FadeInBackground(duration)
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeOutText(duration)
self.MessageLogDisplay:FadeOutText(duration)
self.ChannelsBar:FadeOutText(duration)
end
function methods:FadeInText(duration)
self.MessageLogDisplay:FadeInText(duration)
self.ChannelsBar:FadeInText(duration)
end
function methods:AnimGuiObjects()
self.GuiObjects.ChatChannelParentFrame.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
self.GuiObjects.ChatResizerFrame.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
self.GuiObjects.ResizeIcon.ImageTransparency = self.AnimParams.Background_CurrentTransparency
end
function methods:InitializeAnimParams()
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_CurrentTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
end
function methods:Update(dtScale)
self.ChatBar:Update(dtScale)
self.ChannelsBar:Update(dtScale)
self.MessageLogDisplay:Update(dtScale)
self.AnimParams.Background_CurrentTransparency = CurveUtil:Expt(
self.AnimParams.Background_CurrentTransparency,
self.AnimParams.Background_TargetTransparency,
self.AnimParams.Background_NormalizedExptValue,
dtScale
)
self:AnimGuiObjects()
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new()
local obj = setmetatable({}, methods)
obj.GuiObject = nil
obj.GuiObjects = {}
obj.ChatBar = nil
obj.ChannelsBar = nil
obj.MessageLogDisplay = nil
obj.Channels = {}
obj.CurrentChannel = nil
obj.Visible = true
obj.CoreGuiEnabled = true
obj.AnimParams = {}
obj:InitializeAnimParams()
return obj
end
return module
================================================
FILE: src/Chat/ChatScript/ChatMain/CommandProcessor.lua
================================================
-- // FileName: ProcessCommands.lua
-- // Written by: TheGamer101
-- // Description: Module for processing commands using the client CommandModules
local module = {}
local methods = {}
methods.__index = methods
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local commandModules = clientChatModules:WaitForChild("CommandModules")
local commandUtil = require(commandModules:WaitForChild("Util"))
local modulesFolder = script.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
function methods:SetupCommandProcessors()
local commands = commandModules:GetChildren()
for i = 1, #commands do
if commands[i]:IsA("ModuleScript") then
if commands[i].Name ~= "Util" then
local commandProcessor = require(commands[i])
local processorType = commandProcessor[commandUtil.KEY_COMMAND_PROCESSOR_TYPE]
local processorFunction = commandProcessor[commandUtil.KEY_PROCESSOR_FUNCTION]
if processorType == commandUtil.IN_PROGRESS_MESSAGE_PROCESSOR then
table.insert(self.InProgressMessageProcessors, processorFunction)
elseif processorType == commandUtil.COMPLETED_MESSAGE_PROCESSOR then
table.insert(self.CompletedMessageProcessors, processorFunction)
end
end
end
end
end
function methods:ProcessCompletedChatMessage(message, ChatWindow)
for i = 1, #self.CompletedMessageProcessors do
local processedCommand = self.CompletedMessageProcessors[i](message, ChatWindow, ChatSettings)
if processedCommand then
return true
end
end
return false
end
function methods:ProcessInProgressChatMessage(message, ChatWindow, ChatBar)
for i = 1, #self.InProgressMessageProcessors do
local customState = self.InProgressMessageProcessors[i](message, ChatWindow, ChatBar, ChatSettings)
if customState then
return customState
end
end
return nil
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new()
local obj = setmetatable({}, methods)
obj.CompletedMessageProcessors = {}
obj.InProgressMessageProcessors = {}
obj:SetupCommandProcessors()
return obj
end
return module
================================================
FILE: src/Chat/ChatScript/ChatMain/CurveUtil.lua
================================================
local CurveUtil = { }
local DEFAULT_THRESHOLD = 0.01
function CurveUtil:Expt(start, to, pct, dt_scale)
if math.abs(to - start) < DEFAULT_THRESHOLD then
return to
end
local y = CurveUtil:Expty(start,to,pct,dt_scale)
--rtv = start + (to - start) * timescaled_friction--
local delta = (to - start) * y
return start + delta
end
function CurveUtil:Expty(start, to, pct, dt_scale)
--y = e ^ (-a * timescale)--
local friction = 1 - pct
local a = -math.log(friction)
return 1 - math.exp(-a * dt_scale)
end
function CurveUtil:Sign(val)
if val > 0 then
return 1
elseif val < 0 then
return -1
else
return 0
end
end
function CurveUtil:BezierValForT(p0, p1, p2, p3, t)
local cp0 = (1 - t) * (1 - t) * (1 - t)
local cp1 = 3 * t * (1-t)*(1-t)
local cp2 = 3 * t * t * (1 - t)
local cp3 = t * t * t
return cp0 * p0 + cp1 * p1 + cp2 * p2 + cp3 * p3
end
CurveUtil._BezierPt2ForT = { x = 0; y = 0 }
function CurveUtil:BezierPt2ForT(
p0x, p0y,
p1x, p1y,
p2x, p2y,
p3x, p3y,
t)
CurveUtil._BezierPt2ForT.x = CurveUtil:BezierValForT(p0x,p1x,p2x,p3x,t)
CurveUtil._BezierPt2ForT.y = CurveUtil:BezierValForT(p0y,p1y,p2y,p3y,t)
return CurveUtil._BezierPt2ForT
end
function CurveUtil:YForPointOf2PtLine(pt1, pt2, x)
--(y - y1)/(x - x1) = m--
local m = (pt1.y - pt2.y) / (pt1.x - pt2.x)
--y - mx = b--
local b = pt1.y - m * pt1.x
return m * x + b
end
function CurveUtil:DeltaTimeToTimescale(s_frame_delta_time)
return s_frame_delta_time / (1.0 / 60.0)
end
function CurveUtil:SecondsToTick(sec)
return (1 / 60.0) / sec
end
function CurveUtil:ExptValueInSeconds(threshold, start, seconds)
return 1 - math.pow((threshold / start), 1 / (60.0 * seconds))
end
function CurveUtil:NormalizedDefaultExptValueInSeconds(seconds)
return self:ExptValueInSeconds(DEFAULT_THRESHOLD, 1, seconds)
end
return CurveUtil
================================================
FILE: src/Chat/ChatScript/ChatMain/MessageLabelCreator.lua
================================================
-- // FileName: MessageLabelCreator.lua
-- // Written by: Xsitsu
-- // Description: Module to handle taking text and creating stylized GUI objects for display in ChatWindow.
local OBJECT_POOL_SIZE = 50
local module = {}
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local messageCreatorModules = clientChatModules:WaitForChild("MessageCreatorModules")
local messageCreatorUtil = require(messageCreatorModules:WaitForChild("Util"))
local modulesFolder = script.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local moduleObjectPool = require(modulesFolder:WaitForChild("ObjectPool"))
local MessageSender = require(modulesFolder:WaitForChild("MessageSender"))
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
-- merge properties on both table to target
function mergeProps(source, target)
if not source then return end
for prop, value in pairs(source) do
target[prop] = value
end
end
function ReturnToObjectPoolRecursive(instance, objectPool)
local children = instance:GetChildren()
for i = 1, #children do
ReturnToObjectPoolRecursive(children[i], objectPool)
end
instance.Parent = nil
objectPool:ReturnInstance(instance)
end
function GetMessageCreators()
local typeToFunction = {}
local creators = messageCreatorModules:GetChildren()
for i = 1, #creators do
if creators[i]:IsA("ModuleScript") then
if creators[i].Name ~= "Util" then
local creator = require(creators[i])
typeToFunction[creator[messageCreatorUtil.KEY_MESSAGE_TYPE]] = creator[messageCreatorUtil.KEY_CREATOR_FUNCTION]
end
end
end
return typeToFunction
end
function methods:WrapIntoMessageObject(messageData, createdMessageObject)
local BaseFrame = createdMessageObject[messageCreatorUtil.KEY_BASE_FRAME]
local BaseMessage = nil
if messageCreatorUtil.KEY_BASE_MESSAGE then
BaseMessage = createdMessageObject[messageCreatorUtil.KEY_BASE_MESSAGE]
end
local UpdateTextFunction = createdMessageObject[messageCreatorUtil.KEY_UPDATE_TEXT_FUNC]
local GetHeightFunction = createdMessageObject[messageCreatorUtil.KEY_GET_HEIGHT]
local FadeInFunction = createdMessageObject[messageCreatorUtil.KEY_FADE_IN]
local FadeOutFunction = createdMessageObject[messageCreatorUtil.KEY_FADE_OUT]
local UpdateAnimFunction = createdMessageObject[messageCreatorUtil.KEY_UPDATE_ANIMATION]
local obj = {}
obj.ID = messageData.ID
obj.BaseFrame = BaseFrame
obj.BaseMessage = BaseMessage
obj.UpdateTextFunction = UpdateTextFunction or function() warn("NO MESSAGE RESIZE FUNCTION") end
obj.GetHeightFunction = GetHeightFunction
obj.FadeInFunction = FadeInFunction
obj.FadeOutFunction = FadeOutFunction
obj.UpdateAnimFunction = UpdateAnimFunction
obj.ObjectPool = self.ObjectPool
obj.Destroyed = false
function obj:Destroy()
ReturnToObjectPoolRecursive(self.BaseFrame, self.ObjectPool)
self.Destroyed = true
end
return obj
end
function methods:CreateMessageLabel(messageData, currentChannelName)
messageData.Channel = currentChannelName
local extraDeveloperFormatTable
pcall(function()
extraDeveloperFormatTable = Chat:InvokeChatCallback(Enum.ChatCallbackType.OnClientFormattingMessage, messageData)
end)
messageData.ExtraData = messageData.ExtraData or {}
mergeProps(extraDeveloperFormatTable, messageData.ExtraData)
local messageType = messageData.MessageType
if self.MessageCreators[messageType] then
local createdMessageObject = self.MessageCreators[messageType](messageData, currentChannelName)
if createdMessageObject then
return self:WrapIntoMessageObject(messageData, createdMessageObject)
end
elseif self.DefaultCreatorType then
local createdMessageObject = self.MessageCreators[self.DefaultCreatorType](messageData, currentChannelName)
if createdMessageObject then
return self:WrapIntoMessageObject(messageData, createdMessageObject)
end
else
error("No message creator available for message type: " ..messageType)
end
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new()
local obj = setmetatable({}, methods)
obj.ObjectPool = moduleObjectPool.new(OBJECT_POOL_SIZE)
obj.MessageCreators = GetMessageCreators()
obj.DefaultCreatorType = messageCreatorUtil.DEFAULT_MESSAGE_CREATOR
messageCreatorUtil:RegisterObjectPool(obj.ObjectPool)
return obj
end
function module:GetStringTextBounds(text, font, textSize, sizeBounds)
return messageCreatorUtil:GetStringTextBounds(text, font, textSize, sizeBounds)
end
return module
================================================
FILE: src/Chat/ChatScript/ChatMain/MessageLogDisplay.lua
================================================
-- // FileName: MessageLogDisplay.lua
-- // Written by: Xsitsu, TheGamer101
-- // Description: ChatChannel window for displaying messages.
local module = {}
module.ScrollBarThickness = 4
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local modulesFolder = script.Parent
local moduleMessageLabelCreator = require(modulesFolder:WaitForChild("MessageLabelCreator"))
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local FlagFixChatMessageLogPerformance = false do
local ok, value = pcall(function()
return UserSettings():IsUserFeatureEnabled("UserFixChatMessageLogPerformance")
end)
if ok then
FlagFixChatMessageLogPerformance = value
end
end
local MessageLabelCreator = moduleMessageLabelCreator.new()
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
local function CreateGuiObjects()
local BaseFrame = Instance.new("Frame")
BaseFrame.Selectable = false
BaseFrame.Size = UDim2.new(1, 0, 1, 0)
BaseFrame.BackgroundTransparency = 1
local Scroller = Instance.new("ScrollingFrame")
Scroller.Selectable = ChatSettings.GamepadNavigationEnabled
Scroller.Name = "Scroller"
Scroller.BackgroundTransparency = 1
Scroller.BorderSizePixel = 0
Scroller.Position = UDim2.new(0, 0, 0, 3)
Scroller.Size = UDim2.new(1, -4, 1, -6)
Scroller.CanvasSize = UDim2.new(0, 0, 0, 0)
Scroller.ScrollBarThickness = module.ScrollBarThickness
Scroller.Active = false
Scroller.Parent = BaseFrame
local Layout
if FlagFixChatMessageLogPerformance then
Layout = Instance.new("UIListLayout")
Layout.SortOrder = Enum.SortOrder.LayoutOrder
Layout.Parent = Scroller
end
return BaseFrame, Scroller, Layout
end
function methods:Destroy()
self.GuiObject:Destroy()
self.Destroyed = true
end
function methods:SetActive(active)
self.GuiObject.Visible = active
end
function methods:UpdateMessageFiltered(messageData)
local messageObject = nil
local searchIndex = 1
local searchTable = self.MessageObjectLog
while (#searchTable >= searchIndex) do
local obj = searchTable[searchIndex]
if obj.ID == messageData.ID then
messageObject = obj
break
end
searchIndex = searchIndex + 1
end
if messageObject then
local isScrolledDown = self:IsScrolledDown()
messageObject.UpdateTextFunction(messageData)
if FlagFixChatMessageLogPerformance then
self:PositionMessageLabelInWindow(messageObject, isScrolledDown)
else
self:ReorderAllMessages()
end
end
end
function methods:AddMessage(messageData)
self:WaitUntilParentedCorrectly()
local messageObject = MessageLabelCreator:CreateMessageLabel(messageData, self.CurrentChannelName)
if messageObject == nil then
return
end
table.insert(self.MessageObjectLog, messageObject)
self:PositionMessageLabelInWindow(messageObject)
end
function methods:AddMessageAtIndex(messageData, index)
local messageObject = MessageLabelCreator:CreateMessageLabel(messageData, self.CurrentChannelName)
if messageObject == nil then
return
end
table.insert(self.MessageObjectLog, index, messageObject)
local wasScrolledToBottom = self:IsScrolledDown()
self:ReorderAllMessages()
if wasScrolledToBottom then
self.Scroller.CanvasPosition = Vector2.new(0, math.max(0, self.Scroller.CanvasSize.Y.Offset - self.Scroller.AbsoluteSize.Y))
end
end
function methods:RemoveLastMessage()
self:WaitUntilParentedCorrectly()
local lastMessage = self.MessageObjectLog[1]
-- remove with FlagFixChatMessageLogPerformance
local posOffset = UDim2.new(0, 0, 0, lastMessage.BaseFrame.AbsoluteSize.Y)
lastMessage:Destroy()
table.remove(self.MessageObjectLog, 1)
if not FlagFixChatMessageLogPerformance then
for i, messageObject in pairs(self.MessageObjectLog) do
messageObject.BaseFrame.Position = messageObject.BaseFrame.Position - posOffset
end
self.Scroller.CanvasSize = self.Scroller.CanvasSize - posOffset
end
end
function methods:IsScrolledDown()
local yCanvasSize = self.Scroller.CanvasSize.Y.Offset
local yContainerSize = self.Scroller.AbsoluteWindowSize.Y
local yScrolledPosition = self.Scroller.CanvasPosition.Y
if FlagFixChatMessageLogPerformance then
return
yCanvasSize < yContainerSize or
yCanvasSize + yScrolledPosition >= yContainerSize - 5
else
return (yCanvasSize < yContainerSize or
yCanvasSize - yScrolledPosition <= yContainerSize + 5)
end
end
function methods:PositionMessageLabelInWindow(messageObject, isScrolledDown)
self:WaitUntilParentedCorrectly()
local baseFrame = messageObject.BaseFrame
if FlagFixChatMessageLogPerformance then
if isScrolledDown == nil then
isScrolledDown = self:IsScrolledDown()
end
baseFrame.LayoutOrder = messageObject.ID
else
baseFrame.Parent = self.Scroller
baseFrame.Position = UDim2.new(0, 0, 0, self.Scroller.CanvasSize.Y.Offset)
end
baseFrame.Size = UDim2.new(1, 0, 0, messageObject.GetHeightFunction(self.Scroller.AbsoluteSize.X))
if FlagFixChatMessageLogPerformance then
baseFrame.Parent = self.Scroller
end
if messageObject.BaseMessage then
if FlagFixChatMessageLogPerformance then
for i = 1, 10 do
if messageObject.BaseMessage.TextFits then
break
end
local trySize = self.Scroller.AbsoluteSize.X - i
baseFrame.Size = UDim2.new(1, 0, 0, messageObject.GetHeightFunction(trySize))
end
else
local trySize = self.Scroller.AbsoluteSize.X
local minTrySize = math.min(self.Scroller.AbsoluteSize.X - 10, 0)
while not messageObject.BaseMessage.TextFits do
trySize = trySize - 1
if trySize < minTrySize then
break
end
baseFrame.Size = UDim2.new(1, 0, 0, messageObject.GetHeightFunction(trySize))
end
end
end
if FlagFixChatMessageLogPerformance then
if isScrolledDown then
local scrollValue = self.Scroller.CanvasSize.Y.Offset - self.Scroller.AbsoluteSize.Y
self.Scroller.CanvasPosition = Vector2.new(0, math.max(0, scrollValue))
end
else
isScrolledDown = self:IsScrolledDown()
local add = UDim2.new(0, 0, 0, baseFrame.Size.Y.Offset)
self.Scroller.CanvasSize = self.Scroller.CanvasSize + add
if isScrolledDown then
self.Scroller.CanvasPosition = Vector2.new(0, math.max(0, self.Scroller.CanvasSize.Y.Offset - self.Scroller.AbsoluteSize.Y))
end
end
end
function methods:ReorderAllMessages()
self:WaitUntilParentedCorrectly()
--// Reordering / reparenting with a size less than 1 causes weird glitches to happen with scrolling as repositioning happens.
if self.GuiObject.AbsoluteSize.Y < 1 then return end
local oldCanvasPositon = self.Scroller.CanvasPosition
local wasScrolledDown = self:IsScrolledDown()
self.Scroller.CanvasSize = UDim2.new(0, 0, 0, 0)
for i, messageObject in pairs(self.MessageObjectLog) do
self:PositionMessageLabelInWindow(messageObject)
end
if not wasScrolledDown then
self.Scroller.CanvasPosition = oldCanvasPositon
end
end
function methods:Clear()
for i, v in pairs(self.MessageObjectLog) do
v:Destroy()
end
self.MessageObjectLog = {}
if not FlagFixChatMessageLogPerformance then
self.Scroller.CanvasSize = UDim2.new(0, 0, 0, 0)
end
end
function methods:SetCurrentChannelName(name)
self.CurrentChannelName = name
end
function methods:FadeOutBackground(duration)
--// Do nothing
end
function methods:FadeInBackground(duration)
--// Do nothing
end
function methods:FadeOutText(duration)
for i = 1, #self.MessageObjectLog do
if self.MessageObjectLog[i].FadeOutFunction then
self.MessageObjectLog[i].FadeOutFunction(duration, CurveUtil)
end
end
end
function methods:FadeInText(duration)
for i = 1, #self.MessageObjectLog do
if self.MessageObjectLog[i].FadeInFunction then
self.MessageObjectLog[i].FadeInFunction(duration, CurveUtil)
end
end
end
function methods:Update(dtScale)
for i = 1, #self.MessageObjectLog do
if self.MessageObjectLog[i].UpdateAnimFunction then
self.MessageObjectLog[i].UpdateAnimFunction(dtScale, CurveUtil)
end
end
end
--// ToDo: Move to common modules
function methods:WaitUntilParentedCorrectly()
while (not self.GuiObject:IsDescendantOf(game:GetService("Players").LocalPlayer)) do
self.GuiObject.AncestryChanged:wait()
end
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new()
local obj = setmetatable({}, methods)
obj.Destroyed = false
local BaseFrame, Scroller, Layout = CreateGuiObjects()
obj.GuiObject = BaseFrame
obj.Scroller = Scroller
obj.Layout = Layout
obj.MessageObjectLog = {}
obj.Name = "MessageLogDisplay"
obj.GuiObject.Name = "Frame_" .. obj.Name
obj.CurrentChannelName = ""
obj.GuiObject:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
spawn(function() obj:ReorderAllMessages() end)
end)
if FlagFixChatMessageLogPerformance then
local wasScrolledDown = true
obj.Layout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function()
local size = obj.Layout.AbsoluteContentSize
obj.Scroller.CanvasSize = UDim2.new(0, 0, 0, size.Y)
if wasScrolledDown then
local windowSize = obj.Scroller.AbsoluteWindowSize
obj.Scroller.CanvasPosition = Vector2.new(0, size.Y - windowSize.Y)
end
end)
obj.Scroller:GetPropertyChangedSignal("CanvasPosition"):Connect(function()
wasScrolledDown = obj:IsScrolledDown()
end)
end
return obj
end
return module
================================================
FILE: src/Chat/ChatScript/ChatMain/MessageSender.lua
================================================
-- // FileName: MessageSender.lua
-- // Written by: Xsitsu
-- // Description: Module to centralize sending message functionality.
local module = {}
--////////////////////////////// Include
--//////////////////////////////////////
local modulesFolder = script.Parent
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:SendMessage(message, toChannel)
self.SayMessageRequest:FireServer(message, toChannel)
end
function methods:RegisterSayMessageFunction(func)
self.SayMessageRequest = func
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new()
local obj = setmetatable({}, methods)
obj.SayMessageRequest = nil
return obj
end
return module.new()
================================================
FILE: src/Chat/ChatScript/ChatMain/ObjectPool.lua
================================================
-- // FileName: ObjectPool.lua
-- // Written by: TheGamer101
-- // Description: An object pool class used to avoid unnecessarily instantiating Instances.
local module = {}
--////////////////////////////// Include
--//////////////////////////////////////
local modulesFolder = script.Parent
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:GetInstance(className)
if self.InstancePoolsByClass[className] == nil then
self.InstancePoolsByClass[className] = {}
end
local availableInstances = #self.InstancePoolsByClass[className]
if availableInstances > 0 then
local instance = self.InstancePoolsByClass[className][availableInstances]
table.remove(self.InstancePoolsByClass[className])
return instance
end
return Instance.new(className)
end
function methods:ReturnInstance(instance)
if self.InstancePoolsByClass[instance.ClassName] == nil then
self.InstancePoolsByClass[instance.ClassName] = {}
end
if #self.InstancePoolsByClass[instance.ClassName] < self.PoolSizePerType then
table.insert(self.InstancePoolsByClass[instance.ClassName], instance)
else
instance:Destroy()
end
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new(poolSizePerType)
local obj = setmetatable({}, methods)
obj.InstancePoolsByClass = {}
obj.Name = "ObjectPool"
obj.PoolSizePerType = poolSizePerType
return obj
end
return module
================================================
FILE: src/Chat/ChatScript/ChatMain/init.lua
================================================
-- // FileName: ChatMain.lua
-- // Written by: Xsitsu
-- // Description: Main module to handle initializing chat window UI and hooking up events to individual UI pieces.
local moduleApiTable = {}
--// This section of code waits until all of the necessary RemoteEvents are found in EventFolder.
--// I have to do some weird stuff since people could potentially already have pre-existing
--// things in a folder with the same name, and they may have different class types.
--// I do the useEvents thing and set EventFolder to useEvents so I can have a pseudo folder that
--// the rest of the code can interface with and have the guarantee that the RemoteEvents they want
--// exist with their desired names.
local FILTER_MESSAGE_TIMEOUT = 60
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Chat = game:GetService("Chat")
local StarterGui = game:GetService("StarterGui")
local DefaultChatSystemChatEvents = ReplicatedStorage:WaitForChild("DefaultChatSystemChatEvents")
local EventFolder = ReplicatedStorage:WaitForChild("DefaultChatSystemChatEvents")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local messageCreatorModules = clientChatModules:WaitForChild("MessageCreatorModules")
local MessageCreatorUtil = require(messageCreatorModules:WaitForChild("Util"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local configuration = modules.load("configuration")
local numChildrenRemaining = 10 -- #waitChildren returns 0 because it's a dictionary
local waitChildren =
{
OnNewMessage = "RemoteEvent",
OnMessageDoneFiltering = "RemoteEvent",
OnNewSystemMessage = "RemoteEvent",
OnChannelJoined = "RemoteEvent",
OnChannelLeft = "RemoteEvent",
OnMuted = "RemoteEvent",
OnUnmuted = "RemoteEvent",
OnMainChannelSet = "RemoteEvent",
SayMessageRequest = "RemoteEvent",
GetInitDataRequest = "RemoteFunction",
}
-- waitChildren/EventFolder does not contain all the remote events, because the server version could be older than the client version.
-- In that case it would not create the new events.
-- These events are accessed directly from DefaultChatSystemChatEvents
local useEvents = {}
local FoundAllEventsEvent = Instance.new("BindableEvent")
function TryRemoveChildWithVerifyingIsCorrectType(child)
if (waitChildren[child.Name] and child:IsA(waitChildren[child.Name])) then
waitChildren[child.Name] = nil
useEvents[child.Name] = child
numChildrenRemaining = numChildrenRemaining - 1
end
end
for i, child in pairs(EventFolder:GetChildren()) do
TryRemoveChildWithVerifyingIsCorrectType(child)
end
if (numChildrenRemaining > 0) then
local con = EventFolder.ChildAdded:connect(function(child)
TryRemoveChildWithVerifyingIsCorrectType(child)
if (numChildrenRemaining < 1) then
FoundAllEventsEvent:Fire()
end
end)
FoundAllEventsEvent.Event:wait()
con:disconnect()
FoundAllEventsEvent:Destroy()
end
EventFolder = useEvents
--// Rest of code after waiting for correct events.
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local LocalPlayer = Players.LocalPlayer
while not LocalPlayer do
Players.ChildAdded:wait()
LocalPlayer = Players.LocalPlayer
end
local canChat = true
local ChatDisplayOrder = 6
if ChatSettings.ScreenGuiDisplayOrder ~= nil then
ChatDisplayOrder = ChatSettings.ScreenGuiDisplayOrder
end
local PlayerGui = LocalPlayer:WaitForChild("PlayerGui")
local GuiParent = Instance.new("ScreenGui")
GuiParent.Name = "Chat"
GuiParent.ResetOnSpawn = false
GuiParent.DisplayOrder = ChatDisplayOrder
GuiParent.Parent = PlayerGui
local DidFirstChannelsLoads = false
local modulesFolder = script
local moduleChatWindow = require(modulesFolder:WaitForChild("ChatWindow"))
local moduleChatBar = require(modulesFolder:WaitForChild("ChatBar"))
local moduleChannelsBar = require(modulesFolder:WaitForChild("ChannelsBar"))
local moduleMessageLabelCreator = require(modulesFolder:WaitForChild("MessageLabelCreator"))
local moduleMessageLogDisplay = require(modulesFolder:WaitForChild("MessageLogDisplay"))
local moduleChatChannel = require(modulesFolder:WaitForChild("ChatChannel"))
local moduleCommandProcessor = require(modulesFolder:WaitForChild("CommandProcessor"))
local ChatWindow = moduleChatWindow.new()
local ChannelsBar = moduleChannelsBar.new()
local MessageLogDisplay = moduleMessageLogDisplay.new()
local CommandProcessor = moduleCommandProcessor.new()
local ChatBar = moduleChatBar.new(CommandProcessor, ChatWindow)
ChatWindow:CreateGuiObjects(GuiParent)
ChatWindow:RegisterChatBar(ChatBar)
ChatWindow:RegisterChannelsBar(ChannelsBar)
ChatWindow:RegisterMessageLogDisplay(MessageLogDisplay)
MessageCreatorUtil:RegisterChatWindow(ChatWindow)
local MessageSender = require(modulesFolder:WaitForChild("MessageSender"))
MessageSender:RegisterSayMessageFunction(EventFolder.SayMessageRequest)
if (UserInputService.TouchEnabled) then
ChatBar:SetTextLabelText(ChatLocalization:Get("GameChat_ChatMain_ChatBarText",'Tap here to chat'))
else
ChatBar:SetTextLabelText(ChatLocalization:Get("GameChat_ChatMain_ChatBarTextTouch",'To chat click here or press "/" key'))
end
spawn(function()
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
local animationFps = ChatSettings.ChatAnimationFPS or 20.0
local updateWaitTime = 1.0 / animationFps
local lastTick = tick()
while true do
local currentTick = tick()
local tickDelta = currentTick - lastTick
local dtScale = CurveUtil:DeltaTimeToTimescale(tickDelta)
if dtScale ~= 0 then
ChatWindow:Update(dtScale)
end
lastTick = currentTick
wait(updateWaitTime)
end
end)
--////////////////////////////////////////////////////////////////////////////////////////////
--////////////////////////////////////////////////////////////// Code to do chat window fading
--////////////////////////////////////////////////////////////////////////////////////////////
function CheckIfPointIsInSquare(checkPos, topLeft, bottomRight)
return (topLeft.X <= checkPos.X and checkPos.X <= bottomRight.X and
topLeft.Y <= checkPos.Y and checkPos.Y <= bottomRight.Y)
end
local backgroundIsFaded = false
local textIsFaded = false
local lastTextFadeTime = 0
local lastBackgroundFadeTime = 0
local fadedChanged = Instance.new("BindableEvent")
local mouseStateChanged = Instance.new("BindableEvent")
local chatBarFocusChanged = Instance.new("BindableEvent")
function DoBackgroundFadeIn(setFadingTime)
lastBackgroundFadeTime = tick()
backgroundIsFaded = false
fadedChanged:Fire()
ChatWindow:FadeInBackground((setFadingTime or ChatSettings.ChatDefaultFadeDuration))
local currentChannelObject = ChatWindow:GetCurrentChannel()
if (currentChannelObject) then
local Scroller = MessageLogDisplay.Scroller
Scroller.ScrollingEnabled = true
Scroller.ScrollBarThickness = moduleMessageLogDisplay.ScrollBarThickness
end
end
function DoBackgroundFadeOut(setFadingTime)
lastBackgroundFadeTime = tick()
backgroundIsFaded = true
fadedChanged:Fire()
ChatWindow:FadeOutBackground((setFadingTime or ChatSettings.ChatDefaultFadeDuration))
local currentChannelObject = ChatWindow:GetCurrentChannel()
if (currentChannelObject) then
local Scroller = MessageLogDisplay.Scroller
Scroller.ScrollingEnabled = false
Scroller.ScrollBarThickness = 0
end
end
function DoTextFadeIn(setFadingTime)
lastTextFadeTime = tick()
textIsFaded = false
fadedChanged:Fire()
ChatWindow:FadeInText((setFadingTime or ChatSettings.ChatDefaultFadeDuration) * 0)
end
function DoTextFadeOut(setFadingTime)
lastTextFadeTime = tick()
textIsFaded = true
fadedChanged:Fire()
ChatWindow:FadeOutText((setFadingTime or ChatSettings.ChatDefaultFadeDuration))
end
function DoFadeInFromNewInformation()
DoTextFadeIn()
if ChatSettings.ChatShouldFadeInFromNewInformation then
DoBackgroundFadeIn()
end
end
function InstantFadeIn()
DoBackgroundFadeIn(0)
DoTextFadeIn(0)
end
function InstantFadeOut()
DoBackgroundFadeOut(0)
DoTextFadeOut(0)
end
local mouseIsInWindow = nil
function UpdateFadingForMouseState(mouseState)
mouseIsInWindow = mouseState
mouseStateChanged:Fire()
if (ChatBar:IsFocused()) then return end
if (mouseState) then
DoBackgroundFadeIn()
DoTextFadeIn()
else
DoBackgroundFadeIn()
end
end
spawn(function()
while true do
RunService.RenderStepped:wait()
while (mouseIsInWindow or ChatBar:IsFocused()) do
if (mouseIsInWindow) then
mouseStateChanged.Event:wait()
end
if (ChatBar:IsFocused()) then
chatBarFocusChanged.Event:wait()
end
end
if (not backgroundIsFaded) then
local timeDiff = tick() - lastBackgroundFadeTime
if (timeDiff > ChatSettings.ChatWindowBackgroundFadeOutTime) then
DoBackgroundFadeOut()
end
elseif (not textIsFaded) then
local timeDiff = tick() - lastTextFadeTime
if (timeDiff > ChatSettings.ChatWindowTextFadeOutTime) then
DoTextFadeOut()
end
else
fadedChanged.Event:wait()
end
end
end)
function getClassicChatEnabled()
if ChatSettings.ClassicChatEnabled ~= nil then
return ChatSettings.ClassicChatEnabled
end
return Players.ClassicChat
end
function getBubbleChatEnabled()
if ChatSettings.BubbleChatEnabled ~= nil then
return ChatSettings.BubbleChatEnabled
end
return Players.BubbleChat
end
function bubbleChatOnly()
return not getClassicChatEnabled() and getBubbleChatEnabled()
end
function UpdateMousePosition(mousePos)
if not (moduleApiTable.Visible and moduleApiTable.IsCoreGuiEnabled and (moduleApiTable.TopbarEnabled or ChatSettings.ChatOnWithTopBarOff)) then return end
if bubbleChatOnly() then
return
end
local windowPos = ChatWindow.GuiObject.AbsolutePosition
local windowSize = ChatWindow.GuiObject.AbsoluteSize
local newMouseState = CheckIfPointIsInSquare(mousePos, windowPos, windowPos + windowSize)
if (newMouseState ~= mouseIsInWindow) then
UpdateFadingForMouseState(newMouseState)
end
end
UserInputService.InputChanged:connect(function(inputObject)
if (inputObject.UserInputType == Enum.UserInputType.MouseMovement) then
local mousePos = Vector2.new(inputObject.Position.X, inputObject.Position.Y)
UpdateMousePosition(mousePos)
end
end)
UserInputService.TouchTap:connect(function(tapPos, gameProcessedEvent)
UpdateMousePosition(tapPos[1])
end)
UserInputService.TouchMoved:connect(function(inputObject, gameProcessedEvent)
local tapPos = Vector2.new(inputObject.Position.X, inputObject.Position.Y)
UpdateMousePosition(tapPos)
end)
--[[
UserInputService.Changed:connect(function(prop)
if prop == "MouseBehavior" then
if UserInputService.MouseBehavior == Enum.MouseBehavior.LockCenter then
local windowPos = ChatWindow.GuiObject.AbsolutePosition
local windowSize = ChatWindow.GuiObject.AbsoluteSize
local screenSize = GuiParent.AbsoluteSize
local centerScreenIsInWindow = CheckIfPointIsInSquare(screenSize/2, windowPos, windowPos + windowSize)
if centerScreenIsInWindow then
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end
end
end)
]]
--// Start and stop fading sequences / timers
UpdateFadingForMouseState(true)
UpdateFadingForMouseState(false)
--////////////////////////////////////////////////////////////////////////////////////////////
--///////////// Code to talk to topbar and maintain set/get core backwards compatibility stuff
--////////////////////////////////////////////////////////////////////////////////////////////
local Util = {}
do
function Util.Signal()
local sig = {}
local mSignaler = Instance.new('BindableEvent')
local mArgData = nil
local mArgDataCount = nil
function sig:fire(...)
mArgData = {...}
mArgDataCount = select('#', ...)
mSignaler:Fire()
end
function sig:connect(f)
if not f then error("connect(nil)", 2) end
return mSignaler.Event:connect(function()
f(unpack(mArgData, 1, mArgDataCount))
end)
end
function sig:wait()
mSignaler.Event:wait()
assert(mArgData, "Missing arg data, likely due to :TweenSize/Position corrupting threadrefs.")
return unpack(mArgData, 1, mArgDataCount)
end
return sig
end
end
function SetVisibility(val)
ChatWindow:SetVisible(val)
moduleApiTable.VisibilityStateChanged:fire(val)
moduleApiTable.Visible = val
if (moduleApiTable.IsCoreGuiEnabled) then
if (val) then
InstantFadeIn()
else
InstantFadeOut()
end
end
end
do
moduleApiTable.TopbarEnabled = true
moduleApiTable.MessageCount = 0
moduleApiTable.Visible = true
moduleApiTable.IsCoreGuiEnabled = true
function moduleApiTable:ToggleVisibility()
SetVisibility(not ChatWindow:GetVisible())
end
function moduleApiTable:SetVisible(visible)
if (ChatWindow:GetVisible() ~= visible) then
SetVisibility(visible)
end
end
function moduleApiTable:FocusChatBar()
ChatBar:CaptureFocus()
end
function moduleApiTable:EnterWhisperState(player)
ChatBar:EnterWhisperState(player)
end
function moduleApiTable:GetVisibility()
return ChatWindow:GetVisible()
end
function moduleApiTable:GetMessageCount()
return self.MessageCount
end
function moduleApiTable:TopbarEnabledChanged(enabled)
self.TopbarEnabled = enabled
self.CoreGuiEnabled:fire(game:GetService("StarterGui"):GetCoreGuiEnabled(Enum.CoreGuiType.Chat))
end
function moduleApiTable:IsFocused(useWasFocused)
return ChatBar:IsFocused()
end
moduleApiTable.ChatBarFocusChanged = Util.Signal()
moduleApiTable.VisibilityStateChanged = Util.Signal()
moduleApiTable.MessagesChanged = Util.Signal()
moduleApiTable.MessagePosted = Util.Signal()
moduleApiTable.CoreGuiEnabled = Util.Signal()
moduleApiTable.ChatMakeSystemMessageEvent = Util.Signal()
moduleApiTable.ChatWindowPositionEvent = Util.Signal()
moduleApiTable.ChatWindowSizeEvent = Util.Signal()
moduleApiTable.ChatBarDisabledEvent = Util.Signal()
function moduleApiTable:fChatWindowPosition()
return ChatWindow.GuiObject.Position
end
function moduleApiTable:fChatWindowSize()
return ChatWindow.GuiObject.Size
end
function moduleApiTable:fChatBarDisabled()
return not ChatBar:GetEnabled()
end
function moduleApiTable:SpecialKeyPressed(key, modifiers)
if (key == Enum.SpecialKey.ChatHotkey) then
if canChat then
DoChatBarFocus()
end
end
end
end
moduleApiTable.CoreGuiEnabled:connect(function(enabled)
moduleApiTable.IsCoreGuiEnabled = enabled
enabled = enabled and (moduleApiTable.TopbarEnabled or ChatSettings.ChatOnWithTopBarOff)
ChatWindow:SetCoreGuiEnabled(enabled)
if (not enabled) then
ChatBar:ReleaseFocus()
InstantFadeOut()
else
InstantFadeIn()
end
end)
function trimTrailingSpaces(str)
local lastSpace = #str
while lastSpace > 0 do
--- The pattern ^%s matches whitespace at the start of the string. (Starting from lastSpace)
if str:find("^%s", lastSpace) then
lastSpace = lastSpace - 1
else
break
end
end
return str:sub(1, lastSpace)
end
moduleApiTable.ChatMakeSystemMessageEvent:connect(function(valueTable)
if (valueTable["Text"] and type(valueTable["Text"]) == "string") then
while (not DidFirstChannelsLoads) do wait() end
local channel = ChatSettings.GeneralChannelName
local channelObj = ChatWindow:GetChannel(channel)
if (channelObj) then
local messageObject = {
ID = -1,
FromSpeaker = nil,
SpeakerUserId = 0,
OriginalChannel = channel,
IsFiltered = true,
MessageLength = string.len(valueTable.Text),
Message = trimTrailingSpaces(valueTable.Text),
MessageType = ChatConstants.MessageTypeSetCore,
Time = os.time(),
ExtraData = valueTable,
}
channelObj:AddMessageToChannel(messageObject)
ChannelsBar:UpdateMessagePostedInChannel(channel)
moduleApiTable.MessageCount = moduleApiTable.MessageCount + 1
moduleApiTable.MessagesChanged:fire(moduleApiTable.MessageCount)
end
end
end)
moduleApiTable.ChatBarDisabledEvent:connect(function(disabled)
if canChat then
ChatBar:SetEnabled(not disabled)
if (disabled) then
ChatBar:ReleaseFocus()
end
end
end)
moduleApiTable.ChatWindowSizeEvent:connect(function(size)
ChatWindow.GuiObject.Size = size
end)
moduleApiTable.ChatWindowPositionEvent:connect(function(position)
ChatWindow.GuiObject.Position = position
end)
--////////////////////////////////////////////////////////////////////////////////////////////
--///////////////////////////////////////////////// Code to hook client UI up to server events
--////////////////////////////////////////////////////////////////////////////////////////////
function DoChatBarFocus()
if (not ChatWindow:GetCoreGuiEnabled()) then return end
if (not ChatBar:GetEnabled()) then return end
if (not ChatBar:IsFocused() and ChatBar:GetVisible()) then
moduleApiTable:SetVisible(true)
InstantFadeIn()
ChatBar:CaptureFocus()
moduleApiTable.ChatBarFocusChanged:fire(true)
end
end
chatBarFocusChanged.Event:connect(function(focused)
moduleApiTable.ChatBarFocusChanged:fire(focused)
end)
function DoSwitchCurrentChannel(targetChannel)
if (ChatWindow:GetChannel(targetChannel)) then
ChatWindow:SwitchCurrentChannel(targetChannel)
end
end
function SendMessageToSelfInTargetChannel(message, channelName, extraData)
local channelObj = ChatWindow:GetChannel(channelName)
if (channelObj) then
local messageData =
{
ID = -1,
FromSpeaker = nil,
SpeakerUserId = 0,
OriginalChannel = channelName,
IsFiltered = true,
MessageLength = string.len(message),
Message = trimTrailingSpaces(message),
MessageType = ChatConstants.MessageTypeSystem,
Time = os.time(),
ExtraData = extraData,
}
channelObj:AddMessageToChannel(messageData)
end
end
function chatBarFocused()
if (not mouseIsInWindow) then
DoBackgroundFadeIn()
if (textIsFaded) then
DoTextFadeIn()
end
end
chatBarFocusChanged:Fire(true)
end
--// Event for making player say chat message.
function chatBarFocusLost(enterPressed, inputObject)
DoBackgroundFadeIn()
chatBarFocusChanged:Fire(false)
if (enterPressed) then
local message = ChatBar:GetTextBox().Text
if ChatBar:IsInCustomState() then
local customMessage = ChatBar:GetCustomMessage()
if customMessage then
message = customMessage
end
local messageSunk = ChatBar:CustomStateProcessCompletedMessage(message)
ChatBar:ResetCustomState()
if messageSunk then
return
end
end
message = string.sub(message, 1, ChatSettings.MaximumMessageLength)
ChatBar:GetTextBox().Text = ""
if message ~= "" then
--// Sends signal to eventually call Player:Chat() to handle C++ side legacy stuff.
moduleApiTable.MessagePosted:fire(message)
if not CommandProcessor:ProcessCompletedChatMessage(message, ChatWindow) then
if ChatSettings.DisallowedWhiteSpace then
for i = 1, #ChatSettings.DisallowedWhiteSpace do
if ChatSettings.DisallowedWhiteSpace[i] == "\t" then
message = string.gsub(message, ChatSettings.DisallowedWhiteSpace[i], " ")
else
message = string.gsub(message, ChatSettings.DisallowedWhiteSpace[i], "")
end
end
end
message = string.gsub(message, "\n", "")
message = string.gsub(message, "[ ]+", " ")
local targetChannel = ChatWindow:GetTargetMessageChannel()
if targetChannel then
MessageSender:SendMessage(message, targetChannel)
else
MessageSender:SendMessage(message, nil)
end
end
end
end
end
local ChatBarConnections = {}
function setupChatBarConnections()
for i = 1, #ChatBarConnections do
ChatBarConnections[i]:Disconnect()
end
ChatBarConnections = {}
local focusLostConnection = ChatBar:GetTextBox().FocusLost:connect(chatBarFocusLost)
table.insert(ChatBarConnections, focusLostConnection)
local focusGainedConnection = ChatBar:GetTextBox().Focused:connect(chatBarFocused)
table.insert(ChatBarConnections, focusGainedConnection)
end
setupChatBarConnections()
ChatBar.GuiObjectsChanged:connect(setupChatBarConnections)
function getEchoMessagesInGeneral()
if ChatSettings.EchoMessagesInGeneralChannel == nil then
return true
end
return ChatSettings.EchoMessagesInGeneralChannel
end
EventFolder.OnMessageDoneFiltering.OnClientEvent:connect(function(messageData)
if not ChatSettings.ShowUserOwnFilteredMessage then
if messageData.FromSpeaker == LocalPlayer.Name then
return
end
end
local channelName = messageData.OriginalChannel
local channelObj = ChatWindow:GetChannel(channelName)
if channelObj then
channelObj:UpdateMessageFiltered(messageData)
end
if getEchoMessagesInGeneral() and ChatSettings.GeneralChannelName and channelName ~= ChatSettings.GeneralChannelName then
local generalChannel = ChatWindow:GetChannel(ChatSettings.GeneralChannelName)
if generalChannel then
generalChannel:UpdateMessageFiltered(messageData)
end
end
end)
EventFolder.OnNewMessage.OnClientEvent:connect(function(messageData, channelName)
local channelObj = ChatWindow:GetChannel(channelName)
if (channelObj) then
channelObj:AddMessageToChannel(messageData)
if (messageData.FromSpeaker ~= LocalPlayer.Name) then
ChannelsBar:UpdateMessagePostedInChannel(channelName)
end
if getEchoMessagesInGeneral() and ChatSettings.GeneralChannelName and channelName ~= ChatSettings.GeneralChannelName then
local generalChannel = ChatWindow:GetChannel(ChatSettings.GeneralChannelName)
if generalChannel then
generalChannel:AddMessageToChannel(messageData)
end
end
moduleApiTable.MessageCount = moduleApiTable.MessageCount + 1
moduleApiTable.MessagesChanged:fire(moduleApiTable.MessageCount)
DoFadeInFromNewInformation()
end
end)
EventFolder.OnNewSystemMessage.OnClientEvent:connect(function(messageData, channelName)
channelName = channelName or "System"
local channelObj = ChatWindow:GetChannel(channelName)
if (channelObj) then
channelObj:AddMessageToChannel(messageData)
ChannelsBar:UpdateMessagePostedInChannel(channelName)
moduleApiTable.MessageCount = moduleApiTable.MessageCount + 1
moduleApiTable.MessagesChanged:fire(moduleApiTable.MessageCount)
DoFadeInFromNewInformation()
if getEchoMessagesInGeneral() and ChatSettings.GeneralChannelName and channelName ~= ChatSettings.GeneralChannelName then
local generalChannel = ChatWindow:GetChannel(ChatSettings.GeneralChannelName)
if generalChannel then
generalChannel:AddMessageToChannel(messageData)
end
end
else
warn(string.format("Just received system message for channel I'm not in [%s]", channelName))
end
end)
function HandleChannelJoined(channel, welcomeMessage, messageLog, channelNameColor, addHistoryToGeneralChannel,
addWelcomeMessageToGeneralChannel)
if ChatWindow:GetChannel(channel) then
--- If the channel has already been added, remove it first.
ChatWindow:RemoveChannel(channel)
end
if (channel == ChatSettings.GeneralChannelName) then
DidFirstChannelsLoads = true
end
if channelNameColor then
ChatBar:SetChannelNameColor(channel, channelNameColor)
end
local channelObj = ChatWindow:AddChannel(channel)
if (channelObj) then
if (channel == ChatSettings.GeneralChannelName) then
DoSwitchCurrentChannel(channel)
end
if (messageLog) then
local startIndex = 1
if #messageLog > ChatSettings.MessageHistoryLengthPerChannel then
startIndex = #messageLog - ChatSettings.MessageHistoryLengthPerChannel
end
for i = startIndex, #messageLog do
channelObj:AddMessageToChannel(messageLog[i])
end
if getEchoMessagesInGeneral() and addHistoryToGeneralChannel then
if ChatSettings.GeneralChannelName and channel ~= ChatSettings.GeneralChannelName then
local generalChannel = ChatWindow:GetChannel(ChatSettings.GeneralChannelName)
if generalChannel then
generalChannel:AddMessagesToChannelByTimeStamp(messageLog, startIndex)
end
end
end
end
if (welcomeMessage ~= "") then
local welcomeMessageObject = {
ID = -1,
FromSpeaker = nil,
SpeakerUserId = 0,
OriginalChannel = channel,
IsFiltered = true,
MessageLength = string.len(welcomeMessage),
Message = trimTrailingSpaces(welcomeMessage),
MessageType = ChatConstants.MessageTypeWelcome,
Time = os.time(),
ExtraData = nil,
}
channelObj:AddMessageToChannel(welcomeMessageObject)
if getEchoMessagesInGeneral() and addWelcomeMessageToGeneralChannel and not ChatSettings.ShowChannelsBar then
if channel ~= ChatSettings.GeneralChannelName then
local generalChannel = ChatWindow:GetChannel(ChatSettings.GeneralChannelName)
if generalChannel then
generalChannel:AddMessageToChannel(welcomeMessageObject)
end
end
end
end
DoFadeInFromNewInformation()
end
end
EventFolder.OnChannelJoined.OnClientEvent:connect(function(channel, welcomeMessage, messageLog, channelNameColor)
HandleChannelJoined(channel, welcomeMessage, messageLog, channelNameColor, false, true)
end)
EventFolder.OnChannelLeft.OnClientEvent:connect(function(channel)
ChatWindow:RemoveChannel(channel)
DoFadeInFromNewInformation()
end)
EventFolder.OnMuted.OnClientEvent:connect(function(channel)
--// Do something eventually maybe?
--// This used to take away the chat bar in channels the player was muted in.
--// We found out this behavior was inconvenient for doing chat commands though.
end)
EventFolder.OnUnmuted.OnClientEvent:connect(function(channel)
--// Same as above.
end)
EventFolder.OnMainChannelSet.OnClientEvent:connect(function(channel)
DoSwitchCurrentChannel(channel)
end)
coroutine.wrap(function()
-- ChannelNameColorUpdated may not exist if the client version is older than the server version.
local ChannelNameColorUpdated = DefaultChatSystemChatEvents:WaitForChild("ChannelNameColorUpdated", 5)
if ChannelNameColorUpdated then
ChannelNameColorUpdated.OnClientEvent:connect(function(channelName, channelNameColor)
ChatBar:SetChannelNameColor(channelName, channelNameColor)
end)
end
end)()
--- Interaction with SetCore Player events.
local PlayerBlockedEvent = nil
local PlayerMutedEvent = nil
local PlayerUnBlockedEvent = nil
local PlayerUnMutedEvent = nil
-- This is pcalled because the SetCore methods may not be released yet.
pcall(function()
PlayerBlockedEvent = StarterGui:GetCore("PlayerBlockedEvent")
PlayerMutedEvent = StarterGui:GetCore("PlayerMutedEvent")
PlayerUnBlockedEvent = StarterGui:GetCore("PlayerUnblockedEvent")
PlayerUnMutedEvent = StarterGui:GetCore("PlayerUnmutedEvent")
end)
function SendSystemMessageToSelf(message)
local currentChannel = ChatWindow:GetCurrentChannel()
if currentChannel then
local messageData =
{
ID = -1,
FromSpeaker = nil,
SpeakerUserId = 0,
OriginalChannel = currentChannel.Name,
IsFiltered = true,
MessageLength = string.len(message),
Message = trimTrailingSpaces(message),
MessageType = ChatConstants.MessageTypeSystem,
Time = os.time(),
ExtraData = nil,
}
currentChannel:AddMessageToChannel(messageData)
end
end
function MutePlayer(player)
local mutePlayerRequest = DefaultChatSystemChatEvents:FindFirstChild("MutePlayerRequest")
if mutePlayerRequest then
return mutePlayerRequest:InvokeServer(player.Name)
end
return false
end
if PlayerBlockedEvent then
PlayerBlockedEvent.Event:connect(function(player)
if MutePlayer(player) then
SendSystemMessageToSelf(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatMain_SpeakerHasBeenBlocked",
string.format("Speaker '%s' has been blocked.", player.Name)
),
"{RBX_NAME}",player.Name
)
)
end
end)
end
if PlayerMutedEvent then
PlayerMutedEvent.Event:connect(function(player)
if MutePlayer(player) then
SendSystemMessageToSelf(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatMain_SpeakerHasBeenMuted",
string.format("Speaker '%s' has been muted.", player.Name)
),
"{RBX_NAME}", player.Name
)
)
end
end)
end
function UnmutePlayer(player)
local unmutePlayerRequest = DefaultChatSystemChatEvents:FindFirstChild("UnMutePlayerRequest")
if unmutePlayerRequest then
return unmutePlayerRequest:InvokeServer(player.Name)
end
return false
end
if PlayerUnBlockedEvent then
PlayerUnBlockedEvent.Event:connect(function(player)
if UnmutePlayer(player) then
SendSystemMessageToSelf(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatMain_SpeakerHasBeenUnBlocked",
string.format("Speaker '%s' has been unblocked.", player.Name)
),
"{RBX_NAME}",player.Name
)
)
end
end)
end
if PlayerUnMutedEvent then
PlayerUnMutedEvent.Event:connect(function(player)
if UnmutePlayer(player) then
SendSystemMessageToSelf(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatMain_SpeakerHasBeenUnMuted",
string.format("Speaker '%s' has been unmuted.", player.Name)
),
"{RBX_NAME}",player.Name
)
)
end
end)
end
-- Get a list of blocked users from the corescripts.
-- Spawned because this method can yeild.
spawn(function()
-- Pcalled because this method is not released on all platforms yet.
if LocalPlayer.UserId > 0 then
pcall(function()
local blockedUserIds = StarterGui:GetCore("GetBlockedUserIds")
if #blockedUserIds > 0 then
local setInitalBlockedUserIds = DefaultChatSystemChatEvents:FindFirstChild("SetBlockedUserIdsRequest")
if setInitalBlockedUserIds then
setInitalBlockedUserIds:FireServer(blockedUserIds)
end
end
end)
end
end)
spawn(function()
local success, canLocalUserChat = pcall(function()
return Chat:CanUserChatAsync(LocalPlayer.UserId)
end)
if success then
canChat = RunService:IsStudio() or canLocalUserChat
end
end)
local initData = EventFolder.GetInitDataRequest:InvokeServer()
-- Handle joining general channel first.
for i, channelData in pairs(initData.Channels) do
if channelData[1] == ChatSettings.GeneralChannelName then
HandleChannelJoined(channelData[1], channelData[2], channelData[3], channelData[4], true, false)
end
end
for i, channelData in pairs(initData.Channels) do
if channelData[1] ~= ChatSettings.GeneralChannelName then
HandleChannelJoined(channelData[1], channelData[2], channelData[3], channelData[4], true, false)
end
end
return moduleApiTable
================================================
FILE: src/Chat/ChatScript/init.client.lua
================================================
-- // FileName: ChatScript.lua
-- // Written by: Xsitsu
-- // Description: Hooks main chat module up to Topbar in corescripts.
local StarterGui = game:GetService("StarterGui")
local GuiService = game:GetService("GuiService")
local ChatService = game:GetService("Chat")
local MAX_COREGUI_CONNECTION_ATTEMPTS = 10
local ClientChatModules = ChatService:WaitForChild("ClientChatModules")
local ChatSettings = require(ClientChatModules:WaitForChild("ChatSettings"))
local function DoEverything()
local Chat = require(script:WaitForChild("ChatMain"))
local containerTable = {}
containerTable.ChatWindow = {}
containerTable.SetCore = {}
containerTable.GetCore = {}
containerTable.ChatWindow.ChatTypes = {}
containerTable.ChatWindow.ChatTypes.BubbleChatEnabled = ChatSettings.BubbleChatEnabled
containerTable.ChatWindow.ChatTypes.ClassicChatEnabled = ChatSettings.ClassicChatEnabled
--// Connection functions
local function ConnectEvent(name)
local event = Instance.new("BindableEvent")
event.Name = name
containerTable.ChatWindow[name] = event
event.Event:connect(function(...) Chat[name](Chat, ...) end)
end
local function ConnectFunction(name)
local func = Instance.new("BindableFunction")
func.Name = name
containerTable.ChatWindow[name] = func
func.OnInvoke = function(...) return Chat[name](Chat, ...) end
end
local function ReverseConnectEvent(name)
local event = Instance.new("BindableEvent")
event.Name = name
containerTable.ChatWindow[name] = event
Chat[name]:connect(function(...) event:Fire(...) end)
end
local function ConnectSignal(name)
local event = Instance.new("BindableEvent")
event.Name = name
containerTable.ChatWindow[name] = event
event.Event:connect(function(...) Chat[name]:fire(...) end)
end
local function ConnectSetCore(name)
local event = Instance.new("BindableEvent")
event.Name = name
containerTable.SetCore[name] = event
event.Event:connect(function(...) Chat[name.."Event"]:fire(...) end)
end
local function ConnectGetCore(name)
local func = Instance.new("BindableFunction")
func.Name = name
containerTable.GetCore[name] = func
func.OnInvoke = function(...) return Chat["f"..name](...) end
end
--// Do connections
ConnectEvent("ToggleVisibility")
ConnectEvent("SetVisible")
ConnectEvent("FocusChatBar")
ConnectEvent("EnterWhisperState")
ConnectFunction("GetVisibility")
ConnectFunction("GetMessageCount")
ConnectEvent("TopbarEnabledChanged")
ConnectFunction("IsFocused")
ReverseConnectEvent("ChatBarFocusChanged")
ReverseConnectEvent("VisibilityStateChanged")
ReverseConnectEvent("MessagesChanged")
ReverseConnectEvent("MessagePosted")
ConnectSignal("CoreGuiEnabled")
ConnectSetCore("ChatMakeSystemMessage")
ConnectSetCore("ChatWindowPosition")
ConnectSetCore("ChatWindowSize")
ConnectGetCore("ChatWindowPosition")
ConnectGetCore("ChatWindowSize")
ConnectSetCore("ChatBarDisabled")
ConnectGetCore("ChatBarDisabled")
ConnectEvent("SpecialKeyPressed")
SetCoreGuiChatConnections(containerTable)
end
function SetCoreGuiChatConnections(containerTable)
local tries = 0
while tries < MAX_COREGUI_CONNECTION_ATTEMPTS do
tries = tries + 1
local success, ret = pcall(function() StarterGui:SetCore("CoreGuiChatConnections", containerTable) end)
if success then
break
end
if not success and tries == MAX_COREGUI_CONNECTION_ATTEMPTS then
error("Error calling SetCore CoreGuiChatConnections: " .. ret)
end
wait()
end
end
function checkBothChatTypesDisabled()
if ChatSettings.BubbleChatEnabled ~= nil then
if ChatSettings.ClassicChatEnabled ~= nil then
return not (ChatSettings.BubbleChatEnabled or ChatSettings.ClassicChatEnabled)
end
end
return false
end
if (not GuiService:IsTenFootInterface()) and (not game:GetService('UserInputService').VREnabled) then
if not checkBothChatTypesDisabled() then
DoEverything()
else
local containerTable = {}
containerTable.ChatWindow = {}
containerTable.ChatWindow.ChatTypes = {}
containerTable.ChatWindow.ChatTypes.BubbleChatEnabled = false
containerTable.ChatWindow.ChatTypes.ClassicChatEnabled = false
SetCoreGuiChatConnections(containerTable)
end
end
================================================
FILE: src/Chat/ChatServiceRunner/ChatChannel.lua
================================================
-- // FileName: ChatChannel.lua
-- // Written by: Xsitsu
-- // Description: A representation of one channel that speakers can chat in.
local forceNewFilterAPI = false
local IN_GAME_CHAT_USE_NEW_FILTER_API
do
local textServiceExists = (game:GetService("TextService") ~= nil)
local success, enabled = pcall(function() return UserSettings():IsUserFeatureEnabled("UserInGameChatUseNewFilterAPIV2") end)
local flagEnabled = (success and enabled)
IN_GAME_CHAT_USE_NEW_FILTER_API = (forceNewFilterAPI or flagEnabled) and textServiceExists
end
local module = {}
local modulesFolder = script.Parent
local Chat = game:GetService("Chat")
local RunService = game:GetService("RunService")
local replicatedModules = Chat:WaitForChild("ClientChatModules")
--////////////////////////////// Include
--//////////////////////////////////////
local ChatConstants = require(replicatedModules:WaitForChild("ChatConstants"))
local Util = require(modulesFolder:WaitForChild("Util"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = { Get = function(key,default) return default end } end
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:SendSystemMessage(message, extraData)
local messageObj = self:InternalCreateMessageObject(message, nil, true, extraData)
local success, err = pcall(function() self.eMessagePosted:Fire(messageObj) end)
if not success and err then
print("Error posting message: " ..err)
end
self:InternalAddMessageToHistoryLog(messageObj)
for i, speaker in pairs(self.Speakers) do
speaker:InternalSendSystemMessage(messageObj, self.Name)
end
return messageObj
end
function methods:SendSystemMessageToSpeaker(message, speakerName, extraData)
local speaker = self.Speakers[speakerName]
if (speaker) then
local messageObj = self:InternalCreateMessageObject(message, nil, true, extraData)
speaker:InternalSendSystemMessage(messageObj, self.Name)
else
warn(string.format("Speaker '%s' is not in channel '%s' and cannot be sent a system message", speakerName, self.Name))
end
end
function methods:SendMessageObjToFilters(message, messageObj, fromSpeaker)
local oldMessage = messageObj.Message
messageObj.Message = message
self:InternalDoMessageFilter(fromSpeaker.Name, messageObj, self.Name)
self.ChatService:InternalDoMessageFilter(fromSpeaker.Name, messageObj, self.Name)
local newMessage = messageObj.Message
messageObj.Message = oldMessage
return newMessage
end
function methods:CanCommunicateByUserId(userId1, userId2)
if RunService:IsStudio() then
return true
end
-- UserId is set as 0 for non player speakers.
if userId1 == 0 or userId2 == 0 then
return true
end
local success, canCommunicate = pcall(function()
return Chat:CanUsersChatAsync(userId1, userId2)
end)
return success and canCommunicate
end
function methods:CanCommunicate(speakerObj1, speakerObj2)
local player1 = speakerObj1:GetPlayer()
local player2 = speakerObj2:GetPlayer()
if player1 and player2 then
return self:CanCommunicateByUserId(player1.UserId, player2.UserId)
end
return true
end
function methods:SendMessageToSpeaker(message, speakerName, fromSpeakerName, extraData)
local speakerTo = self.Speakers[speakerName]
local speakerFrom = self.ChatService:GetSpeaker(fromSpeakerName)
if speakerTo and speakerFrom then
local isMuted = speakerTo:IsSpeakerMuted(fromSpeakerName)
if isMuted then
return
end
if not self:CanCommunicate(speakerTo, speakerFrom) then
return
end
-- We need to claim the message is filtered even if it not in this case for compatibility with legacy client side code.
local isFiltered = speakerName == fromSpeakerName
local messageObj = self:InternalCreateMessageObject(message, fromSpeakerName, isFiltered, extraData)
message = self:SendMessageObjToFilters(message, messageObj, fromSpeakerName)
speakerTo:InternalSendMessage(messageObj, self.Name)
--// START FFLAG
if (not IN_GAME_CHAT_USE_NEW_FILTER_API) then --// USES FFLAG
--// OLD BEHAVIOR
local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message, speakerName)
if filteredMessage then
messageObj.Message = filteredMessage
messageObj.IsFiltered = true
speakerTo:InternalSendFilteredMessage(messageObj, self.Name)
end
--// OLD BEHAVIOR
else
--// NEW BEHAVIOR
local textContext = self.Private and Enum.TextFilterContext.PrivateChat or Enum.TextFilterContext.PublicChat
local filterSuccess, isFilterResult, filteredMessage = self.ChatService:InternalApplyRobloxFilterNewAPI(
messageObj.FromSpeaker,
message,
textContext
)
if (filterSuccess) then
messageObj.FilterResult = filteredMessage
messageObj.IsFilterResult = isFilterResult
messageObj.IsFiltered = true
speakerTo:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
end
--// NEW BEHAVIOR
end
--// END FFLAG
else
warn(string.format("Speaker '%s' is not in channel '%s' and cannot be sent a message", speakerName, self.Name))
end
end
function methods:KickSpeaker(speakerName, reason)
local speaker = self.ChatService:GetSpeaker(speakerName)
if (not speaker) then
error("Speaker \"" .. speakerName .. "\" does not exist!")
end
local messageToSpeaker = ""
local messageToChannel = ""
if (reason) then
messageToSpeaker = string.format("You were kicked from '%s' for the following reason(s): %s", self.Name, reason)
messageToChannel = string.format("%s was kicked for the following reason(s): %s", speakerName, reason)
else
messageToSpeaker = string.format("You were kicked from '%s'", self.Name)
messageToChannel = string.format("%s was kicked", speakerName)
end
self:SendSystemMessageToSpeaker(messageToSpeaker, speakerName)
speaker:LeaveChannel(self.Name)
self:SendSystemMessage(messageToChannel)
end
function methods:MuteSpeaker(speakerName, reason, length)
local speaker = self.ChatService:GetSpeaker(speakerName)
if (not speaker) then
error("Speaker \"" .. speakerName .. "\" does not exist!")
end
self.Mutes[speakerName:lower()] = (length == 0 or length == nil) and 0 or (os.time() + length)
if (reason) then
self:SendSystemMessage(string.format("%s was muted for the following reason(s): %s", speakerName, reason))
end
local success, err = pcall(function() self.eSpeakerMuted:Fire(speakerName, reason, length) end)
if not success and err then
print("Error mutting speaker: " ..err)
end
local spkr = self.ChatService:GetSpeaker(speakerName)
if (spkr) then
local success, err = pcall(function() spkr.eMuted:Fire(self.Name, reason, length) end)
if not success and err then
print("Error mutting speaker: " ..err)
end
end
end
function methods:UnmuteSpeaker(speakerName)
local speaker = self.ChatService:GetSpeaker(speakerName)
if (not speaker) then
error("Speaker \"" .. speakerName .. "\" does not exist!")
end
self.Mutes[speakerName:lower()] = nil
local success, err = pcall(function() self.eSpeakerUnmuted:Fire(speakerName) end)
if not success and err then
print("Error unmuting speaker: " ..err)
end
local spkr = self.ChatService:GetSpeaker(speakerName)
if (spkr) then
local success, err = pcall(function() spkr.eUnmuted:Fire(self.Name) end)
if not success and err then
print("Error unmuting speaker: " ..err)
end
end
end
function methods:IsSpeakerMuted(speakerName)
return (self.Mutes[speakerName:lower()] ~= nil)
end
function methods:GetSpeakerList()
local list = {}
for i, speaker in pairs(self.Speakers) do
table.insert(list, speaker.Name)
end
return list
end
function methods:RegisterFilterMessageFunction(funcId, func, priority)
if self.FilterMessageFunctions:HasFunction(funcId) then
error(string.format("FilterMessageFunction '%s' already exists", funcId))
end
self.FilterMessageFunctions:AddFunction(funcId, func, priority)
end
function methods:FilterMessageFunctionExists(funcId)
return self.FilterMessageFunctions:HasFunction(funcId)
end
function methods:UnregisterFilterMessageFunction(funcId)
if not self.FilterMessageFunctions:HasFunction(funcId) then
error(string.format("FilterMessageFunction '%s' does not exists", funcId))
end
self.FilterMessageFunctions:RemoveFunction(funcId)
end
function methods:RegisterProcessCommandsFunction(funcId, func, priority)
if self.ProcessCommandsFunctions:HasFunction(funcId) then
error(string.format("ProcessCommandsFunction '%s' already exists", funcId))
end
self.ProcessCommandsFunctions:AddFunction(funcId, func, priority)
end
function methods:ProcessCommandsFunctionExists(funcId)
return self.ProcessCommandsFunctions:HasFunction(funcId)
end
function methods:UnregisterProcessCommandsFunction(funcId)
if not self.ProcessCommandsFunctions:HasFunction(funcId) then
error(string.format("ProcessCommandsFunction '%s' does not exist", funcId))
end
self.ProcessCommandsFunctions:RemoveFunction(funcId)
end
local function ShallowCopy(table)
local copy = {}
for i, v in pairs(table) do
copy[i] = v
end
return copy
end
function methods:GetHistoryLog()
return ShallowCopy(self.ChatHistory)
end
function methods:GetHistoryLogForSpeaker(speaker)
local userId = -1
local player = speaker:GetPlayer()
if player then
userId = player.UserId
end
local chatlog = {}
--// START FFLAG
if (not IN_GAME_CHAT_USE_NEW_FILTER_API) then --// USES FFLAG
--// OLD BEHAVIOR
for i = 1, #self.ChatHistory do
local logUserId = self.ChatHistory[i].SpeakerUserId
if self:CanCommunicateByUserId(userId, logUserId) then
table.insert(chatlog, ShallowCopy(self.ChatHistory[i]))
end
end
--// OLD BEHAVIOR
else
--// NEW BEHAVIOR
for i = 1, #self.ChatHistory do
local logUserId = self.ChatHistory[i].SpeakerUserId
if self:CanCommunicateByUserId(userId, logUserId) then
local messageObj = ShallowCopy(self.ChatHistory[i])
--// Since we're using the new filter API, we need to convert the stored filter result
--// into an actual string message to send to players for their chat history.
--// System messages aren't filtered the same way, so they just have a regular
--// text value in the Message field.
if (messageObj.MessageType == ChatConstants.MessageTypeDefault or messageObj.MessageType == ChatConstants.MessageTypeMeCommand) then
local filterResult = messageObj.FilterResult
if (messageObj.IsFilterResult) then
if (player) then
messageObj.Message = filterResult:GetChatForUserAsync(player.UserId)
else
messageObj.Message = filterResult:GetNonChatStringForBroadcastAsync()
end
else
messageObj.Message = filterResult
end
end
table.insert(chatlog, messageObj)
end
end
--// NEW BEHAVIOR
end
--// END FFLAG
return chatlog
end
--///////////////// Internal-Use Methods
--//////////////////////////////////////
function methods:InternalDestroy()
for i, speaker in pairs(self.Speakers) do
speaker:LeaveChannel(self.Name)
end
self.eDestroyed:Fire()
self.eDestroyed:Destroy()
self.eMessagePosted:Destroy()
self.eSpeakerJoined:Destroy()
self.eSpeakerLeft:Destroy()
self.eSpeakerMuted:Destroy()
self.eSpeakerUnmuted:Destroy()
end
function methods:InternalDoMessageFilter(speakerName, messageObj, channel)
local filtersIterator = self.FilterMessageFunctions:GetIterator()
for funcId, func, priority in filtersIterator do
local success, errorMessage = pcall(function()
func(speakerName, messageObj, channel)
end)
if not success then
warn(string.format("DoMessageFilter Function '%s' failed for reason: %s", funcId, errorMessage))
end
end
end
function methods:InternalDoProcessCommands(speakerName, message, channel)
local commandsIterator = self.ProcessCommandsFunctions:GetIterator()
for funcId, func, priority in commandsIterator do
local success, returnValue = pcall(function()
local ret = func(speakerName, message, channel)
if type(ret) ~= "boolean" then
error("Process command functions must return a bool")
end
return ret
end)
if not success then
warn(string.format("DoProcessCommands Function '%s' failed for reason: %s", funcId, returnValue))
elseif returnValue then
return true
end
end
return false
end
function methods:InternalPostMessage(fromSpeaker, message, extraData)
if (self:InternalDoProcessCommands(fromSpeaker.Name, message, self.Name)) then return false end
if (self.Mutes[fromSpeaker.Name:lower()] ~= nil) then
local t = self.Mutes[fromSpeaker.Name:lower()]
if (t > 0 and os.time() > t) then
self:UnmuteSpeaker(fromSpeaker.Name)
else
self:SendSystemMessageToSpeaker(ChatLocalization:Get("GameChat_ChatChannel_MutedInChannel","You are muted and cannot talk in this channel"), fromSpeaker.Name)
return false
end
end
local messageObj = self:InternalCreateMessageObject(message, fromSpeaker.Name, false, extraData)
-- allow server to process the unfiltered message string
messageObj.Message = message
local processedMessage
pcall(function()
processedMessage = Chat:InvokeChatCallback(Enum.ChatCallbackType.OnServerReceivingMessage, messageObj)
end)
messageObj.Message = nil
if processedMessage then
-- developer server code's choice to mute the message
if processedMessage.ShouldDeliver == false then
return false
end
messageObj = processedMessage
end
message = self:SendMessageObjToFilters(message, messageObj, fromSpeaker)
local sentToList = {}
for i, speaker in pairs(self.Speakers) do
local isMuted = speaker:IsSpeakerMuted(fromSpeaker.Name)
if not isMuted and self:CanCommunicate(fromSpeaker, speaker) then
table.insert(sentToList, speaker.Name)
if speaker.Name == fromSpeaker.Name then
-- Send unfiltered message to speaker who sent the message.
local cMessageObj = ShallowCopy(messageObj)
cMessageObj.Message = message
cMessageObj.IsFiltered = true
-- We need to claim the message is filtered even if it not in this case for compatibility with legacy client side code.
speaker:InternalSendMessage(cMessageObj, self.Name)
else
speaker:InternalSendMessage(messageObj, self.Name)
end
end
end
local success, err = pcall(function() self.eMessagePosted:Fire(messageObj) end)
if not success and err then
print("Error posting message: " ..err)
end
--// START FFLAG
if (not IN_GAME_CHAT_USE_NEW_FILTER_API) then --// USES FFLAG
--// OLD BEHAVIOR
local filteredMessages = {}
for i, speakerName in pairs(sentToList) do
local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message, speakerName)
if filteredMessage then
filteredMessages[speakerName] = filteredMessage
else
return false
end
end
for i, speakerName in pairs(sentToList) do
local speaker = self.Speakers[speakerName]
if (speaker) then
local cMessageObj = ShallowCopy(messageObj)
cMessageObj.Message = filteredMessages[speakerName]
cMessageObj.IsFiltered = true
speaker:InternalSendFilteredMessage(cMessageObj, self.Name)
end
end
local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message)
if filteredMessage then
messageObj.Message = filteredMessage
else
return false
end
messageObj.IsFiltered = true
self:InternalAddMessageToHistoryLog(messageObj)
--// OLD BEHAVIOR
else
--// NEW BEHAVIOR
local textFilterContext = self.Private and Enum.TextFilterContext.PrivateChat or Enum.TextFilterContext.PublicChat
local filterSuccess, isFilterResult, filteredMessage = self.ChatService:InternalApplyRobloxFilterNewAPI(
messageObj.FromSpeaker,
message,
textFilterContext
)
if (filterSuccess) then
messageObj.FilterResult = filteredMessage
messageObj.IsFilterResult = isFilterResult
else
return false
end
messageObj.IsFiltered = true
self:InternalAddMessageToHistoryLog(messageObj)
for _, speakerName in pairs(sentToList) do
local speaker = self.Speakers[speakerName]
if (speaker) then
speaker:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
end
end
--// NEW BEHAVIOR
end
--// END FFLAG
-- One more pass is needed to ensure that no speakers do not recieve the message.
-- Otherwise a user could join while the message is being filtered who had not originally been sent the message.
local speakersMissingMessage = {}
for _, speaker in pairs(self.Speakers) do
local isMuted = speaker:IsSpeakerMuted(fromSpeaker.Name)
if not isMuted and self:CanCommunicate(fromSpeaker, speaker) then
local wasSentMessage = false
for _, sentSpeakerName in pairs(sentToList) do
if speaker.Name == sentSpeakerName then
wasSentMessage = true
break
end
end
if not wasSentMessage then
table.insert(speakersMissingMessage, speaker.Name)
end
end
end
--// START FFLAG
if (not IN_GAME_CHAT_USE_NEW_FILTER_API) then --// USES FFLAG
--// OLD BEHAVIOR
for _, speakerName in pairs(speakersMissingMessage) do
local speaker = self.Speakers[speakerName]
if speaker then
local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message, speakerName)
if filteredMessage == nil then
return false
end
local cMessageObj = ShallowCopy(messageObj)
cMessageObj.Message = filteredMessage
cMessageObj.IsFiltered = true
speaker:InternalSendFilteredMessage(cMessageObj, self.Name)
end
end
--// OLD BEHAVIOR
else
--// NEW BEHAVIOR
for _, speakerName in pairs(speakersMissingMessage) do
local speaker = self.Speakers[speakerName]
if speaker then
speaker:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
end
end
--// NEW BEHAVIOR
end
--// END FFLAG
return messageObj
end
function methods:InternalAddSpeaker(speaker)
if (self.Speakers[speaker.Name]) then
warn("Speaker \"" .. speaker.name .. "\" is already in the channel!")
return
end
self.Speakers[speaker.Name] = speaker
local success, err = pcall(function() self.eSpeakerJoined:Fire(speaker.Name) end)
if not success and err then
print("Error removing channel: " ..err)
end
end
function methods:InternalRemoveSpeaker(speaker)
if (not self.Speakers[speaker.Name]) then
warn("Speaker \"" .. speaker.name .. "\" is not in the channel!")
return
end
self.Speakers[speaker.Name] = nil
local success, err = pcall(function() self.eSpeakerLeft:Fire(speaker.Name) end)
if not success and err then
print("Error removing speaker: " ..err)
end
end
function methods:InternalRemoveExcessMessagesFromLog()
local remove = table.remove
while (#self.ChatHistory > self.MaxHistory) do
remove(self.ChatHistory, 1)
end
end
function methods:InternalAddMessageToHistoryLog(messageObj)
table.insert(self.ChatHistory, messageObj)
self:InternalRemoveExcessMessagesFromLog()
end
function methods:GetMessageType(message, fromSpeaker)
if fromSpeaker == nil then
return ChatConstants.MessageTypeSystem
end
return ChatConstants.MessageTypeDefault
end
function methods:InternalCreateMessageObject(message, fromSpeaker, isFiltered, extraData)
local messageType = self:GetMessageType(message, fromSpeaker)
local speakerUserId = -1
local speaker = nil
if fromSpeaker then
speaker = self.Speakers[fromSpeaker]
if speaker then
local player = speaker:GetPlayer()
if player then
speakerUserId = player.UserId
else
speakerUserId = 0
end
end
end
local messageObj =
{
ID = self.ChatService:InternalGetUniqueMessageId(),
FromSpeaker = fromSpeaker,
SpeakerUserId = speakerUserId,
OriginalChannel = self.Name,
MessageLength = string.len(message),
MessageType = messageType,
IsFiltered = isFiltered,
Message = isFiltered and message or nil,
--// These two get set by the new API. The comments are just here
--// to remind readers that they will exist so it's not super
--// confusing if they find them in the code but cannot find them
--// here.
--FilterResult = nil,
--IsFilterResult = false,
Time = os.time(),
ExtraData = {},
}
if speaker then
for k, v in pairs(speaker.ExtraData) do
messageObj.ExtraData[k] = v
end
end
if (extraData) then
for k, v in pairs(extraData) do
messageObj.ExtraData[k] = v
end
end
return messageObj
end
function methods:SetChannelNameColor(color)
self.ChannelNameColor = color
for i, speaker in pairs(self.Speakers) do
speaker:UpdateChannelNameColor(self.Name, color)
end
end
function methods:GetWelcomeMessageForSpeaker(speaker)
if self.GetWelcomeMessageFunction then
local welcomeMessage = self.GetWelcomeMessageFunction(speaker)
if welcomeMessage then
return welcomeMessage
end
end
return self.WelcomeMessage
end
function methods:RegisterGetWelcomeMessageFunction(func)
if type(func) ~= "function" then
error("RegisterGetWelcomeMessageFunction must be called with a function.")
end
self.GetWelcomeMessageFunction = func
end
function methods:UnRegisterGetWelcomeMessageFunction()
self.GetWelcomeMessageFunction = nil
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new(vChatService, name, welcomeMessage, channelNameColor)
local obj = setmetatable({}, methods)
obj.ChatService = vChatService
obj.Name = name
obj.WelcomeMessage = welcomeMessage or ""
obj.GetWelcomeMessageFunction = nil
obj.ChannelNameColor = channelNameColor
obj.Joinable = true
obj.Leavable = true
obj.AutoJoin = false
obj.Private = false
obj.Speakers = {}
obj.Mutes = {}
obj.MaxHistory = 200
obj.HistoryIndex = 0
obj.ChatHistory = {}
obj.FilterMessageFunctions = Util:NewSortedFunctionContainer()
obj.ProcessCommandsFunctions = Util:NewSortedFunctionContainer()
-- Make sure to destroy added binadable events in the InternalDestroy method.
obj.eDestroyed = Instance.new("BindableEvent")
obj.eMessagePosted = Instance.new("BindableEvent")
obj.eSpeakerJoined = Instance.new("BindableEvent")
obj.eSpeakerLeft = Instance.new("BindableEvent")
obj.eSpeakerMuted = Instance.new("BindableEvent")
obj.eSpeakerUnmuted = Instance.new("BindableEvent")
obj.MessagePosted = obj.eMessagePosted.Event
obj.SpeakerJoined = obj.eSpeakerJoined.Event
obj.SpeakerLeft = obj.eSpeakerLeft.Event
obj.SpeakerMuted = obj.eSpeakerMuted.Event
obj.SpeakerUnmuted = obj.eSpeakerUnmuted.Event
obj.Destroyed = obj.eDestroyed.Event
return obj
end
return module
================================================
FILE: src/Chat/ChatServiceRunner/ChatService.lua
================================================
-- // FileName: ChatService.lua
-- // Written by: Xsitsu
-- // Description: Manages creating and destroying ChatChannels and Speakers.
local MAX_FILTER_RETRIES = 3
local FILTER_BACKOFF_INTERVALS = {50/1000, 100/1000, 200/1000}
local MAX_FILTER_DURATION = 60
--- Constants used to decide when to notify that the chat filter is having issues filtering messages.
local FILTER_NOTIFCATION_THRESHOLD = 3 --Number of notifcation failures before an error message is output.
local FILTER_NOTIFCATION_INTERVAL = 60 --Time between error messages.
local FILTER_THRESHOLD_TIME = 60 --If there has not been an issue in this many seconds, the count of issues resets.
local module = {}
local RunService = game:GetService("RunService")
local Chat = game:GetService("Chat")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local modulesFolder = script.Parent
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
local errorTextColor = ChatSettings.ErrorMessageTextColor or Color3.fromRGB(245, 50, 50)
local errorExtraData = {ChatColor = errorTextColor}
--////////////////////////////// Include
--//////////////////////////////////////
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local ChatChannel = require(modulesFolder:WaitForChild("ChatChannel"))
local Speaker = require(modulesFolder:WaitForChild("Speaker"))
local Util = require(modulesFolder:WaitForChild("Util"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:AddChannel(channelName, autoJoin)
if (self.ChatChannels[channelName:lower()]) then
error(string.format("Channel %q alrady exists.", channelName))
end
local function DefaultChannelCommands(fromSpeaker, message)
if (message:lower() == "/leave") then
local channel = self:GetChannel(channelName)
local speaker = self:GetSpeaker(fromSpeaker)
if (channel and speaker) then
if (channel.Leavable) then
speaker:LeaveChannel(channelName)
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatService_YouHaveLeftChannel",
string.format("You have left channel '%s'", channelName)
),
"{RBX_NAME}",channelName),
"System"
)
else
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatService_CannotLeaveChannel","You cannot leave this channel."), channelName)
end
end
return true
end
return false
end
local channel = ChatChannel.new(self, channelName)
self.ChatChannels[channelName:lower()] = channel
channel:RegisterProcessCommandsFunction("default_commands", DefaultChannelCommands, ChatConstants.HighPriority)
local success, err = pcall(function() self.eChannelAdded:Fire(channelName) end)
if not success and err then
print("Error addding channel: " ..err)
end
if autoJoin ~= nil then
channel.AutoJoin = autoJoin
if autoJoin then
for _, speaker in pairs(self.Speakers) do
speaker:JoinChannel(channelName)
end
end
end
return channel
end
function methods:RemoveChannel(channelName)
if (self.ChatChannels[channelName:lower()]) then
local n = self.ChatChannels[channelName:lower()].Name
self.ChatChannels[channelName:lower()]:InternalDestroy()
self.ChatChannels[channelName:lower()] = nil
local success, err = pcall(function() self.eChannelRemoved:Fire(n) end)
if not success and err then
print("Error removing channel: " ..err)
end
else
warn(string.format("Channel %q does not exist.", channelName))
end
end
function methods:GetChannel(channelName)
return self.ChatChannels[channelName:lower()]
end
function methods:AddSpeaker(speakerName)
if (self.Speakers[speakerName:lower()]) then
error("Speaker \"" .. speakerName .. "\" already exists!")
end
local speaker = Speaker.new(self, speakerName)
self.Speakers[speakerName:lower()] = speaker
local success, err = pcall(function() self.eSpeakerAdded:Fire(speakerName) end)
if not success and err then
print("Error adding speaker: " ..err)
end
return speaker
end
function methods:InternalUnmuteSpeaker(speakerName)
for channelName, channel in pairs(self.ChatChannels) do
if channel:IsSpeakerMuted(speakerName) then
channel:UnmuteSpeaker(speakerName)
end
end
end
function methods:RemoveSpeaker(speakerName)
if (self.Speakers[speakerName:lower()]) then
local n = self.Speakers[speakerName:lower()].Name
self:InternalUnmuteSpeaker(n)
self.Speakers[speakerName:lower()]:InternalDestroy()
self.Speakers[speakerName:lower()] = nil
local success, err = pcall(function() self.eSpeakerRemoved:Fire(n) end)
if not success and err then
print("Error removing speaker: " ..err)
end
else
warn("Speaker \"" .. speakerName .. "\" does not exist!")
end
end
function methods:GetSpeaker(speakerName)
return self.Speakers[speakerName:lower()]
end
function methods:GetChannelList()
local list = {}
for i, channel in pairs(self.ChatChannels) do
if (not channel.Private) then
table.insert(list, channel.Name)
end
end
return list
end
function methods:GetAutoJoinChannelList()
local list = {}
for i, channel in pairs(self.ChatChannels) do
if channel.AutoJoin then
table.insert(list, channel)
end
end
return list
end
function methods:GetSpeakerList()
local list = {}
for i, speaker in pairs(self.Speakers) do
table.insert(list, speaker.Name)
end
return list
end
function methods:SendGlobalSystemMessage(message)
for i, speaker in pairs(self.Speakers) do
speaker:SendSystemMessage(message, nil)
end
end
function methods:RegisterFilterMessageFunction(funcId, func, priority)
if self.FilterMessageFunctions:HasFunction(funcId) then
error(string.format("FilterMessageFunction '%s' already exists", funcId))
end
self.FilterMessageFunctions:AddFunction(funcId, func, priority)
end
function methods:FilterMessageFunctionExists(funcId)
return self.FilterMessageFunctions:HasFunction(funcId)
end
function methods:UnregisterFilterMessageFunction(funcId)
if not self.FilterMessageFunctions:HasFunction(funcId) then
error(string.format("FilterMessageFunction '%s' does not exists", funcId))
end
self.FilterMessageFunctions:RemoveFunction(funcId)
end
function methods:RegisterProcessCommandsFunction(funcId, func, priority)
if self.ProcessCommandsFunctions:HasFunction(funcId) then
error(string.format("ProcessCommandsFunction '%s' already exists", funcId))
end
self.ProcessCommandsFunctions:AddFunction(funcId, func, priority)
end
function methods:ProcessCommandsFunctionExists(funcId)
return self.ProcessCommandsFunctions:HasFunction(funcId)
end
function methods:UnregisterProcessCommandsFunction(funcId)
if not self.ProcessCommandsFunctions:HasFunction(funcId) then
error(string.format("ProcessCommandsFunction '%s' does not exist", funcId))
end
self.ProcessCommandsFunctions:RemoveFunction(funcId)
end
local LastFilterNoficationTime = 0
local LastFilterIssueTime = 0
local FilterIssueCount = 0
function methods:InternalNotifyFilterIssue()
if (tick() - LastFilterIssueTime) > FILTER_THRESHOLD_TIME then
FilterIssueCount = 0
end
FilterIssueCount = FilterIssueCount + 1
LastFilterIssueTime = tick()
if FilterIssueCount >= FILTER_NOTIFCATION_THRESHOLD then
if (tick() - LastFilterNoficationTime) > FILTER_NOTIFCATION_INTERVAL then
LastFilterNoficationTime = tick()
local systemChannel = self:GetChannel("System")
if systemChannel then
systemChannel:SendSystemMessage(
ChatLocalization:Get(
"GameChat_ChatService_ChatFilterIssues",
"The chat filter is currently experiencing issues and messages may be slow to appear."
),
errorExtraData
)
end
end
end
end
local StudioMessageFilteredCache = {}
--///////////////// Internal-Use Methods
--//////////////////////////////////////
--DO NOT REMOVE THIS. Chat must be filtered or your game will face
--moderation.
function methods:InternalApplyRobloxFilter(speakerName, message, toSpeakerName) --// USES FFLAG
if (RunService:IsServer() and not RunService:IsStudio()) then
local fromSpeaker = self:GetSpeaker(speakerName)
local toSpeaker = toSpeakerName and self:GetSpeaker(toSpeakerName)
if fromSpeaker == nil then
return nil
end
local fromPlayerObj = fromSpeaker:GetPlayer()
local toPlayerObj = toSpeaker and toSpeaker:GetPlayer()
if fromPlayerObj == nil then
return message
end
local filterStartTime = tick()
local filterRetries = 0
while true do
local success, message = pcall(function()
if toPlayerObj then
return Chat:FilterStringAsync(message, fromPlayerObj, toPlayerObj)
else
return Chat:FilterStringForBroadcast(message, fromPlayerObj)
end
end)
if success then
return message
else
warn("Error filtering message:", message)
end
filterRetries = filterRetries + 1
if filterRetries > MAX_FILTER_RETRIES or (tick() - filterStartTime) > MAX_FILTER_DURATION then
self:InternalNotifyFilterIssue()
return nil
end
local backoffInterval = FILTER_BACKOFF_INTERVALS[math.min(#FILTER_BACKOFF_INTERVALS, filterRetries)]
-- backoffWait = backoffInterval +/- (0 -> backoffInterval)
local backoffWait = backoffInterval + ((math.random()*2 - 1) * backoffInterval)
wait(backoffWait)
end
else
--// Simulate filtering latency.
--// There is only latency the first time the message is filtered, all following calls will be instant.
if not StudioMessageFilteredCache[message] then
StudioMessageFilteredCache[message] = true
wait()
end
return message
end
return nil
end
--// Return values: bool filterSuccess, bool resultIsFilterObject, variant result
function methods:InternalApplyRobloxFilterNewAPI(speakerName, message, textFilterContext) --// USES FFLAG
local alwaysRunFilter = false
local runFilter = RunService:IsServer() and not RunService:IsStudio()
if (alwaysRunFilter or runFilter) then
local fromSpeaker = self:GetSpeaker(speakerName)
if fromSpeaker == nil then
return false, nil, nil
end
local fromPlayerObj = fromSpeaker:GetPlayer()
if fromPlayerObj == nil then
return true, false, message
end
local success, filterResult = pcall(function()
local ts = game:GetService("TextService")
local result = ts:FilterStringAsync(message, fromPlayerObj.UserId, textFilterContext)
return result
end)
if (success) then
return true, true, filterResult
else
warn("Error filtering message:", message, filterResult)
self:InternalNotifyFilterIssue()
return false, nil, nil
end
end
--// Simulate filtering latency.
wait()
return true, false, message
end
function methods:InternalDoMessageFilter(speakerName, messageObj, channel)
local filtersIterator = self.FilterMessageFunctions:GetIterator()
for funcId, func, priority in filtersIterator do
local success, errorMessage = pcall(function()
func(speakerName, messageObj, channel)
end)
if not success then
warn(string.format("DoMessageFilter Function '%s' failed for reason: %s", funcId, errorMessage))
end
end
end
function methods:InternalDoProcessCommands(speakerName, message, channel)
local commandsIterator = self.ProcessCommandsFunctions:GetIterator()
for funcId, func, priority in commandsIterator do
local success, returnValue = pcall(function()
local ret = func(speakerName, message, channel)
if type(ret) ~= "boolean" then
error("Process command functions must return a bool")
end
return ret
end)
if not success then
warn(string.format("DoProcessCommands Function '%s' failed for reason: %s", funcId, returnValue))
elseif returnValue then
return true
end
end
return false
end
function methods:InternalGetUniqueMessageId()
local id = self.MessageIdCounter
self.MessageIdCounter = id + 1
return id
end
function methods:InternalAddSpeakerWithPlayerObject(speakerName, playerObj, fireSpeakerAdded)
if (self.Speakers[speakerName:lower()]) then
error("Speaker \"" .. speakerName .. "\" already exists!")
end
local speaker = Speaker.new(self, speakerName)
speaker:InternalAssignPlayerObject(playerObj)
self.Speakers[speakerName:lower()] = speaker
if fireSpeakerAdded then
local success, err = pcall(function() self.eSpeakerAdded:Fire(speakerName) end)
if not success and err then
print("Error adding speaker: " ..err)
end
end
return speaker
end
function methods:InternalFireSpeakerAdded(speakerName)
local success, err = pcall(function() self.eSpeakerAdded:Fire(speakerName) end)
if not success and err then
print("Error firing speaker added: " ..err)
end
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new()
local obj = setmetatable({}, methods)
obj.MessageIdCounter = 0
obj.ChatChannels = {}
obj.Speakers = {}
obj.FilterMessageFunctions = Util:NewSortedFunctionContainer()
obj.ProcessCommandsFunctions = Util:NewSortedFunctionContainer()
obj.eChannelAdded = Instance.new("BindableEvent")
obj.eChannelRemoved = Instance.new("BindableEvent")
obj.eSpeakerAdded = Instance.new("BindableEvent")
obj.eSpeakerRemoved = Instance.new("BindableEvent")
obj.ChannelAdded = obj.eChannelAdded.Event
obj.ChannelRemoved = obj.eChannelRemoved.Event
obj.SpeakerAdded = obj.eSpeakerAdded.Event
obj.SpeakerRemoved = obj.eSpeakerRemoved.Event
obj.ChatServiceMajorVersion = 0
obj.ChatServiceMinorVersion = 5
return obj
end
return module.new()
================================================
FILE: src/Chat/ChatServiceRunner/Speaker.lua
================================================
-- // FileName: Speaker.lua
-- // Written by: Xsitsu
-- // Description: A representation of one entity that can chat in different ChatChannels.
local module = {}
local modulesFolder = script.Parent
--////////////////////////////// Methods
--//////////////////////////////////////
local function ShallowCopy(table)
local copy = {}
for i, v in pairs(table) do
copy[i] = v
end
return copy
end
local methods = {}
local lazyEventNames =
{
eDestroyed = true,
eSaidMessage = true,
eReceivedMessage = true,
eReceivedUnfilteredMessage = true,
eMessageDoneFiltering = true,
eReceivedSystemMessage = true,
eChannelJoined = true,
eChannelLeft = true,
eMuted = true,
eUnmuted = true,
eExtraDataUpdated = true,
eMainChannelSet = true,
eChannelNameColorUpdated = true,
}
local lazySignalNames =
{
Destroyed = "eDestroyed",
SaidMessage = "eSaidMessage",
ReceivedMessage = "eReceivedMessage",
ReceivedUnfilteredMessage = "eReceivedUnfilteredMessage",
RecievedUnfilteredMessage = "eReceivedUnfilteredMessage", -- legacy mispelling
MessageDoneFiltering = "eMessageDoneFiltering",
ReceivedSystemMessage = "eReceivedSystemMessage",
ChannelJoined = "eChannelJoined",
ChannelLeft = "eChannelLeft",
Muted = "eMuted",
Unmuted = "eUnmuted",
ExtraDataUpdated = "eExtraDataUpdated",
MainChannelSet = "eMainChannelSet",
ChannelNameColorUpdated = "eChannelNameColorUpdated"
}
methods.__index = function (self, k)
local fromMethods = rawget(methods, k)
if fromMethods then return fromMethods end
if lazyEventNames[k] and not rawget(self, k) then
rawset(self, k, Instance.new("BindableEvent"))
end
local lazySignalEventName = lazySignalNames[k]
if lazySignalEventName and not rawget(self, k) then
if not rawget(self, lazySignalEventName) then
rawset(self, lazySignalEventName, Instance.new("BindableEvent"))
end
rawset(self, k, rawget(self, lazySignalEventName).Event)
end
return rawget(self, k)
end
function methods:LazyFire(eventName, ...)
local createdEvent = rawget(self, eventName)
if createdEvent then
createdEvent:Fire(...)
end
end
function methods:SayMessage(message, channelName, extraData)
if (self.ChatService:InternalDoProcessCommands(self.Name, message, channelName)) then return end
if (not channelName) then return end
local channel = self.Channels[channelName:lower()]
if (not channel) then
error("Speaker is not in channel \"" .. channelName .. "\"")
end
local messageObj = channel:InternalPostMessage(self, message, extraData)
if (messageObj) then
local success, err = pcall(function() self:LazyFire("eSaidMessage", messageObj, channelName) end)
if not success and err then
print("Error saying message: " ..err)
end
end
return messageObj
end
function methods:JoinChannel(channelName)
if (self.Channels[channelName:lower()]) then
warn("Speaker is already in channel \"" .. channelName .. "\"")
return
end
local channel = self.ChatService:GetChannel(channelName)
if (not channel) then
error("Channel \"" .. channelName .. "\" does not exist!")
end
self.Channels[channelName:lower()] = channel
channel:InternalAddSpeaker(self)
local success, err = pcall(function()
self.eChannelJoined:Fire(channel.Name, channel:GetWelcomeMessageForSpeaker(self))
end)
if not success and err then
print("Error joining channel: " ..err)
end
end
function methods:LeaveChannel(channelName)
if (not self.Channels[channelName:lower()]) then
warn("Speaker is not in channel \"" .. channelName .. "\"")
return
end
local channel = self.Channels[channelName:lower()]
self.Channels[channelName:lower()] = nil
channel:InternalRemoveSpeaker(self)
local success, err = pcall(function()
self:LazyFire("eChannelLeft", channel.Name)
self.EventFolder.OnChannelLeft:FireClient(self.PlayerObj, channel.Name)
end)
if not success and err then
print("Error leaving channel: " ..err)
end
end
function methods:IsInChannel(channelName)
return (self.Channels[channelName:lower()] ~= nil)
end
function methods:GetChannelList()
local list = {}
for i, channel in pairs(self.Channels) do
table.insert(list, channel.Name)
end
return list
end
function methods:SendMessage(message, channelName, fromSpeaker, extraData)
local channel = self.Channels[channelName:lower()]
if (channel) then
channel:SendMessageToSpeaker(message, self.Name, fromSpeaker, extraData)
else
warn(string.format("Speaker '%s' is not in channel '%s' and cannot receive a message in it.", self.Name, channelName))
end
end
function methods:SendSystemMessage(message, channelName, extraData)
local channel = self.Channels[channelName:lower()]
if (channel) then
channel:SendSystemMessageToSpeaker(message, self.Name, extraData)
else
warn(string.format("Speaker '%s' is not in channel '%s' and cannot receive a system message in it.", self.Name, channelName))
end
end
function methods:GetPlayer()
return self.PlayerObj
end
function methods:SetExtraData(key, value)
self.ExtraData[key] = value
self:LazyFire("eExtraDataUpdated", key, value)
end
function methods:GetExtraData(key)
return self.ExtraData[key]
end
function methods:SetMainChannel(channelName)
local success, err = pcall(function()
self:LazyFire("eMainChannelSet", channelName)
self.EventFolder.OnMainChannelSet:FireClient(self.PlayerObj, channelName)
end)
if not success and err then
print("Error setting main channel: " ..err)
end
end
--- Used to mute a speaker so that this speaker does not see their messages.
function methods:AddMutedSpeaker(speakerName)
self.MutedSpeakers[speakerName:lower()] = true
end
function methods:RemoveMutedSpeaker(speakerName)
self.MutedSpeakers[speakerName:lower()] = false
end
function methods:IsSpeakerMuted(speakerName)
return self.MutedSpeakers[speakerName:lower()]
end
--///////////////// Internal-Use Methods
--//////////////////////////////////////
function methods:InternalDestroy()
for i, channel in pairs(self.Channels) do
channel:InternalRemoveSpeaker(self)
end
self.eDestroyed:Fire()
self.EventFolder = nil
self.eDestroyed:Destroy()
self.eSaidMessage:Destroy()
self.eReceivedMessage:Destroy()
self.eReceivedUnfilteredMessage:Destroy()
self.eMessageDoneFiltering:Destroy()
self.eReceivedSystemMessage:Destroy()
self.eChannelJoined:Destroy()
self.eChannelLeft:Destroy()
self.eMuted:Destroy()
self.eUnmuted:Destroy()
self.eExtraDataUpdated:Destroy()
self.eMainChannelSet:Destroy()
self.eChannelNameColorUpdated:Destroy()
end
function methods:InternalAssignPlayerObject(playerObj)
self.PlayerObj = playerObj
end
function methods:InternalAssignEventFolder(eventFolder)
self.EventFolder = eventFolder
end
function methods:InternalSendMessage(messageObj, channelName)
local success, err = pcall(function()
self:LazyFire("eReceivedUnfilteredMessage", messageObj, channelName)
self.EventFolder.OnNewMessage:FireClient(self.PlayerObj, messageObj, channelName)
end)
if not success and err then
print("Error sending internal message: " ..err)
end
end
function methods:InternalSendFilteredMessage(messageObj, channelName)
local success, err = pcall(function()
self:LazyFire("eReceivedMessage", messageObj, channelName)
self:LazyFire("eMessageDoneFiltering", messageObj, channelName)
self.EventFolder.OnMessageDoneFiltering:FireClient(self.PlayerObj, messageObj, channelName)
end)
if not success and err then
print("Error sending internal filtered message: " ..err)
end
end
--// This method is to be used with the new filter API. This method takes the
--// TextFilterResult objects and converts them into the appropriate string
--// messages for each player.
function methods:InternalSendFilteredMessageWithFilterResult(inMessageObj, channelName)
local messageObj = ShallowCopy(inMessageObj)
local oldFilterResult = messageObj.FilterResult
local player = self:GetPlayer()
local msg = ""
pcall(function()
if (messageObj.IsFilterResult) then
if (player) then
msg = oldFilterResult:GetChatForUserAsync(player.UserId)
else
msg = oldFilterResult:GetNonChatStringForBroadcastAsync()
end
else
msg = oldFilterResult
end
end)
--// Messages of 0 length are the result of two users not being allowed
--// to chat, or GetChatForUserAsync() failing. In both of these situations,
--// messages with length of 0 should not be sent.
if (#msg > 0) then
messageObj.Message = msg
messageObj.FilterResult = nil
self:InternalSendFilteredMessage(messageObj, channelName)
end
end
function methods:InternalSendSystemMessage(messageObj, channelName)
local success, err = pcall(function()
self:LazyFire("eReceivedSystemMessage", messageObj, channelName)
self.EventFolder.OnNewSystemMessage:FireClient(self.PlayerObj, messageObj, channelName)
end)
if not success and err then
print("Error sending internal system message: " ..err)
end
end
function methods:UpdateChannelNameColor(channelName, channelNameColor)
self:LazyFire("eChannelNameColorUpdated", channelName, channelNameColor)
self.EventFolder.ChannelNameColorUpdated:FireClient(self.PlayerObj, channelName, channelNameColor)
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new(vChatService, name)
local obj = setmetatable({}, methods)
obj.ChatService = vChatService
obj.PlayerObj = nil
obj.Name = name
obj.ExtraData = {}
obj.Channels = {}
obj.MutedSpeakers = {}
obj.EventFolder = nil
return obj
end
return module
================================================
FILE: src/Chat/ChatServiceRunner/Util.lua
================================================
-- // FileName: Util.lua
-- // Written by: TheGamer101
-- // Description: Utility code used by the server side chat implementation.
local Chat = game:GetService("Chat")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local DEFAULT_PRIORITY = ChatConstants.StandardPriority
if DEFAULT_PRIORITY == nil then
DEFAULT_PRIORITY = 10
end
local Util = {}
Util.__index = Util
local SortedFunctionContainer = {}; do
-- This sorted function container is used to handle the logic around storing filter functions and
-- command processors by priority.
local methods = {}
methods.__index = methods
function methods:RebuildProcessCommandsPriorities()
self.RegisteredPriorites = {}
for priority, functions in pairs(self.FunctionMap) do
local functionsEmpty = true
for funcId, funciton in pairs(functions) do
functionsEmpty = false
break
end
if not functionsEmpty then
table.insert(self.RegisteredPriorites, priority)
end
end
table.sort(self.RegisteredPriorites, function(a, b)
return a > b
end)
end
function methods:HasFunction(funcId)
if self.RegisteredFunctions[funcId] == nil then
return false
end
return true
end
function methods:RemoveFunction(funcId)
local functionPriority = self.RegisteredFunctions[funcId]
self.RegisteredFunctions[funcId] = nil
self.FunctionMap[functionPriority][funcId] = nil
self:RebuildProcessCommandsPriorities()
end
function methods:AddFunction(funcId, func, priority)
if priority == nil then
priority = DEFAULT_PRIORITY
end
if self.RegisteredFunctions[funcId] then
error(funcId .. " is already in use!")
end
self.RegisteredFunctions[funcId] = priority
if self.FunctionMap[priority] == nil then
self.FunctionMap[priority] = {}
end
self.FunctionMap[priority][funcId] = func
self:RebuildProcessCommandsPriorities()
end
function methods:GetIterator()
local priorityIndex = 1
local funcId = nil
local func = nil
return function()
while true do
if priorityIndex > #self.RegisteredPriorites then
return
end
local priority = self.RegisteredPriorites[priorityIndex]
funcId, func = next(self.FunctionMap[priority], funcId)
if funcId == nil then
priorityIndex = priorityIndex + 1
else
return funcId, func, priority
end
end
end
end
function SortedFunctionContainer.new()
local obj = setmetatable({}, methods)
obj.RegisteredFunctions = {}
obj.RegisteredPriorites = {}
obj.FunctionMap = {}
return obj
end
end
function Util:NewSortedFunctionContainer()
return SortedFunctionContainer.new()
end
return Util
================================================
FILE: src/Chat/ChatServiceRunner/init.server.lua
================================================
-- // FileName: ChatServiceRunner.lua
-- // Written by: Xsitsu
-- // Description: Main script to initialize ChatService and run ChatModules.
local EventFolderName = "DefaultChatSystemChatEvents"
local EventFolderParent = game:GetService("ReplicatedStorage")
local modulesFolder = script
local PlayersService = game:GetService("Players")
local RunService = game:GetService("RunService")
local Chat = game:GetService("Chat")
local ChatService = require(modulesFolder:WaitForChild("ChatService"))
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(Chat.ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = { Get = function(key,default) return default end } end
local useEvents = {}
local EventFolder = EventFolderParent:FindFirstChild(EventFolderName)
if (not EventFolder) then
EventFolder = Instance.new("Folder")
EventFolder.Name = EventFolderName
EventFolder.Archivable = false
EventFolder.Parent = EventFolderParent
end
--// No-opt connect Server>Client RemoteEvents to ensure they cannot be called
--// to fill the remote event queue.
local function emptyFunction()
--intentially empty
end
local function GetObjectWithNameAndType(parentObject, objectName, objectType)
for _, child in pairs(parentObject:GetChildren()) do
if (child:IsA(objectType) and child.Name == objectName) then
return child
end
end
return nil
end
local function CreateIfDoesntExist(parentObject, objectName, objectType)
local obj = GetObjectWithNameAndType(parentObject, objectName, objectType)
if (not obj) then
obj = Instance.new(objectType)
obj.Name = objectName
obj.Parent = parentObject
end
useEvents[objectName] = obj
return obj
end
--// All remote events will have a no-opt OnServerEvent connecdted on construction
local function CreateEventIfItDoesntExist(parentObject, objectName)
local obj = CreateIfDoesntExist(parentObject, objectName, "RemoteEvent")
obj.OnServerEvent:Connect(emptyFunction)
return obj
end
CreateEventIfItDoesntExist(EventFolder, "OnNewMessage")
CreateEventIfItDoesntExist(EventFolder, "OnMessageDoneFiltering")
CreateEventIfItDoesntExist(EventFolder, "OnNewSystemMessage")
CreateEventIfItDoesntExist(EventFolder, "OnChannelJoined")
CreateEventIfItDoesntExist(EventFolder, "OnChannelLeft")
CreateEventIfItDoesntExist(EventFolder, "OnMuted")
CreateEventIfItDoesntExist(EventFolder, "OnUnmuted")
CreateEventIfItDoesntExist(EventFolder, "OnMainChannelSet")
CreateEventIfItDoesntExist(EventFolder, "ChannelNameColorUpdated")
CreateEventIfItDoesntExist(EventFolder, "SayMessageRequest")
CreateEventIfItDoesntExist(EventFolder, "SetBlockedUserIdsRequest")
CreateIfDoesntExist(EventFolder, "GetInitDataRequest", "RemoteFunction")
CreateIfDoesntExist(EventFolder, "MutePlayerRequest", "RemoteFunction")
CreateIfDoesntExist(EventFolder, "UnMutePlayerRequest", "RemoteFunction")
EventFolder = useEvents
local function CreatePlayerSpeakerObject(playerObj)
--// If a developer already created a speaker object with the
--// name of a player and then a player joins and tries to
--// take that name, we first need to remove the old speaker object
local speaker = ChatService:GetSpeaker(playerObj.Name)
if (speaker) then
ChatService:RemoveSpeaker(playerObj.Name)
end
speaker = ChatService:InternalAddSpeakerWithPlayerObject(playerObj.Name, playerObj, false)
for _, channel in pairs(ChatService:GetAutoJoinChannelList()) do
speaker:JoinChannel(channel.Name)
end
speaker:InternalAssignEventFolder(EventFolder)
speaker.ChannelJoined:connect(function(channel, welcomeMessage)
local log = nil
local channelNameColor = nil
local channelObject = ChatService:GetChannel(channel)
if (channelObject) then
log = channelObject:GetHistoryLogForSpeaker(speaker)
channelNameColor = channelObject.ChannelNameColor
end
EventFolder.OnChannelJoined:FireClient(playerObj, channel, welcomeMessage, log, channelNameColor)
end)
speaker.Muted:connect(function(channel, reason, length)
EventFolder.OnMuted:FireClient(playerObj, channel, reason, length)
end)
speaker.Unmuted:connect(function(channel)
EventFolder.OnUnmuted:FireClient(playerObj, channel)
end)
ChatService:InternalFireSpeakerAdded(speaker.Name)
end
EventFolder.SayMessageRequest.OnServerEvent:connect(function(playerObj, message, channel)
if type(message) ~= "string" then
return
end
if type(channel) ~= "string" then
return
end
local speaker = ChatService:GetSpeaker(playerObj.Name)
if (speaker) then
return speaker:SayMessage(message, channel)
end
return nil
end)
EventFolder.MutePlayerRequest.OnServerInvoke = function(playerObj, muteSpeakerName)
if type(muteSpeakerName) ~= "string" then
return
end
local speaker = ChatService:GetSpeaker(playerObj.Name)
if speaker then
local muteSpeaker = ChatService:GetSpeaker(muteSpeakerName)
if muteSpeaker then
speaker:AddMutedSpeaker(muteSpeaker.Name)
return true
end
end
return false
end
EventFolder.UnMutePlayerRequest.OnServerInvoke = function(playerObj, unmuteSpeakerName)
if type(unmuteSpeakerName) ~= "string" then
return
end
local speaker = ChatService:GetSpeaker(playerObj.Name)
if speaker then
local unmuteSpeaker = ChatService:GetSpeaker(unmuteSpeakerName)
if unmuteSpeaker then
speaker:RemoveMutedSpeaker(unmuteSpeaker.Name)
return true
end
end
return false
end
-- Map storing Player -> Blocked user Ids.
local BlockedUserIdsMap = {}
PlayersService.PlayerAdded:connect(function(newPlayer)
for player, blockedUsers in pairs(BlockedUserIdsMap) do
local speaker = ChatService:GetSpeaker(player.Name)
if speaker then
for i = 1, #blockedUsers do
local blockedUserId = blockedUsers[i]
if blockedUserId == newPlayer.UserId then
speaker:AddMutedSpeaker(newPlayer.Name)
end
end
end
end
end)
PlayersService.PlayerRemoving:connect(function(removingPlayer)
BlockedUserIdsMap[removingPlayer] = nil
end)
EventFolder.SetBlockedUserIdsRequest.OnServerEvent:connect(function(player, blockedUserIdsList)
if type(blockedUserIdsList) ~= "table" then
return
end
BlockedUserIdsMap[player] = blockedUserIdsList
local speaker = ChatService:GetSpeaker(player.Name)
if speaker then
for i = 1, #blockedUserIdsList do
if type(blockedUserIdsList[i]) == "number" then
local blockedPlayer = PlayersService:GetPlayerByUserId(blockedUserIdsList[i])
if blockedPlayer then
speaker:AddMutedSpeaker(blockedPlayer.Name)
end
end
end
end
end)
EventFolder.GetInitDataRequest.OnServerInvoke = (function(playerObj)
local speaker = ChatService:GetSpeaker(playerObj.Name)
if not (speaker and speaker:GetPlayer()) then
CreatePlayerSpeakerObject(playerObj)
speaker = ChatService:GetSpeaker(playerObj.Name)
end
local data = {}
data.Channels = {}
data.SpeakerExtraData = {}
for _, channelName in pairs(speaker:GetChannelList()) do
local channelObj = ChatService:GetChannel(channelName)
if (channelObj) then
local channelData =
{
channelName,
channelObj:GetWelcomeMessageForSpeaker(speaker),
channelObj:GetHistoryLogForSpeaker(speaker),
channelObj.ChannelNameColor,
}
table.insert(data.Channels, channelData)
end
end
for _, oSpeakerName in pairs(ChatService:GetSpeakerList()) do
local oSpeaker = ChatService:GetSpeaker(oSpeakerName)
data.SpeakerExtraData[oSpeakerName] = oSpeaker.ExtraData
end
return data
end)
local function DoJoinCommand(speakerName, channelName, fromChannelName)
local speaker = ChatService:GetSpeaker(speakerName)
local channel = ChatService:GetChannel(channelName)
if (speaker) then
if (channel) then
if (channel.Joinable) then
if (not speaker:IsInChannel(channel.Name)) then
speaker:JoinChannel(channel.Name)
else
speaker:SetMainChannel(channel.Name)
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_SwitchChannel_NowInChannel",
string.format("You are now chatting in channel: '%s'", channel.Name)
),
"{RBX_NAME}",channel.Name),
channel.Name
)
end
else
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatServiceRunner_YouCannotJoinChannel",
("You cannot join channel '" .. channelName .. "'.")
),
"{RBX_NAME}",channelName),
fromChannelName
)
end
else
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatServiceRunner_ChannelDoesNotExist",
("Channel '" .. channelName .. "' does not exist.")
),
"{RBX_NAME}",channelName),
fromChannelName
)
end
end
end
local function DoLeaveCommand(speakerName, channelName, fromChannelName)
local speaker = ChatService:GetSpeaker(speakerName)
local channel = ChatService:GetChannel(channelName)
if (speaker) then
if (speaker:IsInChannel(channelName)) then
if (channel.Leavable) then
speaker:LeaveChannel(channel.Name)
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatService_YouHaveLeftChannel",
string.format("You have left channel '%s'", channelName)
),
"{RBX_NAME}",channel.Name),
"System"
)
else
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatServiceRunner_YouCannotLeaveChannel",
("You cannot leave channel '" .. channelName .. "'.")
),
"{RBX_NAME}",channelName),
fromChannelName
)
end
else
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatServiceRunner_YouAreNotInChannel",
("You are not in channel '" .. channelName .. "'.")
),
"{RBX_NAME}",channelName),
fromChannelName
)
end
end
end
ChatService:RegisterProcessCommandsFunction("default_commands", function(fromSpeaker, message, channel)
if (string.sub(message, 1, 6):lower() == "/join ") then
DoJoinCommand(fromSpeaker, string.sub(message, 7), channel)
return true
elseif (string.sub(message, 1, 3):lower() == "/j ") then
DoJoinCommand(fromSpeaker, string.sub(message, 4), channel)
return true
elseif (string.sub(message, 1, 7):lower() == "/leave ") then
DoLeaveCommand(fromSpeaker, string.sub(message, 8), channel)
return true
elseif (string.sub(message, 1, 3):lower() == "/l ") then
DoLeaveCommand(fromSpeaker, string.sub(message, 4), channel)
return true
elseif (string.sub(message, 1, 3) == "/e " or string.sub(message, 1, 7) == "/emote ") then
-- Just don't show these in the chatlog. The animation script listens on these.
return true
end
return false
end)
if ChatSettings.GeneralChannelName and ChatSettings.GeneralChannelName ~= "" then
local allChannel = ChatService:AddChannel(ChatSettings.GeneralChannelName)
allChannel.Leavable = false
allChannel.AutoJoin = true
allChannel:RegisterGetWelcomeMessageFunction(function(speaker)
if RunService:IsStudio() then
return nil
end
local player = speaker:GetPlayer()
if player then
local success, canChat = pcall(function()
return Chat:CanUserChatAsync(player.UserId)
end)
if success and not canChat then
return ""
end
end
end)
end
local systemChannel = ChatService:AddChannel("System")
systemChannel.Leavable = false
systemChannel.AutoJoin = true
systemChannel.WelcomeMessage = ChatLocalization:Get(
"GameChat_ChatServiceRunner_SystemChannelWelcomeMessage", "This channel is for system and game notifications."
)
systemChannel.SpeakerJoined:connect(function(speakerName)
systemChannel:MuteSpeaker(speakerName)
end)
local function TryRunModule(module)
if module:IsA("ModuleScript") then
local ret = require(module)
if (type(ret) == "function") then
ret(ChatService)
end
end
end
local modules = Chat:WaitForChild("ChatModules")
modules.ChildAdded:connect(function(child)
local success, returnval = pcall(TryRunModule, child)
if not success and returnval then
print("Error running module " ..child.Name.. ": " ..returnval)
end
end)
for _, module in pairs(modules:GetChildren()) do
local success, returnval = pcall(TryRunModule, module)
if not success and returnval then
print("Error running module " ..module.Name.. ": " ..returnval)
end
end
PlayersService.PlayerRemoving:connect(function(playerObj)
if (ChatService:GetSpeaker(playerObj.Name)) then
ChatService:RemoveSpeaker(playerObj.Name)
end
end)
================================================
FILE: src/Chat/ClientChatModules/ChatConstants.lua
================================================
-- // FileName: ChatConstants.lua
-- // Written by: TheGamer101
-- // Description: Module for creating chat constants shared between server and client.
local module = {}
---[[ Message Types ]]
module.MessageTypeDefault = "Message"
module.MessageTypeSystem = "System"
module.MessageTypeMeCommand = "MeCommand"
module.MessageTypeWelcome = "Welcome"
module.MessageTypeSetCore = "SetCore"
module.MessageTypeWhisper = "Whisper"
--[[ Version ]]
module.MajorVersion = 0
module.MinorVersion = 8
module.BuildVersion = "2018.05.16"
---[[ Command/Filter Priorities ]]
module.VeryLowPriority = -5
module.LowPriority = 0
module.StandardPriority = 10
module.HighPriority = 20
module.VeryHighPriority = 25
module.WhisperChannelPrefix = "To "
return module
================================================
FILE: src/Chat/ClientChatModules/ChatLocalization.lua
================================================
local LocalizationService = game:GetService("LocalizationService")
local ChatService = game:GetService("Chat")
local ChatLocalization = {
_hasFetchedLocalization = false,
}
function ChatLocalization:_getTranslator()
if not self._translator and not self._hasFetchedLocalization then
-- Don't keep retrying if this fails.
self._hasFetchedLocalization = true
local localizationTable = ChatService:WaitForChild("ChatLocalization", 4)
if localizationTable then
self._translator = localizationTable:GetTranslator(LocalizationService.RobloxLocaleId)
LocalizationService:GetPropertyChangedSignal("RobloxLocaleId"):Connect(function()
-- If RobloxLocaleId changes invalidate the cached Translator.
self._hasFetchedLocalization = false
self._translator = nil
end)
else
-- warn("Missing ChatLocalization. Chat interface will not be localized.")
end
end
return self._translator
end
function ChatLocalization:Get(key, default)
local rtv = default
pcall(function()
local translator = self:_getTranslator()
if translator then
rtv = translator:FormatByKey(key)
else
-- warn("Missing Translator. Used default for", key)
end
end)
return rtv
end
return ChatLocalization
================================================
FILE: src/Chat/ClientChatModules/ChatSettings.lua
================================================
-- // FileName: ChatSettings.lua
-- // Written by: Xsitsu
-- // Description: Settings module for configuring different aspects of the chat window.
local PlayersService = game:GetService("Players")
local clientChatModules = script.Parent
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
local module = {}
---[[ Chat Behaviour Settings ]]
module.WindowDraggable = false
module.WindowResizable = false
module.ShowChannelsBar = false
module.GamepadNavigationEnabled = false
module.ShowUserOwnFilteredMessage = true --Show a user the filtered version of their message rather than the original.
-- Make the chat work when the top bar is off
module.ChatOnWithTopBarOff = false
module.ScreenGuiDisplayOrder = 0 -- The DisplayOrder value for the ScreenGui containing the chat.
module.ShowFriendJoinNotification = true -- Show a notification in the chat when a players friend joins the game.
--- Replace with true/false to force the chat type. Otherwise this will default to the setting on the website.
module.BubbleChatEnabled = true
module.ClassicChatEnabled = true
---[[ Chat Text Size Settings ]]
module.ChatWindowTextSize = 19
module.ChatChannelsTabTextSize = 19
module.ChatBarTextSize = 19
module.ChatWindowTextSizePhone = 14
module.ChatChannelsTabTextSizePhone = 19
module.ChatBarTextSizePhone = 14
---[[ Font Settings ]]
module.DefaultFont = Enum.Font.SourceSansBold
module.ChatBarFont = Enum.Font.SourceSansBold
----[[ Color Settings ]]
module.BackGroundColor = Color3.new(0.05, 0.05, 0.05)
module.DefaultChatColor = Color3.new(1, 1, 1)
module.DefaultMessageColor = Color3.new(1, 1, 1)
module.DefaultNameColor = Color3.new(1, 1, 1)
module.ChatBarBackGroundColor = Color3.new(0, 0, 0)
module.ChatBarBoxColor = Color3.new(1, 1, 1)
module.ChatBarTextColor = Color3.new(0, 0, 0)
module.ChannelsTabUnselectedColor = Color3.new(0, 0, 0)
module.ChannelsTabSelectedColor = Color3.new(30/255, 30/255, 30/255)
module.DefaultChannelNameColor = Color3.fromRGB(35, 76, 142)
module.WhisperChannelNameColor = Color3.fromRGB(102, 14, 102)
module.ErrorMessageTextColor = Color3.fromRGB(245, 50, 50)
---[[ Window Settings ]]
module.MinimumWindowSize = UDim2.new(0.3, 0, 0.25, 0)
module.MaximumWindowSize = UDim2.new(1, 0, 1, 0) -- if you change this to be greater than full screen size, weird things start to happen with size/position bounds checking.
module.DefaultWindowPosition = UDim2.new(0, 0, 0, 0)
local extraOffset = (7 * 2) + (5 * 2) -- Extra chatbar vertical offset
module.DefaultWindowSizePhone = UDim2.new(0.5, 0, 0.5, extraOffset)
module.DefaultWindowSizeTablet = UDim2.new(0.4, 0, 0.3, extraOffset)
module.DefaultWindowSizeDesktop = UDim2.new(0.3, 0, 0.25, extraOffset)
---[[ Fade Out and In Settings ]]
module.ChatWindowBackgroundFadeOutTime = 0.5 --Chat background will fade out after this many seconds.
module.ChatWindowTextFadeOutTime = 30 --Chat text will fade out after this many seconds.
module.ChatDefaultFadeDuration = 0.8
module.ChatShouldFadeInFromNewInformation = false
module.ChatAnimationFPS = 20.0
---[[ Channel Settings ]]
module.GeneralChannelName = "All" -- You can set to nil to turn off echoing to a general channel.
module.EchoMessagesInGeneralChannel = true -- Should messages to channels other than general be echoed into the general channel.
-- Setting this to false should be used with ShowChannelsBar
module.ChannelsBarFullTabSize = 4 -- number of tabs in bar before it starts to scroll
module.MaxChannelNameLength = 12
--// Although this feature is pretty much ready, it needs some UI design still.
module.RightClickToLeaveChannelEnabled = false
module.MessageHistoryLengthPerChannel = 50
-- Show the help text for joining and leaving channels. This is not useful unless custom channels have been added.
-- So it is turned off by default.
module.ShowJoinAndLeaveHelpText = false
---[[ Message Settings ]]
module.MaximumMessageLength = 200
module.DisallowedWhiteSpace = {"\n", "\r", "\t", "\v", "\f"}
module.ClickOnPlayerNameToWhisper = true
module.ClickOnChannelNameToSetMainChannel = true
module.BubbleChatMessageTypes = {ChatConstants.MessageTypeDefault, ChatConstants.MessageTypeWhisper}
---[[ Misc Settings ]]
module.WhisperCommandAutoCompletePlayerNames = true
local ChangedEvent = Instance.new("BindableEvent")
local proxyTable = setmetatable({},
{
__index = function(tbl, index)
return module[index]
end,
__newindex = function(tbl, index, value)
module[index] = value
ChangedEvent:Fire(index, value)
end,
})
rawset(proxyTable, "SettingsChanged", ChangedEvent.Event)
return proxyTable
================================================
FILE: src/Chat/ClientChatModules/CommandModules/ClearMessages.lua
================================================
-- // FileName: ClearMessages.lua
-- // Written by: TheGamer101
-- // Description: Command to clear the message log of the current channel.
local util = require(script.Parent:WaitForChild("Util"))
function ProcessMessage(message, ChatWindow, ChatSettings)
if string.sub(message, 1, 4):lower() == "/cls" or string.sub(message, 1, 6):lower() == "/clear" then
local currentChannel = ChatWindow:GetCurrentChannel()
if (currentChannel) then
currentChannel:ClearMessageLog()
end
return true
end
return false
end
return {
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.COMPLETED_MESSAGE_PROCESSOR,
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
}
================================================
FILE: src/Chat/ClientChatModules/CommandModules/Commands.lua
================================================
-- Custom Vesteria chat commands
-- by berezaa
local util = require(script.Parent:WaitForChild("Util"))
local customState = {}
customState.__index = customState
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local configuration = modules.load("configuration")
function matches(newText, pattern)
newText = string.lower(newText)
pattern = string.lower(pattern)
if string.sub(newText, 1, pattern:len()) == pattern then
return true
end
end
function isCommand(newText)
if matches(newText, "/invite ") or matches(newText, "/i ") then
return "invite"
elseif matches(newText, "/duel ") or matches(newText, "/d ") then
return "duel"
elseif matches(newText, "/trade ") or matches(newText, "/t ") then
return "trade"
elseif matches(newText, "/e ") then
return "emote", true
elseif matches(newText, "/expel ") then
return "expel"
end
end
local commandText = {
invite = "Invite player to party:";
duel = "Challenge player to duel:";
trade = "Request trade with player:";
emote = "Perform emote:";
expel = "Expel player from Guild Hall:",
}
function customState:TrimWhiteSpace(text)
local newText = string.gsub(text, "%s+", "")
local wasWhitespaceTrimmed = text[#text] == " "
return newText, wasWhitespaceTrimmed
end
function customState:AutoComplete(enteredText)
enteredText = enteredText:lower()
local trimmedText = enteredText
-- local trimmedText = self:TrimWhisperCommand(enteredText)
if trimmedText then
local possiblePlayerName, whitespaceTrimmed = self:TrimWhiteSpace(trimmedText)
local possibleMatches = {}
local players = game.Players:GetPlayers()
for i = 1, #players do
if players[i] ~= game.Players.LocalPlayer then
local lowerPlayerName = players[i].Name:lower()
if string.sub(lowerPlayerName, 1, string.len(possiblePlayerName)) == possiblePlayerName then
possibleMatches[players[i]] = players[i].Name:lower()
end
end
end
local matchCount = 0
local lastMatch = nil
local lastMatchName = nil
for player, playerName in pairs(possibleMatches) do
matchCount = matchCount + 1
lastMatch = player
lastMatchName = playerName
if playerName == possiblePlayerName and whitespaceTrimmed then
return player
end
end
if matchCount == 1 then
return lastMatch
end
end
return nil
end
function customState:enterFocus(command)
command = command or isCommand(self:GetMessage())
if command then
self.currentCommand = command
self.MessageModeButton.Size = UDim2.new(0, 1000, 1, 0)
self.MessageModeButton.Text = commandText[command] or command .. ":"
self.MessageModeButton.TextColor3 = Color3.fromRGB(0, 12, 255)
local xSize = self.MessageModeButton.TextBounds.X
self.MessageModeButton.Size = UDim2.new(0, xSize, 1, 0)
self.TextBox.Size = UDim2.new(1, -xSize, 1, 0)
self.TextBox.Position = UDim2.new(0, xSize, 0, 0)
self.OriginalPartyText = self.TextBox.Text
self.TextBox.Text = " "
end
end
function customState:TextUpdated()
local newText = self.TextBox.Text
if not self.currentCommand then
local command = isCommand(newText)
if command then
self:enterFocus(command)
end
else
if newText == "" then
self.MessageModeButton.Text = ""
self.MessageModeButton.Size = UDim2.new(0, 0, 0, 0)
self.TextBox.Size = UDim2.new(1, 0, 1, 0)
self.TextBox.Position = UDim2.new(0, 0, 0, 0)
self.TextBox.Text = ""
---Implement this when setting cursor positon is a thing.
---self.TextBox.Text = self.OriginalPartyText
self.currentCommand = nil
---Temporary until setting cursor position...
self.ChatBar:ResetCustomState()
self.ChatBar:CaptureFocus()
elseif self.doAutoComplete then
-- try to autocomplete
local targetPlayer = self:AutoComplete(newText)
if targetPlayer then
self.TextBox.Text = " "..targetPlayer.Name
end
self.ChatBar:CaptureFocus()
self.doAutoComplete = false
end
end
end
function customState:GetMessage()
local currentCommand = self.currentCommand
if currentCommand then
return "/"..currentCommand..self.TextBox.Text
end
return self.TextBox.Text
end
local starterGui = game:GetService("StarterGui")
local MSG_COLOR = Color3.new(0.7,0.7,0.7)
local ERR_COLOR = Color3.new(1,0.6,0.6)
--welcome msg
if game.gameId ==712031239 then
starterGui:SetCore("ChatMakeSystemMessage", {
Text = "Welcome to Free to Play Vesteria! This game is still in early development and may be reset. Say '/help' for a list of commands.";
Color = Color3.fromRGB(0, 255, 149)
})
else
starterGui:SetCore("ChatMakeSystemMessage", {Text = "Welcome to Vesteria! Say '/help' for a list of commands."; Color = Color3.fromRGB(0, 255, 149)})
end
function customState:ProcessCompletedMessage()
local message = self:GetMessage()
local command = self.currentCommand
if command then
if command == "emote" then
local targetEmote = string.gsub(self:GetMessage(), "^[^%s]+ ", "")
local success, reason = false, "invalid emote"
success, reason = network:invoke("playerRequest_performEmote", targetEmote)
if not success then
starterGui:SetCore("ChatMakeSystemMessage", {Text = reason; Color = ERR_COLOR})
end
else
-- commands that involve players
local targetPlayerName = string.gsub(self:GetMessage(), "^[^%s]+ ", "")
local targetPlayer = game.Players:FindFirstChild(targetPlayerName)
-- idk in case spaces in usernames ever become a thing
if targetPlayer == nil then
targetPlayer = game.Players:FindFirstChild(string.gsub(targetPlayerName, " ", ""))
end
if targetPlayer then
local success, reason = false, "invalid command"
if command == "invite" then
success, reason = network:invokeServer("playerRequest_invitePlayerToMyParty", targetPlayer)
if success then
starterGui:SetCore("ChatMakeSystemMessage", {Text = "Sent a party invite to " .. targetPlayerName; Color = MSG_COLOR})
else
starterGui:SetCore("ChatMakeSystemMessage", {Text = reason; Color = ERR_COLOR})
end
elseif command == "duel" then
success, reason = network:invokeServer("playerRequest_requestChallenge", targetPlayer)
if success then
starterGui:SetCore("ChatMakeSystemMessage", {Text = "Sent a duel challenge to " .. targetPlayerName; Color = MSG_COLOR})
else
starterGui:SetCore("ChatMakeSystemMessage", {Text = reason; Color = ERR_COLOR})
end
elseif command == "expel" then
success, reason = network:invokeServer("playerRequest_expelPlayer", targetPlayer)
if success then
starterGui:SetCore("ChatMakeSystemMessage", {Text = "Expelling "..targetPlayerName.."...", Color = MSG_COLOR})
end
elseif command == "trade" then
success, reason = false, "Trading is temporarily disabled."
if configuration.getConfigurationValue("isTradingEnabled") then
success, reason = network:invokeServer("playerRequest_requestTrade", targetPlayer)
end
if success then
starterGui:SetCore("ChatMakeSystemMessage", {Text = "Sent a trade request to " .. targetPlayerName; Color = MSG_COLOR})
end
end
if not success then
starterGui:SetCore("ChatMakeSystemMessage", {Text = reason; Color = ERR_COLOR})
end
else
starterGui:SetCore("ChatMakeSystemMessage", {Text = "Invalid player"; Color = ERR_COLOR})
end
end
return true
end
-- help command
if (message:lower() == "/?" or message:lower() == "/help") then
starterGui:SetCore("ChatMakeSystemMessage", {Text = "Vesteria chat commands:"; Color = MSG_COLOR})
starterGui:SetCore("ChatMakeSystemMessage", {Text = " /me :: roleplaying command for doing actions."; Color = MSG_COLOR})
starterGui:SetCore("ChatMakeSystemMessage", {Text = " /whisper (/w) :: whisper a private message to a player."; Color = MSG_COLOR})
starterGui:SetCore("ChatMakeSystemMessage", {Text = " /mute :: stop seeing chats from a player."; Color = MSG_COLOR})
starterGui:SetCore("ChatMakeSystemMessage", {Text = " /unmute :: unmute a muted player."; Color = MSG_COLOR})
starterGui:SetCore("ChatMakeSystemMessage", {Text = " /party (/p) :: send a message to party members."; Color = MSG_COLOR})
starterGui:SetCore("ChatMakeSystemMessage", {Text = " /invite (/i) :: invite a player to your party."; Color = MSG_COLOR})
starterGui:SetCore("ChatMakeSystemMessage", {Text = " /duel (/d) :: challenge a player to a duel."; Color = MSG_COLOR})
starterGui:SetCore("ChatMakeSystemMessage", {Text = " /trade (/t) :: request a trade with a player."; Color = MSG_COLOR})
starterGui:SetCore("ChatMakeSystemMessage", {Text = " /e (/t) :: perform emote."; Color = MSG_COLOR})
return true
end
return false
end
function customState:Destroy()
print("destroy custom state")
self.Destroyed = true
end
function customState.new(ChatWindow, ChatBar, ChatSettings, overrideAutoComplete)
local obj = {}
setmetatable(obj, customState)
obj.Destroyed = false
obj.ChatWindow = ChatWindow
obj.ChatBar = ChatBar
obj.ChatSettings = ChatSettings
obj.TextBox = ChatBar:GetTextBox()
obj.MessageModeButton = ChatBar:GetMessageModeTextButton()
obj.MessageModeLabel = ChatBar:GetMessageModeTextLabel()
if overrideAutoComplete then
obj.doAutoComplete = false
--print("overrided autocomplete")
else
obj.doAutoComplete = true
end
obj.MessageModeConnection = obj.MessageModeButton.MouseButton1Click:connect(function()
local chatBarText = obj.TextBox.Text
if string.sub(chatBarText, 1, 1) == " " then
chatBarText = string.sub(chatBarText, 2)
end
obj.ChatBar:ResetCustomState()
obj.ChatBar:SetTextBoxText(chatBarText)
obj.ChatBar:CaptureFocus()
end)
obj:enterFocus()
return obj
end
local function ProcessMessage(message, ChatWindow, ChatBar, ChatSettings)
-- overrideAutoComplete is nil or true
local command, overrideAutoComplete = isCommand(message)
if command or message:lower() == "/?" or message:lower() == "/help" then
return customState.new(ChatWindow, ChatBar, ChatSettings, overrideAutoComplete)
end
end
return {
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.IN_PROGRESS_MESSAGE_PROCESSOR,
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
}
================================================
FILE: src/Chat/ClientChatModules/CommandModules/DeveloperConsole.lua
================================================
-- // FileName: DeveloperConsole.lua
-- // Written by: TheGamer101
-- // Description: Command to open or close the developer console.
local StarterGui = game:GetService("StarterGui")
local util = require(script.Parent:WaitForChild("Util"))
function ProcessMessage(message, ChatWindow, ChatSettings)
if string.sub(message, 1, 8):lower() == "/console" or string.sub(message, 1, 11):lower() == "/newconsole" then
local success, developerConsoleVisible = pcall(function() return StarterGui:GetCore("DevConsoleVisible") end)
if success then
local success, err = pcall(function() StarterGui:SetCore("DevConsoleVisible", not developerConsoleVisible) end)
if not success and err then
print("Error making developer console visible: " ..err)
end
end
return true
elseif string.sub(message, 1, 11):lower() == "/oldconsole" then
local success, developerConsoleVisible = pcall(function() return StarterGui:GetCore("DeveloperConsoleVisible") end)
if success then
local success, err = pcall(function() StarterGui:SetCore("DeveloperConsoleVisible", not developerConsoleVisible) end)
if not success and err then
print("Error making developer console visible: " ..err)
end
end
return true
end
return false
end
return {
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.COMPLETED_MESSAGE_PROCESSOR,
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
}
================================================
FILE: src/Chat/ClientChatModules/CommandModules/GetVersion.lua
================================================
-- // FileName: GetVersion.lua
-- // Written by: spotco
-- // Description: Command to print the chat version.
local util = require(script.Parent:WaitForChild("Util"))
local ChatConstants = require(script.Parent.Parent:WaitForChild("ChatConstants"))
local function ProcessMessage(message, ChatWindow, _)
if string.sub(message, 1, 8):lower() == "/version" or string.sub(message, 1, 9):lower() == "/version " then
util:SendSystemMessageToSelf(
string.format("This game is running chat version [%d.%d.%s].",
ChatConstants.MajorVersion,
ChatConstants.MinorVersion,
ChatConstants.BuildVersion),
ChatWindow:GetCurrentChannel(),
{})
return true
end
return false
end
return {
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.COMPLETED_MESSAGE_PROCESSOR,
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
}
================================================
FILE: src/Chat/ClientChatModules/CommandModules/Guild.lua
================================================
-- // FileName: Guild.lua
-- // Written by: Partixel/TheGamer101
-- // Description: Guild chat bar manipulation.
local PlayersService = game:GetService("Players")
local GUILD_COMMANDS = {"/guild ", "/g "}
function IsGuildCommand(message)
for i = 1, #GUILD_COMMANDS do
local guildCommand = GUILD_COMMANDS[i]
if string.sub(message, 1, guildCommand:len()):lower() == guildCommand then
return true
end
end
return false
end
local guildStateMethods = {}
guildStateMethods.__index = guildStateMethods
local util = require(script.Parent:WaitForChild("Util"))
local GuildCustomState = {}
function guildStateMethods:EnterGuildChat()
self.GuildChatEntered = true
self.MessageModeButton.Size = UDim2.new(0, 1000, 1, 0)
self.MessageModeButton.Text = "[Guild]"
self.MessageModeButton.TextColor3 = self:GetGuildChatColor()
local xSize = self.MessageModeButton.TextBounds.X
self.MessageModeButton.Size = UDim2.new(0, xSize, 1, 0)
self.TextBox.Size = UDim2.new(1, -xSize, 1, 0)
self.TextBox.Position = UDim2.new(0, xSize, 0, 0)
self.OriginalGuildText = self.TextBox.Text
self.TextBox.Text = " "
end
function guildStateMethods:TextUpdated()
local newText = self.TextBox.Text
if not self.GuildChatEntered then
if IsGuildCommand(newText) then
self:EnterGuildChat()
end
else
if newText == "" then
self.MessageModeButton.Text = ""
self.MessageModeButton.Size = UDim2.new(0, 0, 0, 0)
self.TextBox.Size = UDim2.new(1, 0, 1, 0)
self.TextBox.Position = UDim2.new(0, 0, 0, 0)
self.TextBox.Text = ""
---Implement this when setting cursor positon is a thing.
---self.TextBox.Text = self.OriginalGuildText
self.GuildChatEntered = false
---Temporary until setting cursor position...
self.ChatBar:ResetCustomState()
self.ChatBar:CaptureFocus()
end
end
end
function guildStateMethods:GetMessage()
if self.GuildChatEntered then
return "/g " ..self.TextBox.Text
end
return self.TextBox.Text
end
function guildStateMethods:ProcessCompletedMessage()
return false
end
function guildStateMethods:Destroy()
self.MessageModeConnection:disconnect()
self.Destroyed = true
end
function guildStateMethods:GetGuildChatColor()
return Color3.fromRGB(145, 71, 255)
end
function GuildCustomState.new(ChatWindow, ChatBar, ChatSettings)
local obj = setmetatable({}, guildStateMethods)
obj.Destroyed = false
obj.ChatWindow = ChatWindow
obj.ChatBar = ChatBar
obj.ChatSettings = ChatSettings
obj.TextBox = ChatBar:GetTextBox()
obj.MessageModeButton = ChatBar:GetMessageModeTextButton()
obj.OriginalGuildText = ""
obj.GuildChatEntered = false
obj.MessageModeConnection = obj.MessageModeButton.MouseButton1Click:connect(function()
local chatBarText = obj.TextBox.Text
if string.sub(chatBarText, 1, 1) == " " then
chatBarText = string.sub(chatBarText, 2)
end
obj.ChatBar:ResetCustomState()
obj.ChatBar:SetTextBoxText(chatBarText)
obj.ChatBar:CaptureFocus()
end)
obj:EnterGuildChat()
return obj
end
function ProcessMessage(message, ChatWindow, ChatBar, ChatSettings)
if ChatBar.TargetChannel == "Guild" then
return
end
if IsGuildCommand(message) then
return GuildCustomState.new(ChatWindow, ChatBar, ChatSettings)
end
return nil
end
return {
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.IN_PROGRESS_MESSAGE_PROCESSOR,
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
}
================================================
FILE: src/Chat/ClientChatModules/CommandModules/Party.lua
================================================
-- // FileName: Party.lua
-- // Written by: Partixel/TheGamer101
-- // Description: Party chat bar manipulation.
local PlayersService = game:GetService("Players")
local PARTY_COMMANDS = {"/party ", "/p "}
function IsPartyCommand(message)
for i = 1, #PARTY_COMMANDS do
local partyCommand = PARTY_COMMANDS[i]
if string.sub(message, 1, partyCommand:len()):lower() == partyCommand then
return true
end
end
return false
end
local partyStateMethods = {}
partyStateMethods.__index = partyStateMethods
local util = require(script.Parent:WaitForChild("Util"))
local PartyCustomState = {}
function partyStateMethods:EnterPartyChat()
self.PartyChatEntered = true
self.MessageModeButton.Size = UDim2.new(0, 1000, 1, 0)
self.MessageModeButton.Text = "[Party]"
self.MessageModeButton.TextColor3 = self:GetPartyChatColor()
local xSize = self.MessageModeButton.TextBounds.X
self.MessageModeButton.Size = UDim2.new(0, xSize, 1, 0)
self.TextBox.Size = UDim2.new(1, -xSize, 1, 0)
self.TextBox.Position = UDim2.new(0, xSize, 0, 0)
self.OriginalPartyText = self.TextBox.Text
self.TextBox.Text = " "
end
function partyStateMethods:TextUpdated()
local newText = self.TextBox.Text
if not self.PartyChatEntered then
if IsPartyCommand(newText) then
self:EnterPartyChat()
end
else
if newText == "" then
self.MessageModeButton.Text = ""
self.MessageModeButton.Size = UDim2.new(0, 0, 0, 0)
self.TextBox.Size = UDim2.new(1, 0, 1, 0)
self.TextBox.Position = UDim2.new(0, 0, 0, 0)
self.TextBox.Text = ""
---Implement this when setting cursor positon is a thing.
---self.TextBox.Text = self.OriginalPartyText
self.PartyChatEntered = false
---Temporary until setting cursor position...
self.ChatBar:ResetCustomState()
self.ChatBar:CaptureFocus()
end
end
end
function partyStateMethods:GetMessage()
if self.PartyChatEntered then
return "/p " ..self.TextBox.Text
end
return self.TextBox.Text
end
function partyStateMethods:ProcessCompletedMessage()
return false
end
function partyStateMethods:Destroy()
self.MessageModeConnection:disconnect()
self.Destroyed = true
end
function partyStateMethods:GetPartyChatColor()
return Color3.fromRGB(0, 240, 244)
end
function PartyCustomState.new(ChatWindow, ChatBar, ChatSettings)
local obj = setmetatable({}, partyStateMethods)
obj.Destroyed = false
obj.ChatWindow = ChatWindow
obj.ChatBar = ChatBar
obj.ChatSettings = ChatSettings
obj.TextBox = ChatBar:GetTextBox()
obj.MessageModeButton = ChatBar:GetMessageModeTextButton()
obj.OriginalPartyText = ""
obj.PartyChatEntered = false
obj.MessageModeConnection = obj.MessageModeButton.MouseButton1Click:connect(function()
local chatBarText = obj.TextBox.Text
if string.sub(chatBarText, 1, 1) == " " then
chatBarText = string.sub(chatBarText, 2)
end
obj.ChatBar:ResetCustomState()
obj.ChatBar:SetTextBoxText(chatBarText)
obj.ChatBar:CaptureFocus()
end)
obj:EnterPartyChat()
return obj
end
function ProcessMessage(message, ChatWindow, ChatBar, ChatSettings)
if ChatBar.TargetChannel == "Party" then
return
end
if IsPartyCommand(message) then
return PartyCustomState.new(ChatWindow, ChatBar, ChatSettings)
end
return nil
end
return {
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.IN_PROGRESS_MESSAGE_PROCESSOR,
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
}
================================================
FILE: src/Chat/ClientChatModules/CommandModules/SwallowGuestChat.lua
================================================
-- // FileName: SwallowGuestChat.lua
-- // Written by: TheGamer101
-- // Description: Stop Guests from chatting and give them a message telling them to sign up.
-- // Guests are generally not allowed to chat, so please do not remove this.
local util = require(script.Parent:WaitForChild("Util"))
local RunService = game:GetService("RunService")
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
function ProcessMessage(message, ChatWindow, ChatSettings)
local LocalPlayer = game:GetService("Players").LocalPlayer
if LocalPlayer and LocalPlayer.UserId < 0 and not RunService:IsStudio() then
local channelObj = ChatWindow:GetCurrentChannel()
if channelObj then
util:SendSystemMessageToSelf(
ChatLocalization:Get("GameChat_SwallowGuestChat_Message","Create a free account to get access to chat permissions!"),
channelObj,
{}
)
end
return true
end
return false
end
return {
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.COMPLETED_MESSAGE_PROCESSOR,
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
}
================================================
FILE: src/Chat/ClientChatModules/CommandModules/SwitchChannel.lua
================================================
-- // FileName: ClearMessages.lua
-- // Written by: TheGamer101
-- // Description: Command to switch channel.
local util = require(script.Parent:WaitForChild("Util"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = { Get = function(key,default) return default end } end
function ProcessMessage(message, ChatWindow, ChatSettings)
if string.sub(message, 1, 3):lower() ~= "/c " then
return false
end
local channelName = string.sub(message, 4)
local targetChannel = ChatWindow:GetChannel(channelName)
if targetChannel then
ChatWindow:SwitchCurrentChannel(channelName)
if not ChatSettings.ShowChannelsBar then
local currentChannel = ChatWindow:GetCurrentChannel()
if currentChannel then
util:SendSystemMessageToSelf(
string.gsub(ChatLocalization:Get(
"GameChat_SwitchChannel_NowInChannel",
string.format("You are now chatting in channel: '%s'", channelName)
),"{RBX_NAME}",channelName),
targetChannel, {}
)
end
end
else
local currentChannel = ChatWindow:GetCurrentChannel()
if currentChannel then
util:SendSystemMessageToSelf(
string.gsub(ChatLocalization:Get(
"GameChat_SwitchChannel_NotInChannel",
string.format("You are not in channel: '%s'", channelName)
),"{RBX_NAME}",channelName),
currentChannel, {ChatColor = Color3.fromRGB(245, 50, 50)}
)
end
end
return true
end
return {
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.COMPLETED_MESSAGE_PROCESSOR,
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
}
================================================
FILE: src/Chat/ClientChatModules/CommandModules/Team.lua
================================================
-- // FileName: Team.lua
-- // Written by: Partixel/TheGamer101
-- // Description: Team chat bar manipulation.
local PlayersService = game:GetService("Players")
local TEAM_COMMANDS = {"/team ", "/t "}
function IsTeamCommand(message)
for i = 1, #TEAM_COMMANDS do
local teamCommand = TEAM_COMMANDS[i]
if string.sub(message, 1, teamCommand:len()):lower() == teamCommand then
return true
end
end
return false
end
local teamStateMethods = {}
teamStateMethods.__index = teamStateMethods
local util = require(script.Parent:WaitForChild("Util"))
local TeamCustomState = {}
function teamStateMethods:EnterTeamChat()
self.TeamChatEntered = true
self.MessageModeButton.Size = UDim2.new(0, 1000, 1, 0)
self.MessageModeButton.Text = "[Team]"
self.MessageModeButton.TextColor3 = self:GetTeamChatColor()
local xSize = self.MessageModeButton.TextBounds.X
self.MessageModeButton.Size = UDim2.new(0, xSize, 1, 0)
self.TextBox.Size = UDim2.new(1, -xSize, 1, 0)
self.TextBox.Position = UDim2.new(0, xSize, 0, 0)
self.OriginalTeamText = self.TextBox.Text
self.TextBox.Text = " "
end
function teamStateMethods:TextUpdated()
local newText = self.TextBox.Text
if not self.TeamChatEntered then
if IsTeamCommand(newText) then
self:EnterTeamChat()
end
else
if newText == "" then
self.MessageModeButton.Text = ""
self.MessageModeButton.Size = UDim2.new(0, 0, 0, 0)
self.TextBox.Size = UDim2.new(1, 0, 1, 0)
self.TextBox.Position = UDim2.new(0, 0, 0, 0)
self.TextBox.Text = ""
---Implement this when setting cursor positon is a thing.
---self.TextBox.Text = self.OriginalTeamText
self.TeamChatEntered = false
---Temporary until setting cursor position...
self.ChatBar:ResetCustomState()
self.ChatBar:CaptureFocus()
end
end
end
function teamStateMethods:GetMessage()
if self.TeamChatEntered then
return "/t " ..self.TextBox.Text
end
return self.TextBox.Text
end
function teamStateMethods:ProcessCompletedMessage()
return false
end
function teamStateMethods:Destroy()
self.MessageModeConnection:disconnect()
self.Destroyed = true
end
function teamStateMethods:GetTeamChatColor()
local LocalPlayer = PlayersService.LocalPlayer
if LocalPlayer.Team then
return LocalPlayer.Team.TeamColor.Color
end
if self.ChatSettings.DefaultChannelNameColor then
return self.ChatSettings.DefaultChannelNameColor
end
return Color3.fromRGB(35, 76, 142)
end
function TeamCustomState.new(ChatWindow, ChatBar, ChatSettings)
local obj = setmetatable({}, teamStateMethods)
obj.Destroyed = false
obj.ChatWindow = ChatWindow
obj.ChatBar = ChatBar
obj.ChatSettings = ChatSettings
obj.TextBox = ChatBar:GetTextBox()
obj.MessageModeButton = ChatBar:GetMessageModeTextButton()
obj.OriginalTeamText = ""
obj.TeamChatEntered = false
obj.MessageModeConnection = obj.MessageModeButton.MouseButton1Click:connect(function()
local chatBarText = obj.TextBox.Text
if string.sub(chatBarText, 1, 1) == " " then
chatBarText = string.sub(chatBarText, 2)
end
obj.ChatBar:ResetCustomState()
obj.ChatBar:SetTextBoxText(chatBarText)
obj.ChatBar:CaptureFocus()
end)
obj:EnterTeamChat()
return obj
end
function ProcessMessage(message, ChatWindow, ChatBar, ChatSettings)
if ChatBar.TargetChannel == "Team" then
return
end
if IsTeamCommand(message) then
return TeamCustomState.new(ChatWindow, ChatBar, ChatSettings)
end
return nil
end
return {
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.IN_PROGRESS_MESSAGE_PROCESSOR,
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
}
================================================
FILE: src/Chat/ClientChatModules/CommandModules/Toggle.lua
================================================
-- // FileName: Toggle.lua
-- // Written by: Nicholas_Foreman
-- // Description: Allows for toggling chat tags/chat color.
local util = require(script.Parent:WaitForChild("Util"))
local ChatConstants = require(script.Parent.Parent:WaitForChild("ChatConstants"))
local event = game:GetService("ReplicatedStorage"):WaitForChild("DefaultChatSystemChatEvents"):WaitForChild("Toggle");
local function ProcessMessage(message, ChatWindow, _)
if string.sub(message, 1, 7):lower() == "/toggle" then
if message:lower() == "/toggle" then
util:SendSystemMessageToSelf("Usage : /toggle : toggles chat tags or chat color.", ChatWindow:GetCurrentChannel(), {})
return true
elseif message:lower() == "/toggle tags" then
event:FireServer("Tags")
util:SendSystemMessageToSelf("Successfully toggled chat tags.", ChatWindow:GetCurrentChannel(), {})
return true
elseif message:lower() == "/toggle color" then
event:FireServer("Color")
util:SendSystemMessageToSelf("Successfully toggled chat color.", ChatWindow:GetCurrentChannel(), {})
return true
else
util:SendSystemMessageToSelf("Usage : /toggle : toggles chat tags or chat color.", ChatWindow:GetCurrentChannel(), {})
return true
end
end
return false
end
return {
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.COMPLETED_MESSAGE_PROCESSOR,
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
}
================================================
FILE: src/Chat/ClientChatModules/CommandModules/Util.lua
================================================
-- // FileName: Util.lua
-- // Written by: TheGamer101
-- // Description: Module for shared code between CommandModules.
--[[
Creating a command module:
1) Create a new module inside the CommandModules folder.
2) Create a function that takes a message, the ChatWindow object and the ChatSettings and returns
a bool command processed.
3) Return this function from the module.
--]]
local clientChatModules = script.Parent.Parent
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
local COMMAND_MODULES_VERSION = 1
local KEY_COMMAND_PROCESSOR_TYPE = "ProcessorType"
local KEY_PROCESSOR_FUNCTION = "ProcessorFunction"
---Command types.
---Process a command as it is being typed. This allows for manipulation of the chat bar.
local IN_PROGRESS_MESSAGE_PROCESSOR = 0
---Simply process a completed message.
local COMPLETED_MESSAGE_PROCESSOR = 1
local module = {}
local methods = {}
methods.__index = methods
function methods:SendSystemMessageToSelf(message, channelObj, extraData)
local messageData =
{
ID = -1,
FromSpeaker = nil,
SpeakerUserId = 0,
OriginalChannel = channelObj.Name,
IsFiltered = true,
MessageLength = string.len(message),
Message = message,
MessageType = ChatConstants.MessageTypeSystem,
Time = os.time(),
ExtraData = extraData,
}
channelObj:AddMessageToChannel(messageData)
end
function module.new()
local obj = setmetatable({}, methods)
obj.COMMAND_MODULES_VERSION = COMMAND_MODULES_VERSION
obj.KEY_COMMAND_PROCESSOR_TYPE = KEY_COMMAND_PROCESSOR_TYPE
obj.KEY_PROCESSOR_FUNCTION = KEY_PROCESSOR_FUNCTION
obj.IN_PROGRESS_MESSAGE_PROCESSOR = IN_PROGRESS_MESSAGE_PROCESSOR
obj.COMPLETED_MESSAGE_PROCESSOR = COMPLETED_MESSAGE_PROCESSOR
return obj
end
return module.new()
================================================
FILE: src/Chat/ClientChatModules/CommandModules/Whisper.lua
================================================
-- // FileName: Whisper.lua
-- // Written by: TheGamer101
-- // Description: Whisper chat bar manipulation.
local util = require(script.Parent:WaitForChild("Util"))
local ChatSettings = require(script.Parent.Parent:WaitForChild("ChatSettings"))
local PlayersService = game:GetService("Players")
local LocalPlayer = PlayersService.LocalPlayer
while LocalPlayer == nil do
PlayersService.ChildAdded:wait()
LocalPlayer = PlayersService.LocalPlayer
end
local whisperStateMethods = {}
whisperStateMethods.__index = whisperStateMethods
local WhisperCustomState = {}
function whisperStateMethods:TrimWhisperCommand(text)
if string.sub(text, 1, 3):lower() == "/w " then
return string.sub(text, 4)
elseif string.sub(text, 1, 9):lower() == "/whisper " then
return string.sub(text, 10)
end
return nil
end
function whisperStateMethods:TrimWhiteSpace(text)
local newText = string.gsub(text, "%s+", "")
local wasWhitespaceTrimmed = text[#text] == " "
return newText, wasWhitespaceTrimmed
end
function whisperStateMethods:ShouldAutoCompleteNames()
if ChatSettings.WhisperCommandAutoCompletePlayerNames ~= nil then
return ChatSettings.WhisperCommandAutoCompletePlayerNames
end
return true
end
function whisperStateMethods:GetWhisperingPlayer(enteredText)
enteredText = enteredText:lower()
local trimmedText = self:TrimWhisperCommand(enteredText)
if trimmedText then
local possiblePlayerName, whitespaceTrimmed = self:TrimWhiteSpace(trimmedText)
local possibleMatches = {}
local players = PlayersService:GetPlayers()
for i = 1, #players do
if players[i] ~= LocalPlayer then
local lowerPlayerName = players[i].Name:lower()
if string.sub(lowerPlayerName, 1, string.len(possiblePlayerName)) == possiblePlayerName then
possibleMatches[players[i]] = players[i].Name:lower()
end
end
end
local matchCount = 0
local lastMatch = nil
local lastMatchName = nil
for player, playerName in pairs(possibleMatches) do
matchCount = matchCount + 1
lastMatch = player
lastMatchName = playerName
if playerName == possiblePlayerName and whitespaceTrimmed then
return player
end
end
if matchCount == 1 then
if self:ShouldAutoCompleteNames() then
return lastMatch
elseif lastMatchName == possiblePlayerName then
return lastMatch
end
end
end
return nil
end
function whisperStateMethods:GetWhisperChanneNameColor()
if self.ChatSettings.WhisperChannelNameColor then
return self.ChatSettings.WhisperChannelNameColor
end
return Color3.fromRGB(102, 14, 102)
end
function whisperStateMethods:TextUpdated()
local newText = self.TextBox.Text
if not self.PlayerNameEntered then
local player = self:GetWhisperingPlayer(newText)
if player then
self.PlayerNameEntered = true
self.PlayerName = player.Name
self.MessageModeButton.Size = UDim2.new(0, 1000, 1, 0)
self.MessageModeButton.Text = string.format("[To %s]", player.Name)
self.MessageModeButton.TextColor3 = self:GetWhisperChanneNameColor()
local xSize = self.MessageModeButton.TextBounds.X
self.MessageModeButton.Size = UDim2.new(0, xSize, 1, 0)
self.TextBox.Size = UDim2.new(1, -xSize, 1, 0)
self.TextBox.Position = UDim2.new(0, xSize, 0, 0)
self.TextBox.Text = " "
end
else
if newText == "" then
self.MessageModeButton.Text = ""
self.MessageModeButton.Size = UDim2.new(0, 0, 0, 0)
self.TextBox.Size = UDim2.new(1, 0, 1, 0)
self.TextBox.Position = UDim2.new(0, 0, 0, 0)
self.TextBox.Text = ""
---Implement this when setting cursor positon is a thing.
---self.TextBox.Text = self.OriginalText .. " " .. self.PlayerName
self.PlayerNameEntered = false
---Temporary until setting cursor position...
self.ChatBar:ResetCustomState()
self.ChatBar:CaptureFocus()
end
end
end
function whisperStateMethods:GetMessage()
if self.PlayerNameEntered then
return "/w " ..self.PlayerName.. " " ..self.TextBox.Text
end
return self.TextBox.Text
end
function whisperStateMethods:ProcessCompletedMessage()
return false
end
function whisperStateMethods:Destroy()
self.MessageModeConnection:disconnect()
self.Destroyed = true
end
function WhisperCustomState.new(ChatWindow, ChatBar, ChatSettings)
local obj = setmetatable({}, whisperStateMethods)
obj.Destroyed = false
obj.ChatWindow = ChatWindow
obj.ChatBar = ChatBar
obj.ChatSettings = ChatSettings
obj.TextBox = ChatBar:GetTextBox()
obj.MessageModeButton = ChatBar:GetMessageModeTextButton()
obj.OriginalWhisperText = ""
obj.PlayerNameEntered = false
obj.MessageModeConnection = obj.MessageModeButton.MouseButton1Click:connect(function()
local chatBarText = obj.TextBox.Text
if string.sub(chatBarText, 1, 1) == " " then
chatBarText = string.sub(chatBarText, 2)
end
obj.ChatBar:ResetCustomState()
obj.ChatBar:SetTextBoxText(chatBarText)
obj.ChatBar:CaptureFocus()
end)
obj:TextUpdated()
return obj
end
function ProcessMessage(message, ChatWindow, ChatBar, ChatSettings)
if string.sub(message, 1, 3):lower() == "/w " or string.sub(message, 1, 9):lower() == "/whisper " then
return WhisperCustomState.new(ChatWindow, ChatBar, ChatSettings)
end
return nil
end
return {
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.IN_PROGRESS_MESSAGE_PROCESSOR,
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
}
================================================
FILE: src/Chat/ClientChatModules/CommandModules/init.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/Chat/ClientChatModules/MessageCreatorModules/DefaultChatMessage.lua
================================================
-- // FileName: DefaultChatMessage.lua
-- // Written by: TheGamer101
-- // Description: Create a message label for a standard chat message.
local clientChatModules = script.Parent.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
local util = require(script.Parent:WaitForChild("Util"))
function CreateMessageLabel(messageData, channelName)
if channelName ~= messageData.OriginalChannel then
if ChatSettings.ShowChannelsBar then
return
end
end
local fromSpeaker = messageData.FromSpeaker
local message = messageData.Message
local extraData = messageData.ExtraData or {}
local useFont = extraData.Font or ChatSettings.DefaultFont
local useTextSize = extraData.TextSize or ChatSettings.ChatWindowTextSize
local useNameColor = extraData.NameColor or ChatSettings.DefaultNameColor
local useChatColor = extraData.ChatColor or ChatSettings.DefaultChatColor
local useChannelColor = extraData.ChannelColor or useChatColor
local tags = extraData.Tags or {}
local formatUseName = string.format("%s: ", fromSpeaker)
local speakerNameSize = util:GetStringTextBounds(formatUseName, useFont, useTextSize)
local numNeededSpaces = util:GetNumberOfSpaces(formatUseName, useFont, useTextSize)
local BaseFrame, BaseMessage = util:CreateBaseMessage("", useFont, useTextSize, useChatColor)
local NameButton = util:AddNameButtonToBaseMessage(BaseMessage, useNameColor, formatUseName, fromSpeaker)
local ChannelButton = nil
local guiObjectSpacing = UDim2.new(0, 0, 0, 0)
if channelName ~= messageData.OriginalChannel then
local formatChannelName = string.format("{%s} ", messageData.OriginalChannel)
ChannelButton = util:AddChannelButtonToBaseMessage(BaseMessage, useChannelColor, formatChannelName, messageData.OriginalChannel)
guiObjectSpacing = UDim2.new(0, ChannelButton.Size.X.Offset, 0, 0)
numNeededSpaces = numNeededSpaces + util:GetNumberOfSpaces(formatChannelName, useFont, useTextSize)
end
local tagLabels = {}
local player = game.Players:FindFirstChild(fromSpeaker)
if player and player:FindFirstChild("level") then
local tagColor = Color3.fromRGB(255, 219, 12)
local tagText = "Lvl."..player.level.Value
local formatTagText = string.format("[%s] ", tagText)
local label = util:AddTagLabelToBaseMessage(BaseMessage, tagColor, formatTagText)
label.Position = guiObjectSpacing
numNeededSpaces = numNeededSpaces + util:GetNumberOfSpaces(formatTagText, useFont, useTextSize)
guiObjectSpacing = guiObjectSpacing + UDim2.new(0, label.Size.X.Offset, 0, 0)
table.insert(tagLabels, label)
end
for i, tag in pairs(tags) do
local tagColor = tag.TagColor or Color3.fromRGB(255, 0, 255)
local tagText = tag.TagText or "???"
local formatTagText = string.format("[%s] ", tagText)
local label = util:AddTagLabelToBaseMessage(BaseMessage, tagColor, formatTagText)
label.Position = guiObjectSpacing
numNeededSpaces = numNeededSpaces + util:GetNumberOfSpaces(formatTagText, useFont, useTextSize)
guiObjectSpacing = guiObjectSpacing + UDim2.new(0, label.Size.X.Offset, 0, 0)
table.insert(tagLabels, label)
end
NameButton.Position = guiObjectSpacing
local function UpdateTextFunction(messageObject)
if messageData.IsFiltered then
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. messageObject.Message
else
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. string.rep("_", messageObject.MessageLength)
end
end
UpdateTextFunction(messageData)
local function GetHeightFunction(xSize)
return util:GetMessageHeight(BaseMessage, BaseFrame, xSize)
end
local FadeParmaters = {}
FadeParmaters[NameButton] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
FadeParmaters[BaseMessage] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
for i, tagLabel in pairs(tagLabels) do
local index = string.format("Tag%d", i)
FadeParmaters[tagLabel] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
end
if ChannelButton then
FadeParmaters[ChannelButton] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
end
local FadeInFunction, FadeOutFunction, UpdateAnimFunction = util:CreateFadeFunctions(FadeParmaters)
return {
[util.KEY_BASE_FRAME] = BaseFrame,
[util.KEY_BASE_MESSAGE] = BaseMessage,
[util.KEY_UPDATE_TEXT_FUNC] = UpdateTextFunction,
[util.KEY_GET_HEIGHT] = GetHeightFunction,
[util.KEY_FADE_IN] = FadeInFunction,
[util.KEY_FADE_OUT] = FadeOutFunction,
[util.KEY_UPDATE_ANIMATION] = UpdateAnimFunction
}
end
return {
[util.KEY_MESSAGE_TYPE] = ChatConstants.MessageTypeDefault,
[util.KEY_CREATOR_FUNCTION] = CreateMessageLabel
}
================================================
FILE: src/Chat/ClientChatModules/MessageCreatorModules/MeCommandMessage.lua
================================================
-- // FileName: MeCommandMessage.lua
-- // Written by: TheGamer101
-- // Description: Create a message label for a me command message.
local clientChatModules = script.Parent.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
local util = require(script.Parent:WaitForChild("Util"))
function CreateMeCommandMessageLabel(messageData, channelName)
if channelName ~= messageData.OriginalChannel then
if ChatSettings.ShowChannelsBar then
return
end
end
local fromSpeaker = messageData.FromSpeaker
local message = messageData.Message
local extraData = messageData.ExtraData or {}
local useFont = extraData.Font or Enum.Font.SourceSansItalic
local useTextSize = extraData.TextSize or ChatSettings.ChatWindowTextSize
local useNameColor = extraData.NameColor or ChatSettings.DefaultNameColor
local useChatColor = extraData.ChatColor or ChatSettings.DefaultChatColor
local useChannelColor = extraData.ChannelColor or useChatColor
local tags = extraData.Tags or {}
local formatUseName = string.format("%s ", fromSpeaker)
local speakerNameSize = util:GetStringTextBounds(formatUseName, useFont, useTextSize)
local numNeededSpaces = util:GetNumberOfSpaces(formatUseName, useFont, useTextSize)
local guiObjectSpacing = UDim2.new(0, 0, 0, 0)
local BaseFrame, BaseMessage = util:CreateBaseMessage("", useFont, useTextSize, useChatColor)
local NameButton = util:AddNameButtonToBaseMessage(BaseMessage, useNameColor, formatUseName, fromSpeaker)
local ChannelButton = nil
if channelName ~= messageData.OriginalChannel then
local formatChannelName = string.format("{%s} ", messageData.OriginalChannel)
ChannelButton = util:AddChannelButtonToBaseMessage(BaseMessage, useChannelColor, formatChannelName, messageData.OriginalChannel)
numNeededSpaces = numNeededSpaces + util:GetNumberOfSpaces(formatChannelName, useFont, useTextSize)
guiObjectSpacing = UDim2.new(0, ChannelButton.Size.X.Offset, 0, 0)
end
local tagLabels = {}
for i, tag in pairs(tags) do
local tagColor = tag.TagColor or Color3.fromRGB(255, 0, 255)
local tagText = tag.TagText or "???"
--I personally suggest remove the space, but it depends on you :?
local formatTagText = string.format("[%s] ", tagText)
local label = util:AddTagLabelToBaseMessage(BaseMessage, tagColor, formatTagText)
label.Position = guiObjectSpacing
numNeededSpaces = numNeededSpaces + util:GetNumberOfSpaces(formatTagText, useFont, useTextSize)
guiObjectSpacing = guiObjectSpacing + UDim2.new(0, label.Size.X.Offset, 0, 0)
table.insert(tagLabels, label)
end
NameButton.Position = guiObjectSpacing
local function UpdateTextFunction(messageObject)
if messageData.IsFiltered then
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. string.sub(messageObject.Message, 5)
else
local messageLength = string.len(messageObject.FromSpeaker) + messageObject.MessageLength - 4
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. string.rep("_", messageLength)
end
end
UpdateTextFunction(messageData)
local function GetHeightFunction(xSize)
return util:GetMessageHeight(BaseMessage, BaseFrame, xSize)
end
local FadeParmaters = {}
FadeParmaters[NameButton] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
FadeParmaters[BaseMessage] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
for i, tagLabel in pairs(tagLabels) do
local index = string.format("Tag%d", i)
FadeParmaters[tagLabel] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
end
if ChannelButton then
FadeParmaters[ChannelButton] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
end
local FadeInFunction, FadeOutFunction, UpdateAnimFunction = util:CreateFadeFunctions(FadeParmaters)
return {
[util.KEY_BASE_FRAME] = BaseFrame,
[util.KEY_BASE_MESSAGE] = BaseMessage,
[util.KEY_UPDATE_TEXT_FUNC] = UpdateTextFunction,
[util.KEY_GET_HEIGHT] = GetHeightFunction,
[util.KEY_FADE_IN] = FadeInFunction,
[util.KEY_FADE_OUT] = FadeOutFunction,
[util.KEY_UPDATE_ANIMATION] = UpdateAnimFunction
}
end
return {
[util.KEY_MESSAGE_TYPE] = ChatConstants.MessageTypeMeCommand,
[util.KEY_CREATOR_FUNCTION] = CreateMeCommandMessageLabel
}
================================================
FILE: src/Chat/ClientChatModules/MessageCreatorModules/SetCoreMessage.lua
================================================
-- // FileName: SetCoreMessage.lua
-- // Written by: TheGamer101
-- // Description: Create a message label for a message created with SetCore(ChatMakeSystemMessage).
local clientChatModules = script.Parent.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
local util = require(script.Parent:WaitForChild("Util"))
function CreateSetCoreMessageLabel(messageData, channelName)
if channelName ~= messageData.OriginalChannel then
if ChatSettings.ShowChannelsBar then
return
end
end
local message = messageData.Message
local extraData = messageData.ExtraData or {}
local useFont = extraData.Font or ChatSettings.DefaultFont
local useTextSize = extraData.TextSize or ChatSettings.ChatWindowTextSize
local useColor = extraData.Color or ChatSettings.DefaultMessageColor
local BaseFrame, BaseMessage = util:CreateBaseMessage(message, useFont, useTextSize, useColor)
local function GetHeightFunction(xSize)
return util:GetMessageHeight(BaseMessage, BaseFrame, xSize)
end
local FadeParmaters = {}
FadeParmaters[BaseMessage] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
local FadeInFunction, FadeOutFunction, UpdateAnimFunction = util:CreateFadeFunctions(FadeParmaters)
return {
[util.KEY_BASE_FRAME] = BaseFrame,
[util.KEY_BASE_MESSAGE] = BaseMessage,
[util.KEY_UPDATE_TEXT_FUNC] = nil,
[util.KEY_GET_HEIGHT] = GetHeightFunction,
[util.KEY_FADE_IN] = FadeInFunction,
[util.KEY_FADE_OUT] = FadeOutFunction,
[util.KEY_UPDATE_ANIMATION] = UpdateAnimFunction
}
end
return {
[util.KEY_MESSAGE_TYPE] = ChatConstants.MessageTypeSetCore,
[util.KEY_CREATOR_FUNCTION] = CreateSetCoreMessageLabel
}
================================================
FILE: src/Chat/ClientChatModules/MessageCreatorModules/SystemMessage.lua
================================================
-- // FileName: SystemMessage.lua
-- // Written by: TheGamer101
-- // Description: Create a message label for a system message.
local clientChatModules = script.Parent.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
local util = require(script.Parent:WaitForChild("Util"))
function CreateSystemMessageLabel(messageData, channelName)
if channelName ~= messageData.OriginalChannel then
if ChatSettings.ShowChannelsBar then
return
end
end
local message = messageData.Message
local extraData = messageData.ExtraData or {}
local useFont = extraData.Font or ChatSettings.DefaultFont
local useTextSize = extraData.TextSize or ChatSettings.ChatWindowTextSize
local useChatColor = extraData.ChatColor or ChatSettings.DefaultMessageColor
local useChannelColor = Color3.fromRGB(255, 0, 0)--extraData.ChannelColor or useChatColor
local BaseFrame, BaseMessage = util:CreateBaseMessage(message, useFont, useTextSize, useChatColor)
local ChannelButton = nil
if channelName ~= messageData.OriginalChannel then
local formatChannelName = string.format("[%s] ", string.upper(messageData.OriginalChannel))
ChannelButton = util:AddChannelButtonToBaseMessage(BaseMessage, useChannelColor, formatChannelName, messageData.OriginalChannel)
local numNeededSpaces = util:GetNumberOfSpaces(formatChannelName, useFont, useTextSize)
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. message
end
local function GetHeightFunction(xSize)
return util:GetMessageHeight(BaseMessage, BaseFrame, xSize)
end
local FadeParmaters = {}
FadeParmaters[BaseMessage] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
if ChannelButton then
FadeParmaters[ChannelButton] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
end
local FadeInFunction, FadeOutFunction, UpdateAnimFunction = util:CreateFadeFunctions(FadeParmaters)
return {
[util.KEY_BASE_FRAME] = BaseFrame,
[util.KEY_BASE_MESSAGE] = BaseMessage,
[util.KEY_UPDATE_TEXT_FUNC] = nil,
[util.KEY_GET_HEIGHT] = GetHeightFunction,
[util.KEY_FADE_IN] = FadeInFunction,
[util.KEY_FADE_OUT] = FadeOutFunction,
[util.KEY_UPDATE_ANIMATION] = UpdateAnimFunction
}
end
return {
[util.KEY_MESSAGE_TYPE] = ChatConstants.MessageTypeSystem,
[util.KEY_CREATOR_FUNCTION] = CreateSystemMessageLabel
}
================================================
FILE: src/Chat/ClientChatModules/MessageCreatorModules/UnknownMessage.lua
================================================
-- // FileName: UnknownMessage.lua
-- // Written by: TheGamer101
-- // Description: Default handler for message types with no other creator registered.
-- // Just print that there was a message with no creator for now.
local MESSAGE_TYPE = "UnknownMessage"
local clientChatModules = script.Parent.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local util = require(script.Parent:WaitForChild("Util"))
function CreateUnknownMessageLabel(messageData)
print("No message creator for message: " ..messageData.Message)
end
return {
[util.KEY_MESSAGE_TYPE] = MESSAGE_TYPE,
[util.KEY_CREATOR_FUNCTION] = CreateUnknownMessageLabel
}
================================================
FILE: src/Chat/ClientChatModules/MessageCreatorModules/Util.lua
================================================
-- // FileName: Util.lua
-- // Written by: Xsitsu, TheGamer101
-- // Description: Module for shared code between MessageCreatorModules.
--[[
Creating a message creator module:
1) Create a new module inside the MessageCreatorModules folder.
2) Create a function that takes a messageData object and returns:
{
KEY_BASE_FRAME = BaseFrame,
KEY_BASE_MESSAGE = BaseMessage,
KEY_UPDATE_TEXT_FUNC = function(newMessageObject) ---Function to update the text of the message.
KEY_GET_HEIGHT = function() ---Function to get the height of the message in absolute pixels,
KEY_FADE_IN = function(duration, CurveUtil) ---Function to tell the message to start fading in.
KEY_FADE_OUT = function(duration, CurveUtil) ---Function to tell the message to start fading out.
KEY_UPDATE_ANIMATION = function(dtScale, CurveUtil) ---Update animation function.
}
3) return the following format from the module:
{
KEY_MESSAGE_TYPE = "Message type this module creates messages for."
KEY_CREATOR_FUNCTION = YourFunctionHere
}
--]]
local DEFAULT_MESSAGE_CREATOR = "UnknownMessage"
local MESSAGE_CREATOR_MODULES_VERSION = 1
---Creator Module Object Keys
local KEY_MESSAGE_TYPE = "MessageType"
local KEY_CREATOR_FUNCTION = "MessageCreatorFunc"
---Creator function return object keys
local KEY_BASE_FRAME = "BaseFrame"
local KEY_BASE_MESSAGE = "BaseMessage"
local KEY_UPDATE_TEXT_FUNC = "UpdateTextFunction"
local KEY_GET_HEIGHT = "GetHeightFunction"
local KEY_FADE_IN = "FadeInFunction"
local KEY_FADE_OUT = "FadeOutFunction"
local KEY_UPDATE_ANIMATION = "UpdateAnimFunction"
local TextService = game:GetService("TextService")
local Players = game:GetService("Players")
local LocalPlayer = Players.LocalPlayer
while not LocalPlayer do
Players.ChildAdded:wait()
LocalPlayer = Players.LocalPlayer
end
local clientChatModules = script.Parent.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
local okShouldClipInGameChat, valueShouldClipInGameChat = pcall(function() return UserSettings():IsUserFeatureEnabled("UserShouldClipInGameChat") end)
local shouldClipInGameChat = okShouldClipInGameChat and valueShouldClipInGameChat
local module = {}
local methods = {}
methods.__index = methods
function methods:GetStringTextBounds(text, font, textSize, sizeBounds)
sizeBounds = sizeBounds or Vector2.new(10000, 10000)
return TextService:GetTextSize(text, textSize, font, sizeBounds)
end
--// Above was taken directly from Util.GetStringTextBounds() in the old chat corescripts.
function methods:GetMessageHeight(BaseMessage, BaseFrame, xSize)
xSize = xSize or BaseFrame.AbsoluteSize.X
local textBoundsSize = self:GetStringTextBounds(BaseMessage.Text, BaseMessage.Font, BaseMessage.TextSize, Vector2.new(xSize, 1000))
return textBoundsSize.Y
end
function methods:GetNumberOfSpaces(str, font, textSize)
local strSize = self:GetStringTextBounds(str, font, textSize)
local singleSpaceSize = self:GetStringTextBounds(" ", font, textSize)
return math.ceil(strSize.X / singleSpaceSize.X)
end
function methods:CreateBaseMessage(message, font, textSize, chatColor)
local BaseFrame = self:GetFromObjectPool("Frame")
BaseFrame.Selectable = false
BaseFrame.Size = UDim2.new(1, 0, 0, 18)
BaseFrame.Visible = true
BaseFrame.BackgroundTransparency = 1
local messageBorder = 8
local BaseMessage = self:GetFromObjectPool("TextLabel")
BaseMessage.Selectable = false
BaseMessage.Size = UDim2.new(1, -(messageBorder + 6), 1, 0)
BaseMessage.Position = UDim2.new(0, messageBorder, 0, 0)
BaseMessage.BackgroundTransparency = 1
BaseMessage.Font = font
BaseMessage.TextSize = textSize
BaseMessage.TextXAlignment = Enum.TextXAlignment.Left
BaseMessage.TextYAlignment = Enum.TextYAlignment.Top
BaseMessage.TextTransparency = 0
BaseMessage.TextColor3 = chatColor
BaseMessage.TextWrapped = true
if shouldClipInGameChat then
BaseMessage.ClipsDescendants = true
end
BaseMessage.Text = message
BaseMessage.Visible = true
BaseMessage.Parent = BaseFrame
-- Vesteria Styling
BaseMessage.TextStrokeTransparency = 0
BaseMessage.TextStrokeColor3 = Color3.new(0.2, 0.2, 0.2)
return BaseFrame, BaseMessage
end
function methods:AddNameButtonToBaseMessage(BaseMessage, nameColor, formatName, playerName)
local speakerNameSize = self:GetStringTextBounds(formatName, BaseMessage.Font, BaseMessage.TextSize)
local NameButton = self:GetFromObjectPool("TextButton")
NameButton.Selectable = false
NameButton.Size = UDim2.new(0, speakerNameSize.X, 0, speakerNameSize.Y)
NameButton.Position = UDim2.new(0, 0, 0, 0)
NameButton.BackgroundTransparency = 1
NameButton.Font = BaseMessage.Font
NameButton.TextSize = BaseMessage.TextSize
NameButton.TextXAlignment = BaseMessage.TextXAlignment
NameButton.TextYAlignment = BaseMessage.TextYAlignment
NameButton.TextTransparency = BaseMessage.TextTransparency
NameButton.TextStrokeTransparency = BaseMessage.TextStrokeTransparency
NameButton.TextStrokeColor3 = BaseMessage.TextStrokeColor3
NameButton.TextColor3 = nameColor
NameButton.Text = formatName
NameButton.Visible = true
NameButton.Parent = BaseMessage
local clickedConn = NameButton.MouseButton1Click:connect(function()
self:NameButtonClicked(NameButton, playerName)
end)
local changedConn = nil
changedConn = NameButton.Changed:connect(function(prop)
if prop == "Parent" then
clickedConn:Disconnect()
changedConn:Disconnect()
end
end)
return NameButton
end
function methods:AddChannelButtonToBaseMessage(BaseMessage, channelColor, formatChannelName, channelName)
local channelNameSize = self:GetStringTextBounds(formatChannelName, BaseMessage.Font, BaseMessage.TextSize)
local ChannelButton = self:GetFromObjectPool("TextButton")
ChannelButton.Selectable = false
ChannelButton.Size = UDim2.new(0, channelNameSize.X, 0, channelNameSize.Y)
ChannelButton.Position = UDim2.new(0, 0, 0, 0)
ChannelButton.BackgroundTransparency = 1
ChannelButton.Font = BaseMessage.Font
ChannelButton.TextSize = BaseMessage.TextSize
ChannelButton.TextXAlignment = BaseMessage.TextXAlignment
ChannelButton.TextYAlignment = BaseMessage.TextYAlignment
ChannelButton.TextTransparency = BaseMessage.TextTransparency
ChannelButton.TextStrokeTransparency = BaseMessage.TextStrokeTransparency
ChannelButton.TextStrokeColor3 = BaseMessage.TextStrokeColor3
ChannelButton.TextColor3 = channelColor
ChannelButton.Text = formatChannelName
ChannelButton.Visible = true
ChannelButton.Parent = BaseMessage
local clickedConn = ChannelButton.MouseButton1Click:connect(function()
self:ChannelButtonClicked(ChannelButton, channelName)
end)
local changedConn = nil
changedConn = ChannelButton.Changed:connect(function(prop)
if prop == "Parent" then
clickedConn:Disconnect()
changedConn:Disconnect()
end
end)
return ChannelButton
end
function methods:AddTagLabelToBaseMessage(BaseMessage, tagColor, formatTagText)
local tagNameSize = self:GetStringTextBounds(formatTagText, BaseMessage.Font, BaseMessage.TextSize)
local TagLabel = self:GetFromObjectPool("TextLabel")
TagLabel.Selectable = false
TagLabel.Size = UDim2.new(0, tagNameSize.X, 0, tagNameSize.Y)
TagLabel.Position = UDim2.new(0, 0, 0, 0)
TagLabel.BackgroundTransparency = 1
TagLabel.Font = BaseMessage.Font
TagLabel.TextSize = BaseMessage.TextSize
TagLabel.TextXAlignment = BaseMessage.TextXAlignment
TagLabel.TextYAlignment = BaseMessage.TextYAlignment
TagLabel.TextTransparency = BaseMessage.TextTransparency
TagLabel.TextStrokeTransparency = BaseMessage.TextStrokeTransparency
TagLabel.TextStrokeColor3 = BaseMessage.TextStrokeColor3
TagLabel.TextColor3 = tagColor
TagLabel.Text = formatTagText
TagLabel.Visible = true
TagLabel.Parent = BaseMessage
return TagLabel
end
function GetWhisperChannelPrefix()
if ChatConstants.WhisperChannelPrefix then
return ChatConstants.WhisperChannelPrefix
end
return "To "
end
function methods:NameButtonClicked(nameButton, playerName)
if not self.ChatWindow then
return
end
if ChatSettings.ClickOnPlayerNameToWhisper then
local player = Players:FindFirstChild(playerName)
if player and player ~= LocalPlayer then
local whisperChannel = GetWhisperChannelPrefix() ..playerName
if self.ChatWindow:GetChannel(whisperChannel) then
self.ChatBar:ResetCustomState()
local targetChannelName = self.ChatWindow:GetTargetMessageChannel()
if targetChannelName ~= whisperChannel then
self.ChatWindow:SwitchCurrentChannel(whisperChannel)
end
local whisperMessage = "/w " ..playerName
self.ChatBar:SetText(whisperMessage)
self.ChatBar:CaptureFocus()
elseif not self.ChatBar:IsInCustomState() then
local whisperMessage = "/w " ..playerName
self.ChatBar:SetText(whisperMessage)
self.ChatBar:CaptureFocus()
end
end
end
end
function methods:ChannelButtonClicked(channelButton, channelName)
if not self.ChatWindow then
return
end
if ChatSettings.ClickOnChannelNameToSetMainChannel then
if self.ChatWindow:GetChannel(channelName) then
self.ChatBar:ResetCustomState()
local targetChannelName = self.ChatWindow:GetTargetMessageChannel()
if targetChannelName ~= channelName then
self.ChatWindow:SwitchCurrentChannel(channelName)
end
self.ChatBar:ResetText()
self.ChatBar:CaptureFocus()
end
end
end
function methods:RegisterChatWindow(chatWindow)
self.ChatWindow = chatWindow
self.ChatBar = chatWindow:GetChatBar()
end
function methods:GetFromObjectPool(className)
if self.ObjectPool == nil then
return Instance.new(className)
end
return self.ObjectPool:GetInstance(className)
end
function methods:RegisterObjectPool(objectPool)
self.ObjectPool = objectPool
end
-- CreateFadeFunctions usage:
-- fadeObjects is a map of text labels and button to start and end values for a given property.
-- e.g {
-- NameButton = {
-- TextTransparency = {
-- FadedIn = 0.5,
-- FadedOut = 1,
-- }
-- },
-- ImageOne = {
-- ImageTransparency = {
-- FadedIn = 0,
-- FadedOut = 0.5,
-- }
-- }
-- }
function methods:CreateFadeFunctions(fadeObjects)
local AnimParams = {}
for object, properties in pairs(fadeObjects) do
AnimParams[object] = {}
for property, values in pairs(properties) do
AnimParams[object][property] = {
Target = values.FadedIn,
Current = object[property],
NormalizedExptValue = 1,
}
end
end
local function FadeInFunction(duration, CurveUtil)
for object, properties in pairs(AnimParams) do
for property, values in pairs(properties) do
values.Target = fadeObjects[object][property].FadedIn
values.NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
end
end
local function FadeOutFunction(duration, CurveUtil)
for object, properties in pairs(AnimParams) do
for property, values in pairs(properties) do
values.Target = fadeObjects[object][property].FadedOut
values.NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
end
end
local function AnimGuiObjects()
for object, properties in pairs(AnimParams) do
for property, values in pairs(properties) do
object[property] = values.Current
end
end
end
local function UpdateAnimFunction(dtScale, CurveUtil)
for object, properties in pairs(AnimParams) do
for property, values in pairs(properties) do
values.Current = CurveUtil:Expt(
values.Current,
values.Target,
values.NormalizedExptValue,
dtScale
)
end
end
AnimGuiObjects()
end
return FadeInFunction, FadeOutFunction, UpdateAnimFunction
end
function methods:NewBindableEvent(name)
local bindable = Instance.new("BindableEvent")
bindable.Name = name
return bindable
end
--- DEPRECATED METHODS:
function methods:RegisterGuiRoot()
-- This is left here for compatibility with ChatScript versions lower than 0.5
end
--- End of Deprecated methods.
function module.new()
local obj = setmetatable({}, methods)
obj.ObjectPool = nil
obj.ChatWindow = nil
obj.DEFAULT_MESSAGE_CREATOR = DEFAULT_MESSAGE_CREATOR
obj.MESSAGE_CREATOR_MODULES_VERSION = MESSAGE_CREATOR_MODULES_VERSION
obj.KEY_MESSAGE_TYPE = KEY_MESSAGE_TYPE
obj.KEY_CREATOR_FUNCTION = KEY_CREATOR_FUNCTION
obj.KEY_BASE_FRAME = KEY_BASE_FRAME
obj.KEY_BASE_MESSAGE = KEY_BASE_MESSAGE
obj.KEY_UPDATE_TEXT_FUNC = KEY_UPDATE_TEXT_FUNC
obj.KEY_GET_HEIGHT = KEY_GET_HEIGHT
obj.KEY_FADE_IN = KEY_FADE_IN
obj.KEY_FADE_OUT = KEY_FADE_OUT
obj.KEY_UPDATE_ANIMATION = KEY_UPDATE_ANIMATION
return obj
end
return module.new()
================================================
FILE: src/Chat/ClientChatModules/MessageCreatorModules/WelcomeMessage.lua
================================================
-- // FileName: WelcomeMessage.lua
-- // Written by: TheGamer101
-- // Description: Create a message label for a welcome message.
local clientChatModules = script.Parent.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
local util = require(script.Parent:WaitForChild("Util"))
function CreateWelcomeMessageLabel(messageData, channelName)
if channelName ~= messageData.OriginalChannel then
if ChatSettings.ShowChannelsBar then
return
end
end
local message = messageData.Message
local extraData = messageData.ExtraData or {}
local useFont = extraData.Font or ChatSettings.DefaultFont
local useTextSize = extraData.FontSize or ChatSettings.ChatWindowTextSize
local useChatColor = extraData.ChatColor or ChatSettings.DefaultMessageColor
local useChannelColor = extraData.ChannelColor or useChatColor
local BaseFrame, BaseMessage = util:CreateBaseMessage(message, useFont, useTextSize, useChatColor)
local ChannelButton = nil
if channelName ~= messageData.OriginalChannel then
local formatChannelName = string.format("{%s} ", messageData.OriginalChannel)
ChannelButton = util:AddChannelButtonToBaseMessage(BaseMessage, useChannelColor, formatChannelName, messageData.OriginalChannel)
local numNeededSpaces = util:GetNumberOfSpaces(formatChannelName, useFont, useTextSize)
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. message
end
local function GetHeightFunction(xSize)
return util:GetMessageHeight(BaseMessage, BaseFrame, xSize)
end
local FadeParmaters = {}
FadeParmaters[BaseMessage] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
if ChannelButton then
FadeParmaters[ChannelButton] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
end
local FadeInFunction, FadeOutFunction, UpdateAnimFunction = util:CreateFadeFunctions(FadeParmaters)
return {
[util.KEY_BASE_FRAME] = BaseFrame,
[util.KEY_BASE_MESSAGE] = BaseMessage,
[util.KEY_UPDATE_TEXT_FUNC] = nil,
[util.KEY_GET_HEIGHT] = GetHeightFunction,
[util.KEY_FADE_IN] = FadeInFunction,
[util.KEY_FADE_OUT] = FadeOutFunction,
[util.KEY_UPDATE_ANIMATION] = UpdateAnimFunction
}
end
return {
[util.KEY_MESSAGE_TYPE] = ChatConstants.MessageTypeWelcome,
[util.KEY_CREATOR_FUNCTION] = CreateWelcomeMessageLabel
}
================================================
FILE: src/Chat/ClientChatModules/MessageCreatorModules/WhisperMessage.lua
================================================
-- // FileName: WhisperMessage.lua
-- // Written by: TheGamer101
-- // Description: Create a message label for a whisper chat message.
local PlayersService = game:GetService("Players")
local LocalPlayer = PlayersService.LocalPlayer
while not LocalPlayer do
PlayersService.ChildAdded:wait()
LocalPlayer = PlayersService.LocalPlayer
end
local clientChatModules = script.Parent.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
local util = require(script.Parent:WaitForChild("Util"))
function CreateMessageLabel(messageData, channelName)
if channelName ~= messageData.OriginalChannel then
if ChatSettings.ShowChannelsBar then
return
end
end
local fromSpeaker = messageData.FromSpeaker
local message = messageData.Message
local extraData = messageData.ExtraData or {}
local useFont = extraData.Font or ChatSettings.DefaultFont
local useTextSize = extraData.TextSize or ChatSettings.ChatWindowTextSize
local useNameColor = extraData.NameColor or ChatSettings.DefaultNameColor
local useChatColor = extraData.ChatColor or ChatSettings.DefaultChatColor
local useChannelColor = extraData.ChannelColor or useChatColor
local tags = extraData.Tags or {}
local formatUseName = string.format("[%s]: ", fromSpeaker)
local speakerNameSize = util:GetStringTextBounds(formatUseName, useFont, useTextSize)
local numNeededSpaces = util:GetNumberOfSpaces(formatUseName, useFont, useTextSize)
local BaseFrame, BaseMessage = util:CreateBaseMessage("", useFont, useTextSize, useChatColor)
local NameButton = util:AddNameButtonToBaseMessage(BaseMessage, useNameColor, formatUseName, fromSpeaker)
local ChannelButton = nil
local guiObjectSpacing = UDim2.new(0, 0, 0, 0)
if channelName ~= messageData.OriginalChannel then
local whisperString = messageData.OriginalChannel
if messageData.FromSpeaker ~= LocalPlayer.Name then
whisperString = string.format("From %s", messageData.FromSpeaker)
end
local formatChannelName = string.format("{%s} ", whisperString)
ChannelButton = util:AddChannelButtonToBaseMessage(BaseMessage, useChannelColor, formatChannelName, messageData.OriginalChannel)
guiObjectSpacing = UDim2.new(0, ChannelButton.Size.X.Offset, 0, 0)
numNeededSpaces = numNeededSpaces + util:GetNumberOfSpaces(formatChannelName, useFont, useTextSize)
end
local tagLabels = {}
for i, tag in pairs(tags) do
local tagColor = tag.TagColor or Color3.fromRGB(255, 0, 255)
local tagText = tag.TagText or "???"
--I personally suggest remove the space, but it depends on you :?
local formatTagText = string.format("[%s] ", tagText)
local label = util:AddTagLabelToBaseMessage(BaseMessage, tagColor, formatTagText)
label.Position = guiObjectSpacing
numNeededSpaces = numNeededSpaces + util:GetNumberOfSpaces(formatTagText, useFont, useTextSize)
guiObjectSpacing = guiObjectSpacing + UDim2.new(0, label.Size.X.Offset, 0, 0)
table.insert(tagLabels, label)
end
NameButton.Position = guiObjectSpacing
local function UpdateTextFunction(messageObject)
if messageData.IsFiltered then
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. messageObject.Message
else
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. string.rep("_", messageObject.MessageLength)
end
end
UpdateTextFunction(messageData)
local function GetHeightFunction(xSize)
return util:GetMessageHeight(BaseMessage, BaseFrame, xSize)
end
local FadeParmaters = {}
FadeParmaters[NameButton] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
FadeParmaters[BaseMessage] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
for i, tagLabel in pairs(tagLabels) do
local index = string.format("Tag%d", i)
FadeParmaters[tagLabel] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
end
if ChannelButton then
FadeParmaters[ChannelButton] = {
TextTransparency = {FadedIn = 0, FadedOut = 1},
TextStrokeTransparency = {FadedIn = BaseMessage.TextStrokeTransparency, FadedOut = 1}
}
end
local FadeInFunction, FadeOutFunction, UpdateAnimFunction = util:CreateFadeFunctions(FadeParmaters)
return {
[util.KEY_BASE_FRAME] = BaseFrame,
[util.KEY_BASE_MESSAGE] = BaseMessage,
[util.KEY_UPDATE_TEXT_FUNC] = UpdateTextFunction,
[util.KEY_GET_HEIGHT] = GetHeightFunction,
[util.KEY_FADE_IN] = FadeInFunction,
[util.KEY_FADE_OUT] = FadeOutFunction,
[util.KEY_UPDATE_ANIMATION] = UpdateAnimFunction
}
end
return {
[util.KEY_MESSAGE_TYPE] = ChatConstants.MessageTypeWhisper,
[util.KEY_CREATOR_FUNCTION] = CreateMessageLabel
}
================================================
FILE: src/Chat/ClientChatModules/MessageCreatorModules/init.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/Chat/ClientChatModules/init.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/admin.lua
================================================
local abilities = {} do
--[[
coroutine.resume(coroutine.create(function()
for i, v in pairs(game.ReplicatedStorage.abilityLookup:GetChildren()) do
table.insert(abilities, {id = require(v).id})
end
end))
]]
spawn(function()
local abilityLookup = require(game.ReplicatedStorage.abilityLookup)
for id, ability in pairs(abilityLookup) do
-- prevent duplicates for string ids
if typeof(id) == "number" then
table.insert(abilities, {id = id})
end
end
end)
end
return {
name = "Admin";
pointsGainPerLevel = 5;
startingPoints = 0;
lockPointsOnClassChange = false;
minLevel = 1;
maxLevel = 9999999999;
-- visual attributes
layoutOrder = 5;
bookColor = Color3.fromRGB(130, 78, 154);
abilities = abilities;
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/adventurer.lua
================================================
return {
name = "Adventurer";
description = "A fledgling explorer can pick up many neat tricks on their adventures.";
pointsGainPerLevel = 1;
startingPoints = 0;
lockPointsOnClassChange = true;
minLevel = 1;
maxLevel = 10;
-- visual attributes
layoutOrder = 1;
bookColor = Color3.fromRGB(130, 98, 54);
thumbnail = "rbxassetid://3559739117";
abilities = {
{
id = 3;
prerequisiteId = nil;
};
{
id = 34;
prerequisiteId = nil;
};
{
id = 1;
prerequisiteId = nil;
};
{
id = 2;
prerequisiteId = nil;
};
};
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/assassin.lua
================================================
return {
name = "Assassin";
description = "Move in silence, strike in darkness. Such is the way.";
pointsGainPerLevel = 1;
startingPoints = 1;
lockPointsOnClassChange = true;
minLevel = 30;
maxLevel = 50;
-- visual attributes
layoutOrder = 3;
bookColor = Color3.fromRGB(20, 112, 84);
bookBackgroundImage = "rbxassetid://4149312565";
thumbnail = "rbxassetid://3559733836";
abilities = {
{
id = 43;
prerequisiteId = nil;
};
{
id = 14;
prerequisiteId = nil;
};
{
id = 53;
prerequisiteId = nil;
};
};
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/berserker.lua
================================================
return {
name = "Berserker";
description = "The fury of the twin blades knows no equal in battle.";
pointsGainPerLevel = 1;
startingPoints = 1;
lockPointsOnClassChange = true;
minLevel = 30;
maxLevel = 50;
-- visual attributes
layoutOrder = 3;
bookColor = Color3.fromRGB(117, 37, 100);
bookBackgroundImage = "rbxassetid://4149116180";
thumbnail = "rbxassetid://3559733966";
abilities = {
{
id = 47;
prerequisiteId = nil;
};
{
id = 55;
prerequisiteId = nil;
};
{
id = 60;
};
};
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/cleric.lua
================================================
return {
name = "Cleric";
description = "By the light of Vesra, your friends will be healed... and your enemies extinguished";
pointsGainPerLevel = 1;
startingPoints = 1;
lockPointsOnClassChange = true;
minLevel = 30;
maxLevel = 50;
-- visual attributes
layoutOrder = 3;
bookColor = Color3.fromRGB(8, 172, 167);
bookBackgroundImage = "rbxassetid://4149214333";
thumbnail = "rbxassetid://3559734054";
abilities = {
{
id = 38;
prerequisiteId = nil;
};
{
id = 50;
prerequisiteId = nil;
};
{
id = 45;
prerequisiteId = nil;
};
};
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/hunter.lua
================================================
return {
name = "Hunter";
description = "Wild and free, the hunter strikes quickly and with deadly precision.";
pointsGainPerLevel = 1;
startingPoints = 1;
lockPointsOnClassChange = true;
minLevel = 10;
maxLevel = 30;
-- visual attributes
layoutOrder = 2;
bookColor = Color3.fromRGB(63, 136, 57);
bookBackgroundImage = "rbxassetid://4149155579";
thumbnail = "rbxassetid://3559733836";
abilities = {
{
id = 7;
prerequisiteId = nil;
};
{
id = 6;
prerequisiteId = nil;
};
{
id = 15;
prerequisiteId = nil;
};
{
id = 13;
prerequisiteId = nil;
};
};
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/init.lua
================================================
--[[
abilityData {}
--> identifying information <--
int abilityId
--> generic information <--
string name
string image
string description
--> execution information <--
function execute
number maxRank
number cooldown
number cost
table prerequisite
prerequisiteData {}
number abilityId
number points
--]]
local lookupTable = {} do
for i, abilityBookModule in pairs(script:GetChildren()) do
local abilityBookData = require(abilityBookModule)
-- internal stuff
abilityBookData.module = abilityBookModule
-- hook ups
lookupTable[string.lower(abilityBookData.name)] = abilityBookData
lookupTable[string.upper(abilityBookData.name):sub(1, 1) .. abilityBookData.name:sub(2)] = abilityBookData
end
end
return lookupTable
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/knight.lua
================================================
return {
name = "Knight";
description = "Let them try and strike you. Your shield shall protect you, and they shall perish.";
pointsGainPerLevel = 1;
startingPoints = 1;
lockPointsOnClassChange = true;
minLevel = 30;
maxLevel = 50;
-- visual attributes
layoutOrder = 3;
bookColor = Color3.fromRGB(130, 25, 26);
bookBackgroundImage = "rbxassetid://4149116051";
thumbnail = "rbxassetid://3559733966";
abilities = {
{
id = 46;
prerequisiteId = nil;
};
{
id = 58,
},
{
id = 61,
},
};
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/mage.lua
================================================
return {
name = "Mage";
description = "An old tome of basic mana techniques for the apprentice mage.";
pointsGainPerLevel = 1;
startingPoints = 1;
lockPointsOnClassChange = true;
minLevel = 10;
maxLevel = 30;
-- visual attributes
layoutOrder = 2;
bookColor = Color3.fromRGB(11, 110, 130);
bookBackgroundImage = "rbxassetid://4149155849";
thumbnail = "rbxassetid://3559734054";
abilities = {
{
id = 4;
prerequisiteId = nil;
};
{
id = 9;
prerequisiteId = nil;
};
{
id = 12;
prerequisiteId = nil;
};
};
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/paladin.lua
================================================
return {
name = "Paladin";
description = "Gaze upon the light and disintegrate evil in it's warm embrace.";
pointsGainPerLevel = 1;
startingPoints = 1;
lockPointsOnClassChange = true;
minLevel = 30;
maxLevel = 50;
-- visual attributes
layoutOrder = 3;
bookColor = Color3.fromRGB(153, 74, 6);
bookBackgroundImage = "rbxassetid://4149136281";
thumbnail = "rbxassetid://3559733966";
abilities = {
{
id = 48,
prerequisiteId = nil
},
{
id = 56;
prerequisiteId = nil;
};
{
id = 54;
prerequisiteId = nil;
};
};
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/ranger.lua
================================================
return {
name = "Ranger";
description = "Fear the opponent that can tear you to pieces before you've even spotted them.";
pointsGainPerLevel = 1;
startingPoints = 1;
lockPointsOnClassChange = true;
minLevel = 30;
maxLevel = 50;
-- visual attributes
layoutOrder = 3;
bookColor = Color3.fromRGB(134, 136, 34);
bookBackgroundImage = "rbxassetid://4149312424";
thumbnail = "rbxassetid://3559733836";
abilities = {
{
id = 31;
prerequisiteId = nil;
};
{
id = 44;
prerequisiteId = nil;
};
{
id = 36;
prerequisiteId = nil;
};
};
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/shadow.lua
================================================
return {
name = "Shadow Master";
pointsGainPerLevel = 5;
startingPoints = 0;
lockPointsOnClassChange = false;
minLevel = 1;
maxLevel = 9999999999;
-- visual attributes
layoutOrder = 2;
bookColor = Color3.fromRGB(63, 136, 57);
bookBackgroundImage = "rbxgameasset://emblem_hunter";
abilities = {
{
id = 23;
prerequisiteId = nil;
};
{
id = 24;
prerequisiteId = nil;
};
};
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/sorcerer.lua
================================================
return {
name = "Sorcerer";
description = "The gold-lined elemantlist tome of the sorcerer contains many types of powerful spells.";
pointsGainPerLevel = 1;
startingPoints = 1;
lockPointsOnClassChange = true;
minLevel = 30;
maxLevel = 50;
-- visual attributes
layoutOrder = 3;
bookColor = Color3.fromRGB(24, 93, 166);
bookBackgroundImage = "rbxassetid://4149214600";
thumbnail = "rbxassetid://3559734054";
abilities = {
{
id = 49;
prerequisiteId = nil;
};
{
id = 52;
prerequisiteId = nil;
};
{
id = 37;
prerequisiteId = nil;
};
};
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/trickster.lua
================================================
return {
name = "Trickster";
description = "Some fear the unknown and revile what they cannot understand. Others embrace it.";
pointsGainPerLevel = 1;
startingPoints = 1;
lockPointsOnClassChange = true;
minLevel = 30;
maxLevel = 50;
-- visual attributes
layoutOrder = 3;
bookColor = Color3.fromRGB(14, 141, 5);
bookBackgroundImage = "rbxassetid://4149257161";
thumbnail = "rbxassetid://3559733836";
abilities = {
{
id = 42;
prerequisiteId = nil;
};
{
id = 41;
prerequisiteId = nil;
};
{
id = 51;
prerequisiteId = nil;
};
};
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/warlock.lua
================================================
return {
name = "Warlock";
description = "An ancient tome of powerful cursed magic. But at what cost?";
pointsGainPerLevel = 1;
startingPoints = 1;
lockPointsOnClassChange = true;
minLevel = 30;
maxLevel = 50;
-- visual attributes
layoutOrder = 3;
bookColor = Color3.fromRGB(76, 9, 153);
bookBackgroundImage = "rbxassetid://4149214475";
thumbnail = "rbxassetid://3559734054";
abilities = {
{
id = 39;
prerequisiteId = nil;
};
{
id = 40;
prerequisiteId = nil;
};
{
id = 57,
prerequisiteId = nil
},
{
id = 59,
},
};
}
================================================
FILE: src/ReplicatedStorage/abilityBookLookup/warrior.lua
================================================
return {
name = "Warrior";
description = "Formidable abilities taught to new Warrior recruits.";
pointsGainPerLevel = 1;
startingPoints = 1;
lockPointsOnClassChange = true;
minLevel = 10;
maxLevel = 30;
-- visual attributes
layoutOrder = 2;
bookColor = Color3.fromRGB(130, 59, 60);
bookBackgroundImage = "rbxassetid://4149155716";
thumbnail = "rbxassetid://3559733966";
abilities = {
{
id = 30;
prerequisiteId = nil;
};
{
id = 5;
prerequisiteId = nil;
};
{
id = 8;
prerequisiteId = nil;
};
{
id = 26;
prerequisiteId = nil;
};
{
id = 17;
prerequisiteId = nil;
};
};
}
================================================
FILE: src/ReplicatedStorage/abilityLookup/groundSlam.lua
================================================
--Modules
local Modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = Modules.load("network")
local Effects = Modules.load("effects")
local Tween = Modules.load("tween")
local Damage = Modules.load("damage")
local Detection = Modules.load("detection")
local PlaceSetup = Modules.load("placeSetup")
--Ability Assets
local replicatedStorage = game.ReplicatedStorage
local abilityAnims = replicatedStorage.assets.abilityAnimations
local abilitySounds = replicatedStorage.assets.abilities[script.Name].sounds
local abilityEffects = replicatedStorage.assets.abilities[script.Name].effects
--Ability Base Data
local abilityData = {
--> Identifying Information <--
id = 2;
--> Generic Information <--
name = "Ground Slam";
image = "rbxassetid://3736598447";
description = "AOE Damage Ability";
--> Misc Information <--
animationName = {"cast", "prayer"};
windupTime = 0.5;
speedMulti = 1.5;
--> Execution Data <--
executionData = {
level = 1;
maxLevel = 5;
};
--> Ability Stats <--
statistics = {
damage = 25;
radius = 15;
manaCost = 10;
cooldown = 15;
increasingStat = "radius";
increaseExponent = 0.2;
};
prerequisites = {
playerLevel = 1;
classRestricted = false;
developerOnly = false;
abilities = {};
};
}
--Client Execute Function
function abilityData:execute(abilityExecutionData, isAbilitySource)
local character = abilityExecutionData.casterCharacter
local renderCharacterContainer = network:invoke("getRenderCharacterContainerByEntityManifest", character.PrimaryPart)
if not renderCharacterContainer or not renderCharacterContainer.PrimaryPart then return false end
local currentlyEquipped = network:invoke("getCurrentlyEquippedForRenderCharacter", renderCharacterContainer.entity)
local currentWeaponManifest = currentlyEquipped["1"] and currentlyEquipped["1"].manifest
if not currentWeaponManifest then return end
local animTrack = renderCharacterContainer.entity.AnimationController:LoadAnimation(abilityAnims.warrior_forwardDownslash)
local trail
local sound = abilitySounds.cast:Clone()
sound.Parent = currentWeaponManifest
sound:Play()
game.Debris:AddItem(sound, 5)
local attach0 = currentWeaponManifest:FindFirstChild("bottomAttachment")
local attach1 = currentWeaponManifest:FindFirstChild("topAttachment")
if attach0 and attach1 then
trail = abilityEffects.Trail:Clone()
trail.Name = "groundSlamTrail"
trail.Attachment0 = attach0
trail.Attachment1 = attach1
trail.Parent = currentWeaponManifest
trail.Enabled = true
end
animTrack:Play(0.1, 1, self.speedMulti or 1)
wait(animTrack.Length * 0.06 / animTrack.Speed)
if isAbilitySource then
local movementVelocity = network:invoke("getMovementVelocity")
local movementDirection
if movementVelocity.magnitude > 0 then
movementDirection = movementVelocity.unit
else
movementDirection = character.PrimaryPart.CFrame.lookVector * 0.05
end
network:invoke("setCharacterArrested", true)
local bodyGyro = character.PrimaryPart.hitboxGyro
local bodyVelocity = character.PrimaryPart.hitboxVelocity
bodyGyro.CFrame = CFrame.new(Vector3.new(), movementDirection)
movementDirection = movementDirection + Vector3.new(0, 1, 0)
network:invoke("setMovementVelocity", movementDirection * 23 * animTrack.Speed)
end
wait(animTrack.Length * 0.36 / animTrack.Speed)
local timeLeft = 30
local startTime = tick()
local hitPart, hitPosition, hitNormal
animTrack:AdjustSpeed(0)
if isAbilitySource then
network:invoke("setMovementVelocity", Vector3.new())
network:invoke("setCharacterArrested", false)
end
repeat
local ray = Ray.new(currentWeaponManifest.Position + (renderCharacterContainer.PrimaryPart.CFrame.lookVector * 3) + Vector3.new(0, 2.5, 0), Vector3.new(0, -10, 0))
hitPart, hitPosition, hitNormal = workspace:FindPartOnRayWithIgnoreList(ray, {renderCharacterContainer, currentWeaponManifest})
wait()
until hitPart or (tick() - startTime) >= timeLeft
if trail then
trail:Destroy()
end
if isAbilitySource then
network:invoke("setCharacterArrested", true)
end
animTrack:AdjustSpeed(self.speedMulti)
spawn(function()
if not hitPart then return false end
local shockwave1 = abilityEffects.shockwaveEntity:Clone()
local shockwave2 = abilityEffects.shockwaveEntity:Clone()
shockwave1.Parent = entitiesFolder
shockwave2.Parent = entitiesFolder
local radius = abilityExecutionData.abilityData["statistics"].radius
local dustPart = abilityEffects.dustPart:Clone()
dustPart.Parent = workspace.CurrentCamera
dustPart.CFrame = CFrame.new(hitPosition)
dustPart.Dust.Speed = NumberRange.new(30 * (radius/10), 50 * (radius/10))
dustPart.Dust:Emit(100)
game.Debris:AddItem(dustPart,6)
local sound = abilitySounds.impact:Clone()
sound.Parent = dustPart
sound:Play()
if isAbilitySource then
for i, v in pairs(Damage.getDamagableTargets(game.Players.LocalPlayer)) do
local targetPosition = Detection.projection_Box(v.CFrame, v.Size, hitPosition)
if ((targetPosition - hitPosition) * Vector3.new(1,0,1)).magnitude <= radius * 0.7 and ((targetPosition - hitPosition) * Vector3.new(0,1,0)).magnitude <= (radius/2) * 0.7 then
network:fire("requestEntityDamageDealt", v, hitPosition, "ability", self.id, "shockwave", abilityExecutionData.abilityGuid)
--self:doKnockback(abilityExecutionData, v, hitPosition, getKnockbackAmount(abilityExecutionData))
elseif ((targetPosition - hitPosition) * Vector3.new(1,0,1)).magnitude <= radius and ((targetPosition - hitPosition) * Vector3.new(0,1,0)).magnitude <= (radius/2) then
network:fire("requestEntityDamageDealt", v, hitPosition, "ability", self.id, "shockwave-outer", abilityExecutionData.abilityGuid)
--self:doKnockback(abilityExecutionData, v, hitPosition, getKnockbackAmount(abilityExecutionData))
end
end
end
local cf = CFrame.new(hitPosition, hitPosition + hitNormal) * CFrame.Angles(math.pi / 2, 0, 0)
local multi = Vector3.new(radius * 1.7, -1.1, radius * 1.7)
local base = Vector3.new(0.25, 1.5, 0.25)
local duration = 1
shockwave1.Size = base
shockwave2.Size = base
shockwave1.CFrame = cf
shockwave2.CFrame = cf
shockwave1.Transparency = 0
shockwave2.Transparency = 0
Tween(shockwave1, {"Size", "Transparency"}, {base + multi, 1}, duration, Enum.EasingStyle.Quad)
Tween(shockwave2, {"Size", "Transparency"}, {base + multi, 1}, duration, Enum.EasingStyle.Quint)
game.Debris:AddItem(shockwave1, 1)
game.Debris:AddItem(shockwave2, 1)
end)
animTrack:AdjustSpeed(self.speedMulti * 1.65)
wait(animTrack.Length * 0.4 / animTrack.Speed)
if isAbilitySource then
network:invoke("setCharacterArrested", false)
end
return true, self.statistics.cooldown
end
--Server Execute Function
function abilityData:execute_server(castPlayer, abilityExecutionData, isAbilitySource)
end
return abilityData
================================================
FILE: src/ReplicatedStorage/abilityLookup/init.lua
================================================
local lookupTable = {}
local registerIds = {}
for i, abilityDataModule in pairs(script:GetChildren()) do
local abilityData = require(abilityDataModule)
lookupTable[abilityData.id] = abilityData
lookupTable[abilityDataModule.Name] = abilityData
if not registerIds[abilityData.id] then
registerIds[abilityData.id] = true
else
warn("@@@ ABILITY ID OVERLAP --", abilityData.id, abilityData.name, abilityDataModule.Name)
end
end
for i = 1, #script:GetChildren() do
if not registerIds[i] then
warn("@@@ ABILITY ID NOT TAKEN", i)
end
end
function lookupTable:GetAbilityIds()
return registerIds
end
return lookupTable
================================================
FILE: src/ReplicatedStorage/abilityLookup/regeneration.lua
================================================
--Modules
local Modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = Modules.load("network")
local Effects = Modules.load("effects")
local Tween = Modules.load("tween")
local PlaceSetup = Modules.load("placeSetup")
--Ability Assets
local replicatedStorage = game.ReplicatedStorage
local abilityAnims = replicatedStorage.assets.abilityAnimations
local abilitySounds = replicatedStorage.assets.abilities[script.Name].sounds
local abilityEffects = replicatedStorage.assets.abilities[script.Name].effects
--Ability Base Data
local abilityData = {
--> Identifying Information <--
id = 1;
--> Generic Information <--
name = "Regeneration";
image = "rbxassetid://2528901754";
description = "Regenerates all players health who are within a certain radius of cast";
--> Misc Information <--
animationName = {"cast", "prayer"};
windupTime = 1;
--> Execution Data <--
executionData = {
level = 1;
maxLevel = 15;
};
--> Ability Stats <--
statistics = {
healing = 5;
radius = 10;
manaCost = 5;
cooldown = 10;
increasingStat = "healing";
increaseExponent = 0.2;
};
prerequisites = {
playerLevel = 1;
classRestricted = false;
developerOnly = false;
abilities = {};
};
}
--Client Execute Function
function abilityData:execute(abilityExecutionData, isAbilitySource)
local character = abilityExecutionData.casterCharacter
local renderCharacterContainer = network:invoke("getRenderCharacterContainerByEntityManifest", character.PrimaryPart)
local abilityGuid = abilityExecutionData.abilityGuid
if not renderCharacterContainer or not abilityExecutionData or not abilityGuid then return false end
if not renderCharacterContainer.PrimaryPart then return false end
local root = renderCharacterContainer.PrimaryPart
local playerData = network:invoke("getLocalPlayerDataCache")
local showWeapons = Effects.hideWeapons(renderCharacterContainer.entity)
--If casting player is source then freeze player during ability
if isAbilitySource then
network:invoke("setCharacterArrested", true)
delay(self.windupTime, function()
network:invoke("setCharacterArrested", false)
end)
end
--Animation Here
local animTrack = renderCharacterContainer.entity.AnimationController:LoadAnimation(abilityAnims.prayer)
animTrack:Play()
--Cast Sound Here
local castSound = abilitySounds.cast:Clone()
castSound.Parent = root
castSound:Play()
game.Debris:AddItem(castSound, castSound.TimeLength)
wait(self.windupTime)
animTrack:Stop(0.5)
showWeapons()
--Ability Sound Here
local abilitySound = abilitySounds.prayer:Clone()
abilitySound.Parent = root
abilitySound:Play()
game.Debris:AddItem(abilitySound, abilitySound.TimeLength)
--Define Ability Statistics
local radius = abilityExecutionData.abilityData["statistics"]["radius"]
local diameter = radius * 2
local ring = abilityEffects.ring:Clone()
ring.CFrame = root.CFrame * CFrame.new(0, -2, 0)
ring.Parent = PlaceSetup.awaitPlaceFolder("entities")
Tween(ring, {"Size"}, Vector3.new(diameter, 2, diameter), 0.25)
Tween(ring, {"Transparency"}, 1, 1)
game.Debris:AddItem(ring, 1)
if isAbilitySource then
network:fireServer("requestAbilityStateUpdate", "end", abilityExecutionData)
end
return true, self.statistics.cooldown
end
--Server Execute Function
function abilityData:execute_server(castPlayer, abilityExecutionData, isAbilitySource)
end
return abilityData
================================================
FILE: src/ReplicatedStorage/accessoryLookup.lua
================================================
local module = {}
return module
================================================
FILE: src/ReplicatedStorage/beam-stuf.lua
================================================
local attach0 = Instance.new("Attachment", game.Workspace.Terrain);
local attach1 = Instance.new("Attachment", game.Workspace.Terrain);
local beam = Instance.new("Beam", game.Workspace.Terrain);
beam.Attachment0 = attach0;
beam.Attachment1 = attach1;
-- credits to: EgoMoose
local function beamProjectile(g, v0, x0, t1)
-- calculate the bezier points
local c = 0.5*0.5*0.5
local p3 = 0.5*g*t1*t1 + v0*t1 + x0
local p2 = p3 - (g*t1*t1 + v0*t1)/3
local p1 = (c*g*t1*t1 + 0.5*v0*t1 + x0 - c*(x0+p3))/(3*c) - p2
-- the curve sizes
local curve0 = (p1 - x0).magnitude
local curve1 = (p2 - p3).magnitude
-- build the world CFrames for the attachments
local b = (x0 - p3).unit
local r1 = (p1 - x0).unit
local u1 = r1:Cross(b).unit
local r2 = (p2 - p3).unit
local u2 = r2:Cross(b).unit
b = u1:Cross(r1).unit
local cf1 = CFrame.new(
x0.x, x0.y, x0.z,
r1.x, u1.x, b.x,
r1.y, u1.y, b.y,
r1.z, u1.z, b.z
)
local cf2 = CFrame.new(
p3.x, p3.y, p3.z,
r2.x, u2.x, b.x,
r2.y, u2.y, b.y,
r2.z, u2.z, b.z
)
return curve0, -curve1, cf1, cf2
end
local x0 = Vector3.new(0, 50, 0)
local v0 = Vector3.new(10, 25, 0)
local g = Vector3.new(0, -10, 0)
local t = 1
local p = Instance.new("Part", workspace)
p.Anchored = true
p.CFrame = CFrame.new(x0, x0+v0)
p.Size = Vector3.new(1, 1, 1)
p.FrontSurface = Enum.SurfaceType.Hinge
local curve0, curve1, cf1, cf2 = beamProjectile(g, v0, x0, t)
beam.CurveSize0 = curve0
beam.CurveSize1 = curve1
-- convert world space CFrames to be relative to the attachment parent
attach0.CFrame = attach0.Parent.CFrame:inverse() * cf1
attach1.CFrame = attach1.Parent.CFrame:inverse() * cf2
================================================
FILE: src/ReplicatedStorage/blessingLookup.lua
================================================
--[[
blessingData {}
--> identifying information <--
int blessingId
--> generic information <--
string name
string image
string description
--> execution information <--
--]]
local lookupTable = {} do
for i, blessingDataModule in pairs(script:GetChildren()) do
local blessingData = require(blessingDataModule)
-- internal stuff
blessingData.module = blessingDataModule
-- hook ups
lookupTable[blessingData.id] = blessingData
lookupTable[blessingDataModule.Name] = blessingData
end
end
return lookupTable
================================================
FILE: src/ReplicatedStorage/defaultCharacterAppearance.lua
================================================
local characterAppearanceData = {}
characterAppearanceData.equipment = {}
characterAppearanceData.accessories = {}
characterAppearanceData.accessories.face = 1
characterAppearanceData.accessories.hair = 3
characterAppearanceData.accessories.undershirt = 1
characterAppearanceData.accessories.underwear = 1
characterAppearanceData.accessories.hairColorId = 1
characterAppearanceData.accessories.skinColorId = 1
return characterAppearanceData
================================================
FILE: src/ReplicatedStorage/defaultMonsterState.lua
================================================
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local pathfinding = modules.load("pathfinding")
local utilities = modules.load("utilities")
local detection = modules.load("detection")
local network = modules.load("network")
local MONSTER_END_POSITION_ALPHA = 1
local rand = Random.new()
return {
processDamageRequest = function(sourceId, baseDamage)
return baseDamage, "physical", "direct"
end;
getClosestEntities = function(monster)
local pool = utilities.getEntities()
for i = #pool, 1, -1 do
local v = pool[i]
if v.Name == monster.monsterName or v == monster.manifest or v.health.Value <= 0 then
table.remove(pool, i)
end
end
return pool
end;
default = "idling",
states = {
["setup"] = {
animationEquivalent = "idling";
transitionLevel = 0;
step = function(monster)
if monster.moveGoal then
monster.__directRoamGoal = monster.moveGoal
monster.__directRoamOrigin = monster.manifest.Position
monster.__blockConfidence = 0
monster.__LAST_ROAM_TIME = tick()
return "direct-roam"
end
end;
};
["sleeping"] = {
animationEquivalent = "idling";
timeBetweenUpdates = 5;
transitionLevel = 1;
step = function(monster, canSwitchState)
if monster.closestEntity then
return "idling"
end
end;
}; ["idling"] = {
lockTimeForLowerTransition = 3;
transitionLevel = 2;
step = function(monster, canSwitchState)
monster.manifest.BodyVelocity.Velocity = Vector3.new()
if monster.moveGoal then
monster.__directRoamGoal = monster.moveGoal
monster.__directRoamOrigin = monster.manifest.Position
monster.__blockConfidence = 0
monster.__LAST_ROAM_TIME = tick()
return "direct-roam"
end
if monster.targetEntity then
return "following"
end
if monster.closestEntity and monster.closestEntity.health.value >= 0 then
local closestEntityDistance = utilities.magnitude(monster.closestEntity.Position - monster.manifest.Position)
local aggressionLockRange = monster.aggressionLockRange or monster.aggressionRange
if closestEntityDistance <= aggressionLockRange and monster:isTargetEntityInLineOfSight(aggressionLockRange, true) then
monster.__providedDirectRoamTheta = nil
monster:setTargetEntity(monster.closestEntity)
return "following"
else
if canSwitchState or monster.__providedDirectRoamTheta then
if (monster.__LAST_ROAM_TIME and tick() - monster.__LAST_ROAM_TIME < 5) and (monster.__providedDirectRoamTheta == nil) then
return "idling"
end
local XZ = Vector3.new(1, 0, 1)
-- local targetPosition = monster:getRoamPositionInSpawnRegion()
local theta = rand:NextInteger(1,360)
if monster.__providedDirectRoamTheta then
theta = monster.__providedDirectRoamTheta
monster.__providedDirectRoamTheta = nil
end
local rad = math.rad(theta)
local monsterPos = monster.manifest.Position * XZ
local targetPosition = monsterPos + Vector3.new(math.cos(rad), 0, math.sin(rad)) * rand:NextInteger(10, monster.maxRoamDistance or 50)
local direction = targetPosition - monsterPos
local startPos = monster.manifest.Position + Vector3.new(0, monster.manifest.Size.Y / 2, 0)
local finalDirection = direction - Vector3.new(0, monster.manifest.Size.Y / 2, 0)
local rey = Ray.new(startPos, finalDirection)
local hitPart, hitPosition = workspace:FindPartOnRayWithIgnoreList(rey,{monster.manifest,workspace.placeFolders:FindFirstChild("entityRenderCollection"),workspace.placeFolders:FindFirstChild("foilage")})
local monsterSize = (monster.manifest.Size.X + monster.manifest.Size.Z) / 2
local distance = (hitPosition - monster.manifest.Position).magnitude
-- cap the distance
--[[
if distance > (monster.maxRoamDistance or 50) then
hitPosition = startPos + finalDirection * 50
end
]]
local canRoam = true
local directRoamGoal = (hitPosition - direction * monsterSize/2) * XZ
if distance >= monsterSize and canRoam then
monster.__directRoamGoal = directRoamGoal
monster.__directRoamOrigin = monsterPos
monster.__directRoamTheta = theta
monster.__blockConfidence = 0
monster.__LAST_ROAM_TIME = tick()
return "direct-roam"
end
monster.__LAST_ROAM_TIME = tick() - 4.5
return
end
end
else
return "sleeping"
end
end;
}; ["wait-roaming"] = {
animationEquivalent = "idling";
transitionLevel = 3;
step = function(monster, canSwitchState)
if not monster.__IS_WAITING_FOR_PATH_FINDING then
if monster.isProcessingPath then
return "roaming"
else
monster:setTargetEntity(nil, nil)
return "idling"
end
end
--ber edit: end wait-roaming state and cancel pathfind if its been longer than 5 seconds
--i think getting stuck in the wait-roaming step is what leads monsters getting frozen
if monster.__PATHFIND_QUEUE_TIME and tick() - monster.__PATHFIND_QUEUE_TIME > 5 then
monster:resetPathfinding()
return "idling"
end
end
}; ["direct-roam"] = {
animationEquivalent = "walking";
transitionLevel = 3;
step = function(monster, canSwitchState)
if monster.moveGoal then
monster.moveGoal = nil
-- ignore block confidence and other checks for normal roaming
monster.__strictMovement = true
end
if monster.targetEntity then
return "following"
end
if monster.closestEntity and canSwitchState then
local closestEntityDistance = utilities.magnitude(monster.closestEntity.Position - monster.manifest.Position)
local aggressionLockRange = monster.aggressionLockRange or monster.aggressionRange
if closestEntityDistance <= aggressionLockRange and monster:isTargetEntityInLineOfSight(aggressionLockRange, true) then
monster:setTargetEntity(monster.closestEntity, monster.closestEntity)
return "following"
end
end
local start, goal = monster.__directRoamOrigin, monster.__directRoamGoal
if start and goal then
local XZ = Vector3.new(1, 0, 1)
local goalDistance = utilities.magnitude((goal - start) * XZ)
local distance = utilities.magnitude((monster.manifest.Position - start) * XZ)
local moveDirection = (goal * XZ - start * XZ).unit
monster.manifest.BodyVelocity.Velocity = moveDirection * monster.baseSpeed
monster.manifest.BodyGyro.CFrame = CFrame.new(start * XZ, goal * XZ)
-- arrived
if distance >= goalDistance then
monster.__strictMovement = false
-- give a chance to keep going in a similar direction
if monster.__directRoamTheta and rand:NextNumber() > 0.5 then
monster.__providedDirectRoamTheta = monster.__directRoamTheta + rand:NextInteger(-35,35)
return "idling"
end
monster.__providedDirectRoamTheta = nil
monster.manifest.BodyVelocity.Velocity = Vector3.new()
return "idling"
end
-- blocked
local expectedVelocity = monster.manifest.BodyVelocity.Velocity
local actualVelocity = monster.manifest.Velocity
local ray1 = Ray.new(monster.manifest.Position, Vector3.new(0,-50,0))
local ray2 = Ray.new(monster.manifest.Position + (moveDirection * monster.baseSpeed / 4), Vector3.new(0,-50,0))
local _, hitpos1 = workspace:FindPartOnRayWithIgnoreList(ray1,{monster.manifest,workspace.placeFolders:FindFirstChild("entityRenderCollection"),workspace.placeFolders:FindFirstChild("foilage")})
local _, hitpos2 = workspace:FindPartOnRayWithIgnoreList(ray2,{monster.manifest,workspace.placeFolders:FindFirstChild("entityRenderCollection"),workspace.placeFolders:FindFirstChild("foilage")})
-- prevent running into things or falling off cliffs
local yDisplacement = hitpos2.Y - hitpos1.Y
if not monster.__strictMovement then
if -yDisplacement >= math.max(monster.manifest.Size.Y, 6)then
-- cliff!
monster.manifest.BodyVelocity.Velocity = Vector3.new()
return "idling"
elseif tick() - monster.__LAST_ROAM_TIME >= 1 and actualVelocity.magnitude <= expectedVelocity.magnitude / 10 then
-- blocked
monster.manifest.BodyVelocity.Velocity = Vector3.new()
return "idling"
end
end
return
end
return "idling"
end
}; ["roaming"] = {
animationEquivalent = "walking";
transitionLevel = 3;
step = function(monster, canSwitchState)
if monster.closestEntity then
if not monster.path then
return "idling"
end
local closestEntityDistance = utilities.magnitude(monster.closestEntity.Position - monster.manifest.Position)
local nodeAt = monster.path[monster.currentNode]
local nodeTo = monster.path[monster.currentNode + 1]
if nodeAt and nodeTo then
if pathfinding.isPastNextPathfindingNodeNode(nodeAt.Position, monster.manifest.Position, nodeTo.Position) then
-- arrived at node
monster.currentNode = monster.currentNode + 1
monster.__PATH_FIND_NODE_START = tick()
local aggressionLockRange = monster.aggressionLockRange or monster.aggressionRange
if closestEntityDistance <= aggressionLockRange and monster:isTargetEntityInLineOfSight(aggressionLockRange, true) then
monster:resetPathfinding()
monster:setTargetEntity(monster.closestEntity, monster.closestEntity)
return "following"
end
else
if tick() - monster.__PATH_FIND_NODE_START < 2 then
-- move!
local adjustNodeToPosition = Vector3.new(nodeTo.Position.X, monster.manifest.Position.Y, nodeTo.Position.Z)
monster.manifest.BodyGyro.CFrame = CFrame.new(monster.manifest.Position, adjustNodeToPosition)
monster.manifest.BodyVelocity.Velocity = (adjustNodeToPosition - monster.manifest.Position).unit * monster.baseSpeed
else
-- monster taking too long to move to node
monster.manifest.BodyVelocity.Velocity = Vector3.new()
monster:resetPathfinding()
return "idling"
end
end
elseif nodeAt and not nodeTo then
-- reached the end
monster.manifest.BodyVelocity.Velocity = Vector3.new()
monster:resetPathfinding()
return "idling"
end
end
end;
}; ["following"] = {
animationEquivalent = "walking";
transitionLevel = 4;
step = function(monster, canSwitchState)
if not monster.targetEntity then
return "idling"
end
if monster.targetEntity.health.Value <= 0 then
monster:setTargetEntity(nil, nil)
return "idling"
end
local manifestProjectorPosition = monster.manifest.Position
local targetEntity = monster.targetEntity
local hrpPosition = targetEntity.Position
-- normalize y value to prevent difficulties when it comes
-- to the player and the monster being on different elevations
local correctHRPPosition = hrpPosition
if monster.manifest.BodyVelocity.MaxForce.Y <= 0.1 then
correctHRPPosition = Vector3.new(hrpPosition.X, manifestProjectorPosition.Y, hrpPosition.Z)
end
if monster.targetingYOffsetMulti then
correctHRPPosition = correctHRPPosition + Vector3.new(0, monster.manifest.Size.Y * monster.targetingYOffsetMulti, 0)
end
-- account for velocity
correctHRPPosition = correctHRPPosition + targetEntity.Velocity * 0.1
local targetEntityDistance = utilities.magnitude(manifestProjectorPosition - correctHRPPosition)
local moveDirection = (correctHRPPosition - manifestProjectorPosition).unit
local positionJustInsideAttackRange = correctHRPPosition - moveDirection * (monster.attackRange)
--monster.DEBUG_TARGET_PART.CFrame = CFrame.new(positionJustInsideAttackRange)
local movingSpeed = monster.followSpeed or monster.baseSpeed
-- have monster move in direction of player
-- but respect step to make this movement
-- framerate independent
-- CFrame.new(manifestProjectorPosition, positionJustInsideAttackRange) * CFrame.new(0, 0, -monster.baseSpeed * (step + (tick() - currTime)))
if monster:isTargetEntityInLineOfSight(monster.targetEntitySetSource and 999, not monster.targetEntitySetSource) then
-- player is still in line of sight of monster
monster.manifest.BodyVelocity.Velocity = moveDirection * movingSpeed
monster.manifest.BodyGyro.CFrame = CFrame.new(manifestProjectorPosition, Vector3.new(correctHRPPosition.X, manifestProjectorPosition.Y, correctHRPPosition.Z)) --CFrame.new(manifestProjectorPosition, correctHRPPosition)
monster.__LAST_POSITION_SEEN = positionJustInsideAttackRange
monster.__LAST_MOVE_DIRECTION = moveDirection * movingSpeed
else
if not monster.__LAST_POSITION_SEEN then
-- player is not in line of sight, but is still within aggro range -- pathfind!
-- ber edit: lose focus if the player is out of range
monster:setTargetEntity(nil, nil)
monster.manifest.BodyVelocity.Velocity = Vector3.new()
return "idling"
elseif monster.__LAST_POSITION_SEEN then
local lowerLastPositionSeen = Vector3.new(monster.__LAST_POSITION_SEEN.X, manifestProjectorPosition.Y, monster.__LAST_POSITION_SEEN.Z)
monster.manifest.BodyVelocity.Velocity = (lowerLastPositionSeen - manifestProjectorPosition).unit * movingSpeed
local sightRange = monster.sightRange
if targetEntityDistance <= sightRange then
-- check if the monster is close 'enough' to the last position seen
-- based on the position alpha
if utilities.magnitude(manifestProjectorPosition - monster.__LAST_POSITION_SEEN) > MONSTER_END_POSITION_ALPHA then
monster.__LAST_POSITION_SEEN = nil
monster:setTargetEntity(nil)
return "idling"
end
else
if monster.targetEntitySetSource == nil then
monster:setTargetEntity(nil)
return "idling"
end
end
else
monster:setTargetEntity(nil)
return "idling"
end
end
local manifestProjection = detection.projection_Box(monster.manifest.CFrame, monster.manifest.Size, hrpPosition)
local targetEntityProjection = detection.projection_Box(monster.targetEntity.CFrame, monster.targetEntity.Size, monster.manifest.Position)
local targetEntityDistance = utilities.magnitude(manifestProjection - targetEntityProjection)
if targetEntityDistance <= monster.attackRange then
return "attack-ready"
end
end;
}; ["attack-ready"] = {
animationEquivalent = "idling";
transitionLevel = 5;
step = function(monster, canSwitchState)
if monster.targetEntity == nil then
return "idling"
end
if monster.targetEntity.health.Value <= 0 then
monster:setTargetEntity(nil, nil)
return "idling"
end
local manifestProjectorPosition = monster.manifest.Position
local targetEntity = monster.targetEntity
local hrpPosition = targetEntity.Position
-- normalize y value to prevent difficulties when it comes
-- to the player and the monster being on different elevations
local correctHRPPosition = hrpPosition
if monster.manifest.BodyVelocity.MaxForce.Y <= 0.1 then
correctHRPPosition = Vector3.new(hrpPosition.X, manifestProjectorPosition.Y, hrpPosition.Z)
end
if monster.targetingYOffsetMulti then
correctHRPPosition = correctHRPPosition + Vector3.new(0, monster.manifest.Size.Y * monster.targetingYOffsetMulti, 0)
end
local manifestProjection = detection.projection_Box(monster.manifest.CFrame, monster.manifest.Size, hrpPosition)
local targetEntityProjection = detection.projection_Box(monster.targetEntity.CFrame, monster.targetEntity.Size, monster.manifest.Position)
local targetEntityDistance = utilities.magnitude(manifestProjection - targetEntityProjection)
if targetEntityDistance <= monster.attackRange then
-- aggro'd and within attackRange
local manifestProjectorPosition = monster.manifest.Position
-- monster.manifest.BodyGyro.CFrame = CFrame.new(manifestProjectorPosition, correctHRPPosition)
if tick() - monster.__LAST_ATTACK_TIME >= monster.attackSpeed then
monster.__LAST_ATTACK_TIME = tick()
return "attacking"
else
monster.manifest.BodyVelocity.Velocity = Vector3.new()
end
else
return "following"
end
end;
}; ["attacking"] = {
transitionLevel = 6;
animationPriority = Enum.AnimationPriority.Action;
doNotLoopAnimation = true;
doNotStopAnimation = true;
execute = function(player, targetAnimation, monsterData, clientMonsterContainer)
if not game:GetService("RunService"):IsClient() then
warn("attacking::execute can only be called on client")
return
elseif not monsterData.damageHitboxCollection then
-- no damageHitboxCollection
end
-- prevent the player from taking damage twice from same animation
local damageDebounce = false
local characterHitbox = player.Character.PrimaryPart
local serverHitbox = clientMonsterContainer.clientHitboxToServerHitboxReference.Value
if clientMonsterContainer:FindFirstChild("entity") and clientMonsterContainer.entity.PrimaryPart:FindFirstChild("attacking") then
local chance = 1
if clientMonsterContainer:FindFirstChild("entity") and clientMonsterContainer.entity.PrimaryPart:FindFirstChild("attacking2") then
chance = math.random(2)
if chance == 2 then
clientMonsterContainer.entity.PrimaryPart.attacking2:Play()
end
end
if chance == 1 then
clientMonsterContainer.entity.PrimaryPart.attacking:Play()
end
end
-- slight delay
local delayTime = targetAnimation.Length * (monsterData.animationDamageStart or 0.3)
wait(delayTime)
local endTime = targetAnimation.Length * (monsterData.animationDamageEnd or 0.7)
local duration = endTime - delayTime
local step = duration / 10
for i = 0, duration, step do
if targetAnimation.IsPlaying and not damageDebounce and clientMonsterContainer:FindFirstChild("entity") then
for i, hitboxData in pairs(monsterData.damageHitboxCollection) do
if clientMonsterContainer.entity:FindFirstChild(hitboxData.partName) and not damageDebounce then
if hitboxData.castType == "sphere" then
local spherecastOrigin = (clientMonsterContainer.entity[hitboxData.partName].CFrame * hitboxData.originOffset).p
local boxProjection_HRP = detection.projection_Box(characterHitbox.CFrame, characterHitbox.Size, spherecastOrigin)
if detection.spherecast_singleTarget(spherecastOrigin, hitboxData.radius, boxProjection_HRP) then
damageDebounce = true
network:fireServer("playerRequest_damageEntity", serverHitbox, boxProjection_HRP, "monster")
end
elseif hitboxData.castType == "box" then
local boxcastOriginCF = clientMonsterContainer.entity[hitboxData.partName].CFrame * hitboxData.originOffset
local boxProjection_HRP = detection.projection_Box(characterHitbox.CFrame, characterHitbox.Size, boxcastOriginCF.p)
if detection.boxcast_singleTarget(boxcastOriginCF, clientMonsterContainer.entity[hitboxData.partName].Size * (hitboxData.hitboxSizeMultiplier or Vector3.new(1, 1, 1)), boxProjection_HRP) then
damageDebounce = true
network:fireServer("playerRequest_damageEntity", serverHitbox, boxProjection_HRP, "monster")
end
end
end
end
wait(step)
else
break
end
end
end;
step = function(monster, canSwitchState)
if monster.targetEntity == nil then
return "idling"
end
local manifestProjectorPosition = monster.manifest.Position
local targetEntity = monster.targetEntity
local hrpPosition = targetEntity.Position
-- normalize y value to prevent difficulties when it comes
-- to the player and the monster being on different elevations
local correctHRPPosition = hrpPosition
if monster.manifest.BodyVelocity.MaxForce.Y <= 0.1 then
correctHRPPosition = Vector3.new(hrpPosition.X, manifestProjectorPosition.Y, hrpPosition.Z)
end
if monster.targetingYOffsetMulti then
correctHRPPosition = correctHRPPosition + Vector3.new(0, monster.manifest.Size.Y * monster.targetingYOffsetMulti, 0)
end
correctHRPPosition = correctHRPPosition + targetEntity.Velocity * 0.1
local targetEntityDistance = utilities.magnitude(manifestProjectorPosition - correctHRPPosition)
local moveDirection = (correctHRPPosition - manifestProjectorPosition).unit
local positionJustInsideAttackRange = correctHRPPosition - moveDirection * (monster.attackRange)
-- todo: this is awfully hacky, change this!
-- monster.manifest.stance.Value = tostring(math.random())
monster.manifest.BodyGyro.CFrame = CFrame.new(manifestProjectorPosition, correctHRPPosition)
if utilities.magnitude(targetEntity.Velocity) > 3 then
local moveDirection = (correctHRPPosition - manifestProjectorPosition).unit
local positionJustInsideAttackRange = correctHRPPosition - moveDirection * (monster.attackRange)
monster.manifest.BodyVelocity.Velocity = moveDirection * (monster.baseSpeed * 0.7)
else
monster.manifest.BodyVelocity.Velocity = Vector3.new()
end
local targetEntityPlayer = game.Players:GetPlayerFromCharacter(monster.targetEntity.Parent)
if not targetEntityPlayer then
delay(0.25, function()
network:invoke("monsterDamageRequest_server",
nil,
monster.targetEntity,
{
damage = monster.damage;
sourceType = "monster";
sourceId = nil;
damageCategory = "direct";
damageType = "physical"
}
)
end)
end
return "micro-sleeping"
end;
}; ["special-attacking"] = {
animationEquivalent = "special-attack";
transitionLevel = 7;
step = function(monster, canSwitchState)
monster.specialsUsed = monster.specialsUsed + 1
if monster.__STATE_OVERRIDES["special-attacking"] then
monster.__STATE_OVERRIDES["special-attacking"](monster)
end
return "special-recovering";
end;
}; ["micro-sleeping"] = {
animationEquivalent = "idling";
transitionLevel = 8;
lockTimeForLowerTransition = 0.2;
step = function(monster, canSwitchState)
return "attack-ready"
end;
};["special-recovering"] = {
animationEquivalent = "idling";
transitionLevel = 9;
lockTimeForLowerTransition = 0.75;
step = function(monster, canSwitchState)
return "attack-ready";
end;
}; ["dead"] = {
animationEquivalent = "death";
transitionLevel = math.huge;
stopTransitions = false;
step = function(monster, canSwitchState)
return nil
end;
execute = function()
return nil
end
}; ["attacked-by-player"] = {
transitionLevel = 1;
step = function(monster)
print("HELLO! :D", monster.closestEntity, monster.entityMonsterWasAttackedBy)
if monster.closestEntity and (monster.targetEntityLockType or 0) <= 1 and monster.entityMonsterWasAttackedBy then
local closestEntityDistance = utilities.magnitude(monster.entityMonsterWasAttackedBy.Position - monster.manifest.Position)
if monster:isTargetEntityInLineOfSight(nil, false, monster.entityMonsterWasAttackedBy) then
monster:setTargetEntity(monster.entityMonsterWasAttackedBy)
return "following"
end
end
return "idling"
end
}
};
}
================================================
FILE: src/ReplicatedStorage/defaultPetState.lua
================================================
-- piggyback off monsters hehe!
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local pathfinding = modules.load("pathfinding")
local utilities = modules.load("utilities")
local projectile = modules.load("projectile")
local detection = modules.load("detection")
local network = modules.load("network")
local placeSetup = modules.load("placeSetup")
local placeFolders = placeSetup.getPlaceFoldersFolder()
local itemLookup do
spawn(function()
-- unfortunately there's a circular reference
-- here if we don't do this.. yucky
itemLookup = require(replicatedStorage.itemData)
end)
end
local playerHeight = 2
local petAcquisitionRange = 6
local petScanRange = 20
local petIgnoreList = {placeFolders}
local function getClosestItemWithinRange(monster, doForceRefresh)
if not monster.__LAST_NEARBY_ITEM_CHECK then
monster.__LAST_NEARBY_ITEM_CHECK = 0
end
if not monster.owner then
return nil
end
if not itemLookup then
return nil
end
if doForceRefresh or tick() - monster.__LAST_NEARBY_ITEM_CHECK >= 2 then
monster.__LAST_NEARBY_ITEM_CHECK = tick()
local closestItem, closestItemDistane = nil, petScanRange
for i, itemPart in pairs(placeFolders.items:GetChildren()) do
local distanceAway = (itemPart.Position - monster.manifest.Position).magnitude
if distanceAway <= closestItemDistane and utilities.playerCanPickUpItem(monster.owner, itemPart, true) and (not itemPart:FindFirstChild("playerDropSource") or itemPart.playerDropSource.Value ~= monster.owner.userId) then
local ray = Ray.new(monster.manifest.Position, itemPart.Position - monster.manifest.Position)
local hitPart = projectile.raycastForProjectile(ray, petIgnoreList)
if not hitPart then
if itemLookup[itemPart.Name] then
if network:invoke("doesPlayerHaveInventorySpaceForTrade", monster.owner, {}, {{id = itemLookup[itemPart.Name].id}}) then
-- good, nothing between
closestItem = itemPart
closestItemDistane = distanceAway
end
end
end
end
end
monster.__CLOSEST_ITEM = closestItem
end
return monster.__CLOSEST_ITEM
end
return {
default = "setup",
states = {
["setup"] = {
animationEquivalent = "idle";
transitionLevel = 0;
step = function(monster)
if monster.owner then
if monster.owner.Character and monster.owner.Character.PrimaryPart then
local targetCFrame = monster.owner.Character.PrimaryPart.CFrame * CFrame.new(0, 0, 3) * CFrame.new(2, 0, 0)
monster.manifest.CFrame = targetCFrame
return "idle"
end
end
end;
}; ["idle"] = {
animationEquivalent = "idle";
transitionLevel = 1;
step = function(monster, canSwitchState)
if monster.owner then
if monster.owner.Character and monster.owner.Character.PrimaryPart then
local closestItem = getClosestItemWithinRange(monster)
if not closestItem then
-- local targetCFrame = monster.owner.Character.PrimaryPart.CFrame * CFrame.new(0, 0, 3) * CFrame.new(2, 0, 0)
-- local correct_manifestPosition = Vector3.new(monster.manifest.Position.X, targetCFrame.Y, monster.manifest.Position.Z)
-- if (correct_manifestPosition - targetCFrame.p).magnitude >= 3 then
-- return "follow"
-- else
-- monster.manifest.BodyGyro.CFrame = CFrame.new(monster.manifest.Position, monster.manifest.Position + targetCFrame.lookVector)
-- monster.manifest.BodyVelocity.Velocity = Vector3.new()
-- end
local distanceFromOwner = ((monster.owner.Character.PrimaryPart.Position - monster.manifest.Position) * Vector3.new(1, 0, 1)).magnitude
if distanceFromOwner > 7 then
return "follow"
else
-- monster.manifest.BodyGyro.CFrame = CFrame.new(monster.manifest.Position, monster.manifest.Position + monster.owner.Character.PrimaryPart.CFrame.lookVector)
monster.manifest.BodyVelocity.Velocity = Vector3.new()
end
else
-- close item!
monster.timeSwitchToFetching = tick()
return "fetching-item"
end
end
end
end;
}; ["follow"] = {
animationEquivalent = "movement";
transitionLevel = 2;
step = function(monster, canSwitchState)
local closestItem = getClosestItemWithinRange(monster)
if not closestItem then
-- local targetCFrame = monster.owner.Character.PrimaryPart.CFrame * CFrame.new(0, 0, 3) * CFrame.new(2, 0, 0)
-- local correct_manifestPosition = Vector3.new(monster.manifest.Position.X, targetCFrame.Y, monster.manifest.Position.Z)
local targetMovementDirection = ((monster.owner.Character.PrimaryPart.Position - monster.manifest.Position) * Vector3.new(1, 0, 1))
monster.manifest.BodyVelocity.Velocity = targetMovementDirection.unit * 20
monster.manifest.BodyGyro.CFrame = CFrame.new(monster.manifest.Position, monster.manifest.Position + targetMovementDirection)
if targetMovementDirection.magnitude < 7 then
return "idle"
elseif targetMovementDirection.magnitude >= 40 then
return "setup"
end
else
monster.timeSwitchToFetching = tick()
return "fetching-item"
end
end;
}; ["fetching-item"] = {
animationEquivalent = "movement";
transitionLevel = 3;
step = function(monster, canSwitchState)
local closestItem = getClosestItemWithinRange(monster)
if closestItem then
local targetCFrame = closestItem.CFrame
local correct_manifestPosition = Vector3.new(monster.manifest.Position.X, targetCFrame.Y, monster.manifest.Position.Z)
monster.manifest.BodyGyro.CFrame = CFrame.new(correct_manifestPosition, targetCFrame.p)
if monster.timeSwitchToFetching and tick() - monster.timeSwitchToFetching <= 6 then
if (correct_manifestPosition - targetCFrame.p).magnitude < 3 then
monster.manifest.BodyVelocity.Velocity = Vector3.new()
local success, errmsg = network:invoke("pickUpItemForPlayer_server", monster.owner, closestItem, true)
if success then
getClosestItemWithinRange(monster, true)
end
else
monster.manifest.BodyVelocity.Velocity = (targetCFrame.p - correct_manifestPosition).unit * 20
end
else
return "setup"
end
else
return "follow"
end
end;
};
};
}
================================================
FILE: src/ReplicatedStorage/dialogue/genericShopkeeper.lua
================================================
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
return {
id = "startTalkingToShopkeeper";
dialogue = {{text = "Hey, you! I'm a test NPC for Dialogue. How are you?"}};
options = {
["Gross, don't talk to me."] = {
dialogue = {{text = "That's not very nice.. :("}};
dialogue2 = {{text = "Get out of here whippersnapper!"}};
};
["That's cool!"] = {
dialogue = {{text = "I know."}};
};
["Give me a sick quest."] = {
onSelected = function()
end;
dialogue = {{text = "Don't worry, I got you."}};
}
};
}
================================================
FILE: src/ReplicatedStorage/dialogue/init.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/ReplicatedStorage/dialogue/testNPCDialog.lua
================================================
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
-- quest 1
local dialogue = {
onSelected = function()
end;
id = "startTalkingToShopkeeper";
dialogue = {{text = "Hey, you! I'm a test NPC for Dialogue. How are you?"}};
options = {
["Gross, don't talk to me."] = {
dialogue = {{text = "That's not very nice.. :("}};
dialogue2 = {{text = "Get out of here whippersnapper!"}};
};
["That's cool!"] = {
dialogue = {{text = "I know."}};
};
["Give me a sick quest."] = {
onSelected = function()
end;
dialogue = {{text = "Don't worry, I got you."}};
}
};
}
local dialogue2 = {
dialogue = {{text = "Greetings Adventurer!"}};
options = {
{
response = "Today is such a beautiful day!";
dialogue = {{text = "No it isn't, my wife is sick."}};
};
{
questId = 2;
}
};
}
return dialogue2
================================================
FILE: src/ReplicatedStorage/dialogueLookup.lua
================================================
local module = {}
return module
================================================
FILE: src/ReplicatedStorage/itemAttributes.lua
================================================
local function stat(itemBaseData, inventorySlotData, multi)
multi = multi or 1
local level = itemBaseData.minLevel or 1
local itemType = itemBaseData.equipmentSlot
local typeScaleMulti = (itemType == 1 and 0.75) or (itemType == 8 and 1) or 1.5
return math.ceil((multi * level) / (5 * typeScaleMulti))
end
local attributes = {
-- hats only
worn = {
prefix = "Worn";
color = Color3.fromRGB(148, 148, 148);
valueMulti = 0.75;
modifier = function(itemBaseData, inventorySlotData)
local penalty = -stat(itemBaseData, inventorySlotData, 0.5)
-- ??
end;
};
-- armor only
tattered = {
prefix = "Tattered";
color = Color3.fromRGB(148, 148, 148);
valueMulti = 0.75;
modifier = function(itemBaseData, inventorySlotData)
return {defense = -(stat(itemBaseData, inventorySlotData)+1)}
end;
};
-- weapons only
dull = {
prefix = "Dull";
color = Color3.fromRGB(148, 148, 148);
valueMulti = 0.75;
modifier = function(itemBaseData, inventorySlotData)
return {baseDamage = -(stat(itemBaseData, inventorySlotData, 0.75)+1)}
end;
};
keen = {
prefix = "Keen";
color = Color3.fromRGB(135, 186, 213);
valueMulti = 1.25;
modifier = function(itemBaseData, inventorySlotData)
return {int = stat(itemBaseData, inventorySlotData)}
end;
};
fierce = {
prefix = "Fierce";
color = Color3.fromRGB(190, 143, 109);
valueMulti = 1.25;
modifier = function(itemBaseData, inventorySlotData)
return {str = stat(itemBaseData, inventorySlotData)}
end;
};
swift = {
prefix = "Swift";
color = Color3.fromRGB(190, 130, 179);
valueMulti = 1.25;
modifier = function(itemBaseData, inventorySlotData)
return {dex = stat(itemBaseData, inventorySlotData)}
end;
};
vibrant = {
prefix = "Vibrant";
color = Color3.fromRGB(194, 127, 128);
valueMulti = 1.25;
modifier = function(itemBaseData, inventorySlotData)
return {vit = stat(itemBaseData, inventorySlotData)}
end;
};
pristine = {
prefix = "Pristine";
color = Color3.fromRGB(150, 45, 202);
valueMulti = 1.75;
modifier = function(itemBaseData, inventorySlotData)
local itemType = itemBaseData.equipmentSlot
if itemType == 1 then
return {
baseDamage = stat(itemBaseData, inventorySlotData, 0.75 * 0.7);
wisdom = stat(itemBaseData, inventorySlotData, 0.4)/100;
}
elseif itemType == 8 then
return {
defense = stat(itemBaseData, inventorySlotData, 0.7);
wisdom = stat(itemBaseData, inventorySlotData, 0.4)/100;
}
end
end;
};
legendary = {
prefix = "Legendary";
color = Color3.fromRGB(225, 176, 28);
valueMulti = 3;
modifier = function(itemBaseData, inventorySlotData)
local itemType = itemBaseData.equipmentSlot
if itemType == 1 then
return {
baseDamage = stat(itemBaseData, inventorySlotData, 0.75 * 1.2);
wisdom = stat(itemBaseData, inventorySlotData, 0.6)/100;
}
elseif itemType == 8 then
return {
defense = stat(itemBaseData, inventorySlotData, 1.2);
wisdom = stat(itemBaseData, inventorySlotData, 0.6)/100;
}
end
end;
};
}
return attributes
================================================
FILE: src/ReplicatedStorage/itemData/100% armor defense scroll.lua
================================================
return {
--> identifying information <--
id = 26;
--> generic information <--
name = "Basic Armor DEF Scroll (100%)";
rarity = "Rare";
image = "rbxassetid://2528903584";
description = "A magical scroll that has the ability to increase an armor's defense.";
enchantments = {
[1] = {modifierData = {defense = 1}; selectionWeight = 1; tier = 1};
};
tier = 2;
--> stats information <--
successRate = 1;
validation = function(itemBaseData, inventorySlotData)
if itemBaseData.category == "equipment" and itemBaseData.equipmentSlot == 8 then
return true
end
return false
end;
applyScroll = function(player, itemInventorySlotData, successfullyScrolled)
local itemLookup = require(game:GetService("ReplicatedStorage"):WaitForChild("itemData"))
local itemBaseData = itemLookup[itemInventorySlotData.id]
if itemBaseData.category == "equipment" and itemBaseData.equipmentSlot == 8 then
return true
end
return false
end;
--> shop information <--
buyValue = 12000;
sellValue = 400;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = false;
enchantsEquipment = true;
--> sorting information <--
isImportant = true;
category = "consumable";
}
================================================
FILE: src/ReplicatedStorage/itemData/100% headgear defense scroll.lua
================================================
return {
--> identifying information <--
id = 61;
--> generic information <--
name = "Basic Headgear Scroll (100%)";
rarity = "Rare";
image = "rbxassetid://2528903584";
description = "A magical scroll that has the ability to increase a headgear's stats.";
enchantments = {
[1] = {modifierData = {}; statUpgrade = 1; selectionWeight = 1; tier = 1};
};
tier = 2;
--> stats information <--
successRate = 1;
validation = function(itemBaseData, inventorySlotData)
if itemBaseData.category == "equipment" and itemBaseData.equipmentSlot == 2 then
return true
end
return false
end;
applyScroll = function(player, itemInventorySlotData, successfullyScrolled)
local itemLookup = require(game:GetService("ReplicatedStorage"):WaitForChild("itemData"))
local itemBaseData = itemLookup[itemInventorySlotData.id]
if itemBaseData.category == "equipment" and itemBaseData.equipmentSlot == 2 then
return true
end
return false
end;
--> shop information <--
buyValue = 10000;
sellValue = 700;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = false;
enchantsEquipment = true;
--> sorting information <--
isImportant = true;
category = "consumable";
}
================================================
FILE: src/ReplicatedStorage/itemData/100% weapon attack scroll.lua
================================================
return {
--> identifying information <--
id = 14;
--> generic information <--
name = "Basic Weapon ATK Scroll (100%)";
rarity = "Rare";
image = "rbxassetid://2528903584";
description = "A magical scroll that has the ability to increase a weapon's attack power.";
enchantments = {
[1] = {modifierData = {baseDamage = 1}; selectionWeight = 1; tier = 1};
};
tier = 2;
validation = function(itemBaseData, inventorySlotData)
if itemBaseData.category == "equipment" and itemBaseData.equipmentSlot == 1 then
return true
end
return false
end;
--> stats information <--
successRate = 1;
applyScroll = function(player, itemInventorySlotData, successfullyScrolled, playerInput, _scrData)
local itemLookup = require(game:GetService("ReplicatedStorage"):WaitForChild("itemData"))
local itemBaseData = itemLookup[itemInventorySlotData.id]
if itemBaseData.category == "equipment" and itemBaseData.equipmentSlot == 1 then
return true
end
return false
end;
--> shop information <--
buyValue = 15000;
sellValue = 1000;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = false;
enchantsEquipment = true;
--> sorting information <--
isImportant = true;
category = "consumable";
}
================================================
FILE: src/ReplicatedStorage/itemData/apple.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
item = {
--> identifying information <--
id = 226;
--> generic information <--
name = "Red Apple";
rarity = "Common";
image = "rbxassetid://4574376530";
description = "A crisp apple fresh from the tree.";
itemType = "food";
useSound = "eat_food";
--> stats information <--
activationEffect = function(player)
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.health.Value > 0 and player.Character.PrimaryPart.health.Value < player.Character.PrimaryPart.maxHealth.Value then
local success = network:invoke("applyPotionStatusEffectToEntityManifest_server", player.Character.PrimaryPart, 30, nil, "item", 226)
return success, success and "You feel refreshed." or "ERRORRRR"
end
return false, "Character is invalid."
end;
consumeTime = 1;
stackSize = 44;
--> shop information <--
buyValue = 200;
sellValue = 50;
--> handling information <--
canStack = true;
canBeBound = true;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "consumable";
}
return item
================================================
FILE: src/ReplicatedStorage/itemData/arrow.lua
================================================
return {
--> identifying information <--
id = 87;
--> generic information <--
name = "Arrow";
rarity = "Common";
image = "rbxassetid://2744225103";
description = "For shooting things.";
--> handling information <--
canStack = true;
canBeBound = false;
canAwaken = false;
equipmentType = "arrow";
equipmentPosition = 12;
--> shop information <--
buyValue = 10;
sellValue = 3;
stackSize = 999;
--> crafting information <--
recipe = {
-- oak wood
{id = 2, stacks = 1},
-- stone
{id = 600, stacks = 1},
-- feather
{id = 271, stacks = 1},
};
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/chicken egg.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
item = {
--> identifying information <--
id = 270;
--> generic information <--
name = "Chicken Egg";
rarity = "Common";
image = "rbxassetid://4635646183";
description = "Makes for a great breakfast.";
--> shop information <--
sellValue = 15;
buyValue = 150;
--> handling information <--
canStack = true;
canBeBound = false;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "miscellaneous";
}
return item
================================================
FILE: src/ReplicatedStorage/itemData/chicken feather.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
item = {
--> identifying information <--
id = 271;
--> generic information <--
name = "Chicken Feather";
rarity = "Common";
image = "rbxassetid://4635643200";
description = "Plucked fresh from the source.";
--> shop information <--
sellValue = 15;
buyValue = 150;
--> handling information <--
canStack = true;
canBeBound = false;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "miscellaneous";
}
return item
================================================
FILE: src/ReplicatedStorage/itemData/chicken leg.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
item = {
--> identifying information <--
id = 277;
--> generic information <--
name = "Chicken Leg";
rarity = "Common";
image = "rbxassetid://5048073993";
description = "A rare treat.";
itemType = "food";
useSound = "eat_food";
--> stats information <--
activationEffect = function(player)
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.health.Value > 0 and player.Character.PrimaryPart.health.Value < player.Character.PrimaryPart.maxHealth.Value then
local success = network:invoke("applyPotionStatusEffectToEntityManifest_server", player.Character.PrimaryPart, 30, nil, "item", 277)
return success, success and "You feel refreshed." or "ERRORRRR"
end
return false, "Character is invalid."
end;
consumeTime = 3;
stackSize = 16;
--> shop information <--
buyValue = 1000;
sellValue = 250;
--> handling information <--
canStack = true;
canBeBound = false;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "consumable";
}
return item
================================================
FILE: src/ReplicatedStorage/itemData/coin piece.lua
================================================
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
return {
--> identifying information <--
id = 1;
--> generic information <--
name = "Mushcoin";
rarity = "Common";
image = "rbxassetid://2535600080";
description = "The main currency of Vesteria";
useSound = "coins";
--> stats information <--
activationEffect = function(player, physItem)
local value = physItem:FindFirstChild("itemValue") and physItem.itemValue.Value or 1
local source = physItem:FindFirstChild("itemSource") and physItem.itemSource.Value
local playerData = network:invoke("getPlayerData", player)
if playerData then
local totalStats = network:invoke("getPlayerData", player).nonSerializeData.statistics_final
local greed = totalStats and totalStats.greed or 1
local amount = math.floor(value * greed)
playerData.nonSerializeData.incrementPlayerData("gold", amount, source)
return true, "Successfully gained "..value.." coins!", amount
end
return false, "Can't consume this right now", 0
end;
--> handling information <--
canStack = true;
canBeBound = false;
canAwaken = false;
autoConsume = true;
--> sorting information <--
isImportant = false;
category = "consumable";
}
================================================
FILE: src/ReplicatedStorage/itemData/copper axe.lua
================================================
return {
--> identifying information <--
id = 403;
--> generic information <--
name = "Copper Axe";
rarity = "Common";
image = "rbxassetid://5344159593";
description = "A smelted bronze axe with a smoothed frame.";
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "axe";
gripCFrame = CFrame.Angles(math.pi / 2, 0, 0) * CFrame.new(0, -0.2, 2);
minLevel = 5;
--> stats information <--
baseDamage = 6;
modifierData = {{woodcutting = 7}};
--> crafting information <--
recipe = {
-- oak wood
{id = 2, stacks = 20},
-- copper
{id = 601, stacks = 20}
};
--> shop information <--
buyValue = 2000;
sellValue = 700;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/copper ore.lua
================================================
return {
--> identifying information <--
id = 601;
--> generic information <--
name = "Copper Ore";
rarity = "Common";
image = "rbxassetid://5429389124";
description = "An ore of shiny copper that is used in many recipes.";
-- todo: wood item type icon
-- itemType = "wood";
--> handling information <--
canStack = true;
--> shop information <--
sellValue = 250;
buyValue = 2500;
--> sorting information <--
category = "miscellaneous";
}
================================================
FILE: src/ReplicatedStorage/itemData/copper pickaxe.lua
================================================
return {
--> identifying information <--
id = 503;
--> generic information <--
name = "Copper Pickaxe";
rarity = "Common";
image = "rbxassetid://5344159326";
description = "A smelted bronze pickaxe with a smoothed frame.";
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "pickaxe";
gripCFrame = CFrame.Angles(math.pi / 2, 0, 0) * CFrame.new(0, -0.2, 2);
minLevel = 5;
--> stats information <--
baseDamage = 6;
modifierData = {{mining = 7}};
--> crafting information <--
recipe = {
-- oak wood
{id = 2, stacks = 20},
-- copper
{id = 601, stacks = 20}
};
--> shop information <--
buyValue = 2000;
sellValue = 700;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/copper sword.lua
================================================
return {
--> identifying information <--
id = 32;
--> generic information <--
name = "Copper Sword";
rarity = "Common";
image = "rbxassetid://2528903917";
description = "A large sword forged from copper. It's not the strongest, but at least it's not falling apart.";
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "sword";
gripCFrame = CFrame.Angles(-math.pi / 2, 0, 0) * CFrame.new(0.1, 0, 2) * CFrame.Angles(0, 0, math.pi / 2);
minLevel = 7;
--> stats information <--
baseDamage = 9;
attackSpeed = 3;
bonusStats = {};
--> crafting information <--
recipe = {
-- copper
{id = 601, stacks = 30}
};
--> shop information <--
buyValue = 2000;
sellValue = 1000;
--> sorting information <--
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/fish.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
return {
--> identifying information <--
id = 30;
--> generic information <--
name = "Fresh Fish";
rarity = "Common";
image = "rbxassetid://2528901664";
description = "A tasty freshly-caught fish that restores 100 HP & MP.";
itemType = "fish";
useSound = "eat_food";
--> stats information <--
activationEffect = function(player)
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.health.Value > 0 then
if (player.Character.PrimaryPart.mana.Value < player.Character.PrimaryPart.maxMana.Value or player.Character.PrimaryPart.health.Value < player.Character.PrimaryPart.maxHealth.Value) then
network:invoke("applyPotionStatusEffectToEntityManifest_server", player.Character.PrimaryPart, 100, 100, "item", 6)
end
return true, "You feel refreshed."
end
return false, "Character is invalid."
end;
--> shop information <--
buyValue = 300;
sellValue = 80;
--> handling information <--
canStack = true;
canBeBound = true;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "consumable";
}
================================================
FILE: src/ReplicatedStorage/itemData/frying pan.lua
================================================
return {
id = 295,
name = "Frying Pan",
rarity = "Common",
image = "rbxassetid://4906325480",
description = "You have to break a few eggs to make an omelette.",
isEquippable = true,
equipmentSlot = 1,
equipmentType = "sword",
minLevel = 6;
--> stats information <--
baseDamage = 7;
attackSpeed = 3;
bonusStats = {};
--> shop information <--
buyValue = 1300;
sellValue = 400;
canAwaken = true,
category = "equipment",
}
================================================
FILE: src/ReplicatedStorage/itemData/health potion.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
return {
--> identifying information <--
id = 6;
--> generic information <--
name = "Red Potion";
rarity = "Common";
image = "rbxassetid://2528902180";
description = "A vibrant potion";
customTag = {
{text = "Restores"; font = Enum.Font.SourceSans; textColor3 = Color3.fromRGB(160,160,160); textSize = 19; textTransparency = 0};
{text = "25 HP"; font = Enum.Font.SourceSansBold; textColor3 = Color3.fromRGB(226,34,40); textSize = 19; textTransparency = 0};
{text = "after"; font = Enum.Font.SourceSans; textColor3 = Color3.fromRGB(160,160,160); textSize = 19; textTransparency = 0};
{text = "1s"; font = Enum.Font.SourceSansBold; textColor3 = Color3.fromRGB(160,160,160); textSize = 19; textTransparency = 0};
};
itemType = "potion";
useSound = "potion";
--> stats information <--
activationEffect = function(player)
print(
player.Character,
player.Character.PrimaryPart,
player.Character.PrimaryPart.health.Value,
player.Character.PrimaryPart.maxHealth.Value
)
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.health.Value > 0 and player.Character.PrimaryPart.health.Value < player.Character.PrimaryPart.maxHealth.Value then
local success = network:invoke("applyPotionStatusEffectToEntityManifest_server", player.Character.PrimaryPart, 25, nil, "item", 6)
return success, success and "You feel refreshed." or "ERRORRRR"
end
return false, "Character is invalid."
end;
stackSize = 32;
--> shop information <--
buyValue = 50;
sellValue = 20;
--> handling information <--
canStack = true;
canBeBound = true;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "consumable";
}
================================================
FILE: src/ReplicatedStorage/itemData/hog headdress.lua
================================================
return {
id = 291,
name = "Hog Headdress",
rarity = "Rare",
image = "rbxassetid://4899219006",
description = "Some hunters like to wear the remains of their enemies for good luck.",
itemType = "hat";
isEquippable = true,
equipmentSlot = 2,
equipmentType = "hat",
perks = nil,
modifierData = {{maxHealth = 10; luck = 1;}},
minimumClass = nil,
minLevel = 15,
canAwaken = true,
category = "equipment",
}
================================================
FILE: src/ReplicatedStorage/itemData/hog meat.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
item = {
--> identifying information <--
id = 144;
--> generic information <--
name = "Hog Meat";
rarity = "Common";
image = "rbxassetid://3164395419";
description = "Mmm, fresh hog. Restores 70 HP and boosts Attack for 3 minutes.";
itemType = "food";
--> handling information <--
canStack = true;
canBeBound = true;
canAwaken = false;
--> shop information <--
sellValue = 200;
buyValue = 2000;
--> stats information <--
activationEffect = function(player)
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.health.Value > 0 then
local success = network:invoke("applyPotionStatusEffectToEntityManifest_server", player.Character.PrimaryPart, 70, nil, "item", 6)
if not success then return false, "failed to give health" end
local wasApplied, reason = network:invoke("applyStatusEffectToEntityManifest", player.Character.PrimaryPart, "empower", {
duration = 3 * 60;
modifierData = {
equipmentDamage = 5;
};
}, player.Character.PrimaryPart, "item", item.id)
return wasApplied, reason
end
return false, "Character is invalid."
end;
stackSize = 32;
--> sorting information <--
isImportant = false;
category = "consumable";
}
return item
================================================
FILE: src/ReplicatedStorage/itemData/hog tusk dagger.lua
================================================
return {
id = 289,
name = "Hog Tusk Dagger",
rarity = "Rare",
image = "rbxassetid://4899155150",
description = "Not in a hog's face, but still used for stabbing.",
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "dagger";
gripCFrame = CFrame.Angles(math.pi / 2, 0, 0) * CFrame.new(-0.15, 0, 0.83) * CFrame.Angles(0, math.pi, -math.pi/2);
minLevel = 6;
--> stats information <--
baseDamage = 4;
attackSpeed = 5;
modifierData = {{criticalStrikeChance = 0.07}};
customTag = {text = "Inflicts Bleeding"; font = Enum.Font.SourceSans; textSize = 19; textColor3 = Color3.fromRGB(212, 95, 91); textTransparency = 0} ;
--> shop information <--
buyValue = 800;
sellValue = 160;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/init.lua
================================================
--[[
itemData {}
--> identifying information <--
int id
--> generic information <--
string name
string rarity
string image
string description
--> tool information <--
CFrame grip
--> stats information <--
number baseDamage
bonusStats {}
int str = 0
int sta = 0
int dex = 0
int int = 0
number cdrPercentage = 0
number cdrFlat = 0
number dmgPercent = 0
number dmgFlat = 0
number hpPercent = 0;
number hpFlat = 0;
number expPercent = 0;
number expFlat = 0;
number defenseFlat = nil;
--> handling information <--
bool canStack = true
bool canBeBound = false
bool canAwaken = false
--> sorting information <--
bool isImportant = false
string category = "miscellaneous" ["equipment", "consumable", "miscellaneous"]
--]]
local collectionService = game:GetService("CollectionService")
-- doing a direct require here to avoid a circular require problem
local levels = require(game.ReplicatedStorage:WaitForChild("modules"):WaitForChild("levels"))
local highest = 0
local lookupTable = {} do
for i, itemDataModule in pairs(script:GetChildren()) do
-- automatically fix up bad items
if itemDataModule:FindFirstChild("container") then
for i, part in pairs(itemDataModule.container:GetChildren()) do
if part:IsA("BasePart") then
if part.Name == "Head" then
part.Transparency = 1
else
part.Material = "Glass"
part.Transparency = 0.5
part.Reflectance = 0.2
part.Color = Color3.new(1,1,1)
end
if collectionService:HasTag(part, "interact") then
collectionService:RemoveTag(part, "interact")
end
end
end
end
local itemData = require(itemDataModule)
if itemData.category == "equipment" then
-- determine the amount of an upgrades an item can have based on its rarity and time
local maxUpgrades = 0
if itemData.equipmentSlot == 1 or itemData.equipmentSlot == 8 then
maxUpgrades = 7
elseif itemData.equipmentSlot == 2 then
maxUpgrades = 3
end
if itemData.equipmentType == "greatsword" then
-- itemData.minimumClass = "paladin"
elseif itemData.equipmentType == "shield" then
-- itemData.minimumClass = "knight"
end
--[[
local equipInfo = levels.getEquipmentInfo(itemData)
if equipInfo then
if equipInfo.cost then
itemData.buyValue = itemData.cost or math.ceil(equipInfo.cost * (itemData.valueMulti or 1))
itemData.sellValue = itemData.cost and itemData.cost * 0.2 or math.ceil(equipInfo.cost * 0.2)
end
if equipInfo.damage then
itemData.baseDamage = math.ceil(equipInfo.damage * (itemData.damageMulti or 1))
end
if equipInfo.defense then
itemData.modifierData = itemData.modifierData or {{}}
itemData.modifierData[1] = itemData.modifierData[1] or {}
itemData.modifierData[1].defense = (itemData.modifierData[1].defense or 0) + math.ceil(equipInfo.defense * (itemData.defenseMulti or 1))
end
if equipInfo.modifierData then
itemData.modifierData = itemData.modifierData or {}
table.insert(itemData.modifierData, equipInfo.modifierData)
end
if equipInfo.statUpgrade then
itemData.statUpgrade = itemData.statUpgrade or equipInfo.statUpgrade
end
end
if itemData.perks then
-- items with perks get blue tier by default
itemData.tier = itemData.tier or 2
end
]]
itemData.maxUpgrades = itemData.maxUpgrades or maxUpgrades
end
-- item categorization for icons
local itemType = "misc"
if itemData.category == "equipment" then
if itemData.equipmentSlot == 1 then
itemType = itemData.equipmentType or "sword"
end
elseif itemData.category == "consumable" then
if itemData.applyScroll then
itemType = "scroll"
end
end
itemData.itemType = itemData.itemType or itemType
-- if itemData.category == "equipment" and itemData.canStack then
-- error(itemDataModule.Name .. " should not be allowed to stack")
-- end
if itemDataModule:FindFirstChild("container") and not itemDataModule.container.PrimaryPart then
itemDataModule.container.PrimaryPart = itemDataModule.container:FindFirstChildWhichIsA("BasePart")
end
-- internal stuff
itemData.module = itemDataModule
-- hook ups, check for conflicts..
if lookupTable[itemData.id] then
warn("CONFLICT OF ITEM IDS @", itemData.id, itemData.name, lookupTable[itemData.id].name)
end
lookupTable[itemData.id] = itemData
lookupTable[itemDataModule.Name] = itemData
if itemData.id > highest then
highest = itemData.id
end
end
end
print("HIGHEST ID >>>", highest)
return lookupTable
================================================
FILE: src/ReplicatedStorage/itemData/iron ore.lua
================================================
return {
id = 288,
name = "Iron Ore",
rarity = "Common",
image = "rbxassetid://4849612961",
description = "Rock rich in iron. Somehow the exposed metal hasn't rusted.",
canStack = true,
sellValue = 30,
buyValue = 150,
isImportant = false,
category = "miscellaneous",
}
================================================
FILE: src/ReplicatedStorage/itemData/item lore.lua
================================================
-- prevents requiring the modules outside of runtime
-- a problem for future me to deal with
--local disallowedWhiteSpace = {"\n", "\r", "\t", "\v", "\f"}
--local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
--local network = modules.load("network")
return {
--> identifying information <--
id = 104;
--> generic information <--
name = "Ink & Quill";
nameColor = Color3.fromRGB(237, 98, 255);
rarity = "Legendary";
image = "rbxassetid://2856319694";
description = "Add a line of custom lore description to your equipment.";
--> stats information <--
successRate = 1;
upgradeCost = 0;
playerInputFunction = function()
local playerInput = {}
playerInput.desiredName = network:invoke("textInputPrompt", {prompt = "Write your item's lore..."})
return playerInput
end;
applyScroll = function(player, itemInventorySlotData, successfullyScrolled, playerInput)
local itemLookup = require(game:GetService("ReplicatedStorage"):WaitForChild("itemData"))
local itemBaseData = itemLookup[itemInventorySlotData.id]
if not playerInput then
return false, "no player input"
end
if itemBaseData.category == "equipment" then
if successfullyScrolled then
local desiredName = playerInput.desiredName
if not desiredName then
return false, "desired item story not provided"
end
if #desiredName > 40 then
return false, "Item story cannot be longer than 40 characters"
end
-- eliminate white space
for i,whitespace in pairs(disallowedWhiteSpace) do
if string.find(desiredName, whitespace) then
return false, "Item story cannot contain whitespace characters."
end
end
if #desiredName < 3 then
return false, "Item story must be at least 3 characters long."
end
local filteredText
local filterSuccess, filterError = pcall(function()
filteredText = game.Chat:FilterStringForBroadcast(desiredName, player)
end)
if not filterSuccess then
return false, "filter error: "..filterError
end
if not filteredText or string.find(filteredText, "#") then
return false, "Item story rejected by Roblox filter."
end
-- if we've gotten this far, we must be good
itemInventorySlotData.customStory = filteredText
return true, "Item story applied."
end
end
return false, "Only equipment can be given a story."
end;
--> shop information <--
buyValue = 1000000;
sellValue = 1000;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = false;
enchantsEquipment = true;
--> sorting information <--
isImportant = true;
category = "consumable";
}
================================================
FILE: src/ReplicatedStorage/itemData/item renamer.lua
================================================
-- prevents requiring the modules outside of runtime
-- a problem for future me to deal with
--local disallowedWhiteSpace = {"\n", "\r", "\t", "\v", "\f"}
--local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
--local network = modules.load("network")
return {
--> identifying information <--
id = 103;
--> generic information <--
name = "Name Tag";
nameColor = Color3.fromRGB(237, 98, 255);
rarity = "Legendary";
image = "rbxassetid://2858147882";
description = "Give a piece of equipment a special name.";
--> stats information <--
successRate = 1;
upgradeCost = 0;
playerInputFunction = function()
local playerInput = {}
playerInput.desiredName = network:invoke("textInputPrompt", {prompt = "Name your item..."})
return playerInput
end;
applyScroll = function(player, itemInventorySlotData, successfullyScrolled, playerInput)
local itemLookup = require(game:GetService("ReplicatedStorage"):WaitForChild("itemData"))
local itemBaseData = itemLookup[itemInventorySlotData.id]
if not playerInput then
return false, "no player input"
end
if itemBaseData.category == "equipment" then
if successfullyScrolled then
local desiredName = playerInput.desiredName
if not desiredName then
return false, "desired item name not provided"
end
if #desiredName > 30 then
return false, "Item name cannot be longer than 30 characters."
end
-- eliminate white space
for i,whitespace in pairs(disallowedWhiteSpace) do
if string.find(desiredName, whitespace) then
return false, "Item name cannot contain whitespace characters."
end
end
if #desiredName < 3 then
return false, "Item name must be at least 3 characters long."
end
local filteredText
local filterSuccess, filterError = pcall(function()
filteredText = game.Chat:FilterStringForBroadcast(desiredName, player)
end)
if not filterSuccess then
return false, "filter error: "..filterError
end
if not filteredText or string.find(filteredText, "#") then
return false, "Item name rejected by Roblox filter."
end
-- if we've gotten this far, we must be good
itemInventorySlotData.customName = filteredText
return true, "Item renamed"
end
end
return false, "Only equipment can be re-named."
end;
--> shop information <--
buyValue = 2000000;
sellValue = 1000;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = false;
enchantsEquipment = true;
--> sorting information <--
isImportant = true;
category = "consumable";
}
================================================
FILE: src/ReplicatedStorage/itemData/leather tunic.lua
================================================
return {
--> identifying information <--
id = 205;
--> generic information <--
name = "Leather Tunic";
rarity = "Common";
image = "rbxassetid://4054263398";
description = "A small tunic made of rough leather. Any protection is better than nothing.";
itemType = "armor";
--> equipment information <--
isEquippable = true;
equipmentSlot = 8;
equipmentType = "armor";
minLevel = 1;
--> stats information <--
modifierData = {{defense = 1;}};
--> shop information <--
buyValue = 50;
sellValue = 40;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/mana potion.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
return {
--> identifying information <--
id = 22;
--> generic information <--
name = "Blue Potion";
rarity = "Common";
image = "rbxassetid://2528903811";
description = "A magical potion";
customTag = {
{text = "Restores"; font = Enum.Font.SourceSans; textColor3 = Color3.fromRGB(160,160,160); textSize = 19; textTransparency = 0};
{text = "25 MP"; font = Enum.Font.SourceSansBold; textColor3 = Color3.fromRGB(0, 152, 255); textSize = 19; textTransparency = 0};
{text = "after"; font = Enum.Font.SourceSans; textColor3 = Color3.fromRGB(160,160,160); textSize = 19; textTransparency = 0};
{text = "1s"; font = Enum.Font.SourceSansBold; textColor3 = Color3.fromRGB(160,160,160); textSize = 19; textTransparency = 0};
};
itemType = "potion";
useSound = "potion";
--> stats information <--
activationEffect = function(player)
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.mana.Value < player.Character.PrimaryPart.maxMana.Value then
local success, reason = network:invoke("applyPotionStatusEffectToEntityManifest_server", player.Character.PrimaryPart, nil, 50, "item", 22)
return success, success and "You feel recharged." or reason
end
return false, "Character is invalid."
end;
stackSize = 32;
--> shop information <--
buyValue = 70;
sellValue = 30;
--> handling information <--
canStack = true;
canBeBound = true;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "consumable";
}
================================================
FILE: src/ReplicatedStorage/itemData/megaphone.lua
================================================
-- prevents requiring the modules outside of runtime
-- a problem for future me to deal with
--local disallowedWhiteSpace = {"\n", "\r", "\t", "\v", "\f"}
--local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
--local network = modules.load("network")
item = {
--> identifying information <--
id = 135;
--> generic information <--
name = "Megaphone";
nameColor = Color3.fromRGB(237, 98, 255);
rarity = "Legendary";
image = "rbxassetid://3109212291";
description = "Send a message to all online Vesteria players.";
-- soulbound = true;
tier = 2;
useSound = "potion";
consumeTime = 0;
playerInputFunction = function()
local playerInput = {}
playerInput.desiredName = network:invoke("textInputPrompt", {prompt = "Enter shout message..."})
return playerInput
end;
activationEffect = function(player, playerInput)
if game.ReplicatedStorage:FindFirstChild("doNotSaveData") then
return false, "No shouting in testing realms"
end
if not playerInput then
return false, "No player input recieved"
end
local desiredName = playerInput.desiredName
if not desiredName then
return false, "Desired shout not provided"
end
if #desiredName > 100 then
return false, "Shout cannot be longer than 100 characters."
end
-- eliminate white space
for i,whitespace in pairs(disallowedWhiteSpace) do
if string.find(desiredName, whitespace) then
return false, "Shout cannot contain whitespace characters."
end
end
if #desiredName < 3 then
return false, "Shout must be at least 3 characters long."
end
local filteredText
local filterSuccess, filterError = pcall(function()
filteredText = game.Chat:FilterStringForBroadcast(desiredName, player)
end)
if not filterSuccess then
return false, "filter error: "..filterError
end
if not filteredText or string.find(filteredText, "#") then
return false, "Shout rejected by Roblox filter: \""..filteredText.."\""
end
-- if we've gotten this far, we must be good
local messageSuccess, errMsg = pcall(function()
game:GetService("MessagingService"):PublishAsync("megaphone", "["..player.Name.."'s shout] " .. filteredText)
warn("MESSAGE SENT!!!")
end)
if not messageSuccess then
return false, "MessagingService error: "..errMsg
end
return true, "Shout sent!"
end;
--> shop information <--
buyValue = 2500;
sellValue = 400;
--> handling information <--
canStack = true;
canBeBound = true;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "consumable";
}
return item
================================================
FILE: src/ReplicatedStorage/itemData/mushroom beard.lua
================================================
return {
--> identifying information <--
id = 10;
--> generic information <--
name = "Elder Beard";
rarity = "Common";
image = "rbxassetid://2528901939";
description = "A beard stolen from an Elder Shroom";
--> handling information <--
canStack = true;
canBeBound = false;
canAwaken = false;
--> shop information <--
sellValue = 20;
buyValue = 100;
--> sorting information <--
isImportant = false;
category = "miscellaneous";
}
================================================
FILE: src/ReplicatedStorage/itemData/mushroom hat.lua
================================================
return {
--> identifying information <--
id = 43;
--> generic information <--
name = "Mushroom Hat";
rarity = "Common";
image = "rbxassetid://2539157198";
description = "A unique mushroom-shaped hat found in the Mushroom Forest.";
itemType = "hat";
--> equipment information <--
isEquippable = true;
equipmentSlot = 2;
equipmentType = "hat";
equipmentHairType = 2;
minLevel = 5;
--> shop information <--
buyValue = 5000;
sellValue = 1000;
--> stats information <--
modifierData = {{jump = 10;}};
statUpgrade = {
jump = 1;
};
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
clipsHair = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/mushroom mini.lua
================================================
return {
--> identifying information <--
id = 11;
--> generic information <--
name = "Red Mushroom";
rarity = "Common";
image = "rbxassetid://2528902146";
description = "A little red mushroom dropped by a felled Shroom";
--> handling information <--
canStack = true;
canBeBound = false;
canAwaken = false;
--> shop information <--
sellValue = 15;
--> sorting information <--
isImportant = false;
category = "miscellaneous";
}
================================================
FILE: src/ReplicatedStorage/itemData/mushroom soup.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
return {
--> identifying information <--
id = 131;
--> generic information <--
name = "Mushroom Soup";
rarity = "Common";
image = "rbxassetid://3164341819";
description = "A hearty bowl of delicious mushroom soup. Fully recovers all HP and MP.";
itemType = "food";
useSound = "potion";
tier=3;
--> stats information <--
activationEffect = function(player)
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.health.Value > 0 then
local success = network:invoke("applyPotionStatusEffectToEntityManifest_server", player.Character.PrimaryPart,
player.Character.PrimaryPart.maxHealth.Value - player.Character.PrimaryPart.health.Value,
player.Character.PrimaryPart.maxMana.Value - player.Character.PrimaryPart.mana.Value,
"item", 6)
return success, "You feel refreshed."
end
return false, "Character is invalid."
end;
--> shop information <--
buyValue = 50000;
sellValue = 5000;
stackSize = 8;
--> handling information <--
canStack = true;
canBeBound = true;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "consumable";
}
================================================
FILE: src/ReplicatedStorage/itemData/mushroom spore.lua
================================================
return {
--> identifying information <--
id = 9;
--> generic information <--
name = "Mushroom Spore";
rarity = "Common";
image = "rbxassetid://2538834712";
description = "A clump of spores dropped by a defeated Baby Shroom";
--> handling information <--
canStack = true;
canBeBound = false;
canAwaken = false;
--> shop information <--
sellValue = 10;
buyValue = 100;
--> sorting information <--
isImportant = false;
category = "miscellaneous";
}
================================================
FILE: src/ReplicatedStorage/itemData/oak axe.lua
================================================
return {
--> identifying information <--
id = 401;
--> generic information <--
name = "Oak Axe";
rarity = "Common";
image = "rbxassetid://5344158782";
description = "A shoddy wooden axe that still gets the job done.";
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "axe";
gripCFrame = CFrame.Angles(math.pi / 2, 0, 0) * CFrame.new(0, -0.2, 2);
minLevel = 1;
--> stats information <--
baseDamage = 2;
modifierData = {{woodcutting = 3}};
--> crafting information <--
recipe = {
-- oak wood
{id = 2, stacks = 10}
};
--> shop information <--
buyValue = 200;
sellValue = 150;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/oak pickaxe.lua
================================================
return {
--> identifying information <--
id = 501;
--> generic information <--
name = "Oak Pickaxe";
rarity = "Common";
image = "rbxassetid://5344159105";
description = "A shoddy wooden pickaxe that still gets the job done.";
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "pickaxe";
gripCFrame = CFrame.Angles(math.pi / 2, 0, 0) * CFrame.new(0, -0.2, 2);
minLevel = 1;
--> stats information <--
baseDamage = 2;
modifierData = {{mining = 3}};
--> crafting information <--
recipe = {
-- oak wood
{id = 2, stacks = 10}
};
--> shop information <--
buyValue = 200;
sellValue = 150;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/oak polearm.lua
================================================
return {
--> identifying information <--
id = 13;
--> generic information <--
name = "Oak Polearm";
rarity = "Common";
--
image = "rbxassetid://2528901964";
description = "A sturdy polearm made from great oak. Great at felling large groups of monsters.";
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "greatsword";
gripCFrame = CFrame.Angles(math.pi / 1, 0, math.pi) * CFrame.new(-.45, 0, 1.6) * CFrame.Angles(0, 0, math.pi / 2);
minLevel = 6;
--> stats information <--
baseDamage = 11;
attackSpeed = 2;
bonusStats = {};
--> shop information <--
buyValue = 1500;
sellValue = 450;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/oak wood.lua
================================================
return {
--> identifying information <--
id = 2;
--> generic information <--
name = "Oak Wood";
rarity = "Common";
image = "rbxassetid://5319820628";
description = "The oak is well-known for its sturdy wood that is used in many recipes.";
-- todo: wood item type icon
-- itemType = "wood";
--> handling information <--
canStack = true;
--> shop information <--
sellValue = 100;
buyValue = 1000;
--> sorting information <--
category = "miscellaneous";
}
================================================
FILE: src/ReplicatedStorage/itemData/pear.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
item = {
--> identifying information <--
id = 253;
--> generic information <--
name = "Pear";
rarity = "Common";
image = "rbxassetid://2661683979";
description = "A ripe pear bustling with flavor.";
itemType = "food";
useSound = "eat_food";
--> stats information <--
activationEffect = function(player)
if player.Character then
local success = network:invoke("applyPotionStatusEffectToEntityManifest_server", player.Character.PrimaryPart, 50, nil, "item", 226)
local wasApplied, reason = network:invoke("applyStatusEffectToEntityManifest", player.Character.PrimaryPart, "empower", {
duration = 60;
modifierData = {
stamina = 1;
};
}, player.Character.PrimaryPart, "item", item.id)
return wasApplied and "You feel refreshed." or "ERRORRRR"
end
return false, "Character is invalid."
end;
consumeTime = 1;
stackSize = 44;
--> shop information <--
buyValue = 500;
sellValue = 100;
--> handling information <--
canStack = true;
canBeBound = true;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "consumable";
}
return item
================================================
FILE: src/ReplicatedStorage/itemData/rune mushtown.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
return {
--> identifying information <--
id = 90;
--> generic information <--
name = "Mushtown Rune";
rarity = "Common";
image = "rbxassetid://2747737875";
description = "A magical gemstone that can be used to return to Mushtown.";
useSound = "fireIgnite";
consumeTime = 0;
--> stats information <--
askForConfirmationBeforeConsume = true;
activationEffect = function(player)
if player:FindFirstChild("teleportRune") == nil and player:FindFirstChild("teleporting") == nil and player:FindFirstChild("DataSaveFail") == nil then
local tag = Instance.new("BoolValue")
tag.Name = "teleportRune"
tag.Parent = player
if game.GameId == 712031239 then
-- demo
delay(0.2, function() network:invoke("teleportPlayer_rune", player, 4041449372) end)
else
delay(0.2, function() network:invoke("teleportPlayer_rune", player, 2064647391) end)
end
return true, "teleport queued"
end
return false, "Character is invalid."
end;
--> shop information <--
buyValue = 4000;
sellValue = 300;
--> handling information <--
canStack = true;
canBeBound = true;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "consumable";
}
================================================
FILE: src/ReplicatedStorage/itemData/rune nilgarf.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
return {
--> identifying information <--
id = 149;
--> generic information <--
name = "Nilgarf Rune";
rarity = "Common";
image = "rbxassetid://3185379989";
description = "A magical gemstone that can be used to return to Nilgarf.";
useSound = "fireIgnite";
consumeTime = 0;
--> stats information <--
askForConfirmationBeforeConsume = true;
activationEffect = function(player)
if player:FindFirstChild("teleportRune") == nil and player:FindFirstChild("teleporting") == nil and player:FindFirstChild("DataSaveFail") == nil then
local tag = Instance.new("BoolValue")
tag.Name = "teleportRune"
tag.Parent = player
if game.GameId == 712031239 then
-- demo
delay(0.2, function() network:invoke("teleportPlayer_rune", player, 4042577479) end)
else
delay(0.2, function() network:invoke("teleportPlayer_rune", player, 2119298605) end)
end
return true, "teleport queued"
end
return false, "Character is invalid."
end;
--> shop information <--
buyValue = 4000;
sellValue = 300;
--> handling information <--
canStack = true;
canBeBound = true;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "consumable";
}
================================================
FILE: src/ReplicatedStorage/itemData/rusty dagger.lua
================================================
return {
--> identifying information <--
id = 27;
--> generic information <--
name = "Rusty Dagger";
rarity = "Common";
image = "rbxassetid://2528902431";
description = "Give your enemies little tetanus kisses.";
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "dagger";
gripCFrame = CFrame.Angles(math.pi / 2, 0, 0) * CFrame.new(-0.15, 0, 0.83) * CFrame.Angles(0, math.pi, -math.pi/2);
minLevel = 6;
--> stats information <--
baseDamage = 4;
attackSpeed = 5;
modifierData = {{criticalStrikeChance = 0.08}};
--> shop information <--
buyValue = 1500;
sellValue = 600;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/shoulder pads 2.lua
================================================
return {
--> identifying information <--
id = 25;
--> generic information <--
name = "Oak Shoulder Pads";
rarity = "Common";
image = "rbxassetid://2528902858";
description = "Wooden protection that's quite effective against soft targets.";
itemType = "armor";
--> equipment information <--
isEquippable = true;
equipmentSlot = 8;
equipmentType = "armor";
minLevel = 3;
--> stats information <--
modifierData = {{defense = 2;}};
--> shop information <--
buyValue = 400;
sellValue = 200;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/shoulder pads 3.lua
================================================
return {
--> identifying information <--
id = 280;
--> generic information <--
name = "Pots and Pans";
rarity = "Common";
image = "rbxassetid://4778835694";
description = "It might not be metal plate, but you're getting there.";
itemType = "armor";
--> equipment information <--
isEquippable = true;
equipmentSlot = 8;
equipmentType = "armor";
minLevel = 5;
--> stats information <--
modifierData = {{defense = 3;}};
--> shop information <--
buyValue = 2000;
sellValue = 400;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/stick.lua
================================================
return {
--> identifying information <--
id = 5;
--> generic information <--
name = "Stick";
rarity = "Common";
image = "rbxassetid://2528902878";
description = "A particularly crisp stick.";
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "sword";
gripCFrame = CFrame.Angles(math.pi / 2, 0, 0) * CFrame.new(0, -0.2, 2);
minLevel = 1;
--> stats information <--
baseDamage = 1;
attackSpeed = 3;
bonusStats = {};
buyValue = 100;
sellValue = 20;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/stone axe.lua
================================================
return {
--> identifying information <--
id = 402;
--> generic information <--
name = "Stone Axe";
rarity = "Common";
image = "rbxassetid://5344159446";
description = "A sharp stone axe built on a sturdier oak frame.";
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "axe";
gripCFrame = CFrame.Angles(math.pi / 2, 0, 0) * CFrame.new(0, -0.2, 2);
minLevel = 3;
--> stats information <--
baseDamage = 4;
modifierData = {{woodcutting = 5}};
--> crafting information <--
recipe = {
-- oak wood
{id = 2, stacks = 20},
-- stone
{id = 600, stacks = 20}
};
--> shop information <--
buyValue = 500;
sellValue = 300;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/stone pickaxe.lua
================================================
return {
--> identifying information <--
id = 502;
--> generic information <--
name = "Stone Pickaxe";
rarity = "Common";
image = "rbxassetid://5344159226";
description = "A sharp stone pickaxe built on a sturdier oak frame.";
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "pickaxe";
gripCFrame = CFrame.Angles(math.pi / 2, 0, 0) * CFrame.new(0, -0.2, 2);
minLevel = 3;
--> stats information <--
baseDamage = 4;
modifierData = {{mining = 5}};
--> crafting information <--
recipe = {
-- oak wood
{id = 2, stacks = 20},
-- stone
{id = 600, stacks = 20}
};
--> shop information <--
buyValue = 500;
sellValue = 300;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/stone.lua
================================================
return {
--> identifying information <--
id = 600;
--> generic information <--
name = "Stone";
rarity = "Common";
image = "rbxassetid://5431762747";
description = "A chunk of rock that is used in many recipes.";
-- todo: wood item type icon
-- itemType = "wood";
--> handling information <--
canStack = true;
--> shop information <--
sellValue = 100;
buyValue = 1000;
--> sorting information <--
category = "miscellaneous";
}
================================================
FILE: src/ReplicatedStorage/itemData/wooden bow.lua
================================================
return {
--> identifying information <--
id = 83;
--> generic information <--
name = "Oak Bow";
rarity = "Common";
image = "rbxassetid://2744225022";
description = "For those who prefer to fight from afar.";
modifierData = {};
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "bow";
projectileSpeed = 170;
minLevel = 4;
--> stats information <--
baseDamage = 4;
attackSpeed = 2;
bonusStats = {};
--> shop information <--
buyValue = 900;
sellValue = 160;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/wooden club.lua
================================================
return {
--> identifying information <--
id = 7;
--> generic information <--
name = "Oak Club";
rarity = "Common";
image = "rbxassetid://2528902806";
description = "A heavy wooden club";
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "sword";
gripCFrame = CFrame.Angles(math.pi / 2, 0, 0) * CFrame.new(0, -0.2, 2);
minLevel = 2;
--> stats information <--
baseDamage = 3;
attackSpeed = 3;
bonusStats = {};
--> shop information <--
buyValue = 200;
sellValue = 150;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/wooden dagger.lua
================================================
return {
--> identifying information <--
id = 15;
--> generic information <--
name = "Oak Dagger";
rarity = "Common";
image = "rbxassetid://2528902839";
description = "A small wooden blade that can be swung very quickly.";
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "dagger";
gripCFrame = CFrame.Angles(math.pi / 2, 0, 0) * CFrame.new(-0.15, 0, 0.83) * CFrame.Angles(0, math.pi, -math.pi/2);
minLevel = 4;
--> stats information <--
baseDamage = 3;
attackSpeed = 5;
modifierData = {{criticalStrikeChance = 0.05}};
--> shop information <--
buyValue = 700;
sellValue = 160;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/wooden fishing pole.lua
================================================
return {
--> identifying information <--
id = 37;
--> generic information <--
name = "Old Fishing Rod";
rarity = "Common";
image = "rbxassetid://2544830441";
description = "An ancient rod once used to catch fish.";
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "fishingrod";
gripCFrame = CFrame.Angles(math.pi / 2, 0, 0) * CFrame.new(-0.25, 0, 2.25) * CFrame.Angles(0, 0, math.pi / 2);
gripType = 2;
minLevel = 3;
--> stats information <--
baseDamage = 26;
bonusStats = {};
--> shop information <--
buyValue = 400;
sellValue = 150;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/wooden sword.lua
================================================
return {
--> identifying information <--
id = 3;
--> generic information <--
name = "Oak Sword";
rarity = "Common";
image = "rbxassetid://2528902907";
description = "A wooden apprentice sword.";
--> equipment information <--
isEquippable = true;
equipmentSlot = 1;
equipmentType = "sword";
gripCFrame = CFrame.Angles(math.pi / 2, 0, 0) * CFrame.new(0.35, 0, 2) * CFrame.Angles(0, 0, math.pi / 2);
minLevel = 4;
--> stats information <--
baseDamage = 5;
attackSpeed = 3;
bonusStats = {};
--> shop information <--
buyValue = 800;
sellValue = 160;
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/worn boots.lua
================================================
return {
--> identifying information <--
id = 184;
--> generic information <--
name = "Worn Boots";
rarity = "Common";
image = "rbxassetid://3377146671";
description = "A pair of hand-me-down leather boots to help you run longer.";
--> equipment information <--
isEquippable = true;
equipmentSlot = 9;
equipmentType = "armour";
minLevel = 2;
valueMulti = 2.2;
--> shop information <--
buyValue = 8000;
sellValue = 1500;
--> stats information <--
modifierData = {{stamina = 1; }};
--> handling information <--
canStack = false;
canBeBound = false;
canAwaken = true;
--> sorting information <--
isImportant = false;
category = "equipment";
}
================================================
FILE: src/ReplicatedStorage/itemData/yellow puffer fish.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
return {
--> identifying information <--
id = 39;
--> generic information <--
name = "Yellow Pufferfish";
rarity = "Common";
image = "rbxassetid://2539240983";
description = "You probably don't want to eat this fish.";
itemType = "fish";
useSound = "eat_food";
--> stats information <--
activationEffect = function(player)
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.health.Value > 0 then
player.Character.PrimaryPart.health.Value = player.Character.PrimaryPart.health.Value - 500
if player.Character.PrimaryPart.health.Value <= 0 then
local text = "☠ " .. player.Name .. " ate a Yellow Pufferfish ☠"
network:fireAllClients("signal_alertChatMessage", {
Text = text,
Font = Enum.Font.SourceSansBold,
Color = Color3.fromRGB(255, 130, 100),
})
end
return true, "You feel refreshed."
end
return false, "Character is invalid."
end;
--> shop information <--
buyValue = 1000;
sellValue = 500;
--> handling information <--
canStack = true;
canBeBound = true;
canAwaken = false;
--> sorting information <--
isImportant = false;
category = "consumable";
}
================================================
FILE: src/ReplicatedStorage/loreBooks.lua
================================================
local library = {
["Mississippi's Journal"] = {
{text = "Day 1\n\nWell, we made it into the Whispering Dunes after negotiating with the bandits. Who knows how long it's been since someone from the three factions has reached this place? Even the Mages seem to have forgotten it. Put bluntly, the region is massive. Who knows if I'll ever be able to explore it all?"},
{text = "Day 5\n\nI can't say I've been sleeping particularly well. The locals call this place the Whispering Dunes because, apparently, spirits in the night will whisper their final regrets. I dismissed it as superstitious nonsense, but the nightmares I've been having... and that place. I can't get it out of my head."},
{text = "Day 22\n\nIt took some time, but I finally found it. Small groups of bandits go there at night. I wonder if their dreams are disturbed, too? It appears to have been a palace, but it's now buried in sand. According to local legend, it was once ruled by a powerful mage called Tal-rey. The nightmares won't stop."},
{text = "it must be me\n\nhaven't slept in days\nthe dreams keep coming, more and more terrible, more and more wonderful\ni must be the only one, i'm special\ni am his return\ni'm him\ni'm tal-rey\ni'm him i'm him i'm him i'm him i'm him i'm him"},
},
["Evil Journal"] = {
{text = "HAAHAAHAAA! THAT FOOL MOBEUS! WHAT A FUNNY GUY! I WAS JUST LOUNGING AT THE BEACH, SOAKING IN THE BEAUTIFUL VESTERIAN SUN RAYS WHEN I HEARD THE BOY MUMBLING TO HIMSELF! HAHAHAHA! A WHALE HUNTER HE SAID HE WANTED TO BE!"},
{text = "I TOLD THE BOY YES OF COURSE I CAN HELP YOU BECOME A WHALE HUNTER! I KNOW THE INITIATION PROCESS! JUST A SIMPLE POTION TO BECOME BIG AND STRONG, THAT'S ALL IT TAKES! HERE YOU GO MOBEUS... POOF!"},{text = "WELCOME TO THE FACTION OF WHALE HUNTERS, YOU SLIPPERY SEA MAMMAL YOU! HAVE FUN IN THE BAY SWIMMING AROUND AND DOING YOUR FUN WHALE THINGS!";
openFunc = function(util)
local playerQuests = util.network:invoke("getCacheValueByNameTag", "quests")
for i, quest in pairs(playerQuests.active) do
if quest.id == 8 then
if quest.currentObjective == 2 and quest.objectives[2].started and quest.objectives[2].steps[2].completion.amount == 0 then
util.network:invokeServer("playerRequest_readEvilJournal") -- needs to yield
util.network:fire("MobeusDialogueEnable")
end
end
end
end};
};
["Drowned Note - Crossroads"] = {
{text = "Aha! MELVIN, YOU ARE A GENIUS! Who needs a bank when you can hide all your belongings in a confusing underwater tunnel system? Any thief will surely drown! Now I just- wait a second. How do I get out of here? Which way is it?!?"}
};
["Yeti cave note"] = {
{text = "I'm alive! The yeti wasn’t able to reach me inside this hole and eventually gave up trying. Now I just have to wait for him to let his guard down... maybe I’ll take a nap to pass the time..."}
};
}
local books = {}
function books.getBook(name)
if library[name] then
return library[name]
end
return { {text = "book does not exist"} }
end
return books
================================================
FILE: src/ReplicatedStorage/modules/ability_utilities.lua
================================================
local module = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Modules = require(ReplicatedStorage.modules)
local Network = Modules.load("network")
local Utilities = Modules.load("utilities")
local abilityLookup = require(ReplicatedStorage.abilityLookup)
function module.canPlayerEquipAbility(player, playerData, abilityId)
local abilityData = abilityLookup[abilityId]
if not playerData then return false, "invalid_data" end
if not abilityData then return false, "invalid_ability" end
if playerData.abilities[abilityId] == nil then return false, "ability_locked" end
if playerData.level < abilityData.prerequisites.playerLevel then return false, "low_level" end
if abilityData.prerequisites.classRestriction == true then
if not playerData.class == abilityData.prerequisites.playerClass then return false, "wrong_class" end
end
--Check if player is in area which they can equip an ability
--Probably use region3 to achieve this ^
return true
end
function module.canPlayerCast(player, playerData, abilityId)
if not Utilities.isEntityManifestValid(player.Character.PrimaryPart) then return false, "invalid_character" end
local abilityData = abilityLookup[abilityId]
local canEquip, errorCode = module.canPlayerEquipAbility(player, playerData, abilityId)
if player.Character.PrimaryPart.mana.Value < abilityData.statistics.manaCost then return false, "lacking_mana" end
if not canEquip then return false, errorCode end
local lastCasted
if RunService:IsServer() then
lastCasted = Network:invoke("returnAbilityCooldown", player, abilityId)
else
lastCasted = Network:invokeServer("requestAbilityCooldown", abilityId)
end
if lastCasted ~= nil and (tick() - lastCasted) < abilityData.statistics.cooldown then return false, "on_cooldown" end
--Check if ability is equipped???
return true
end
function module.returnNearbyPlayers(sourceCFrame, maximumDistance)
if type(sourceCFrame) == type(CFrame) and tonumber(maximumDistance) then
local nearbyPlayers = {}
for i, player in pairs(game.Players:GetPlayers()) do
local char = player.Character
if char and char.PrimaryPart then
if (sourceCFrame.p - char.PrimaryPart.CFrame.p).magnitude < maximumDistance then
table.insert(nearbyPlayers, player)
end
end
end
if #nearbyPlayers >= 1 then return nearbyPlayers end
end
end
function module.getCastingPlayer(abilityExecutionData)
return game:GetService("Players"):GetPlayerByUserId(abilityExecutionData["cast-player-userId"])
end
function module.getAbilityStatisticsForRank(abilityBaseData, rank)
end
function module.calculateStats(playerData, abilityId)
local abilityData = abilityLookup[abilityId]
if not abilityData or not playerData then return nil end
if not playerData.abilities[abilityId] then return nil end
local increasingStat = abilityData.statistics.increasingStat
local baseStat = abilityData.statistics[increasingStat]
local exponent = abilityData.statistics.increaseExponent
local playerAbilityLevel = playerData.abilities[abilityId].level
if not increasingStat or not exponent then return nil end
local finalStatData = baseStat * (1 + (playerAbilityLevel * exponent))
return increasingStat, finalStatData
end
return module
================================================
FILE: src/ReplicatedStorage/modules/camera_shaker/CameraShakeInstance.lua
================================================
-- Camera Shake Instance
-- Crazyman32
-- February 26, 2018
--[[
cameraShakeInstance = CameraShakeInstance.new(magnitude, roughness, fadeInTime, fadeOutTime)
--]]
local CameraShakeInstance = {}
CameraShakeInstance.__index = CameraShakeInstance
local V3 = Vector3.new
local NOISE = math.noise
CameraShakeInstance.CameraShakeState = {
FadingIn = 0;
FadingOut = 1;
Sustained = 2;
Inactive = 3;
}
function CameraShakeInstance.new(magnitude, roughness, fadeInTime, fadeOutTime)
if (fadeInTime == nil) then fadeInTime = 0 end
if (fadeOutTime == nil) then fadeOutTime = 0 end
assert(type(magnitude) == "number", "Magnitude must be a number")
assert(type(roughness) == "number", "Roughness must be a number")
assert(type(fadeInTime) == "number", "FadeInTime must be a number")
assert(type(fadeOutTime) == "number", "FadeOutTime must be a number")
local self = setmetatable({
Magnitude = magnitude;
Roughness = roughness;
PositionInfluence = V3();
RotationInfluence = V3();
DeleteOnInactive = true;
roughMod = 1;
magnMod = 1;
fadeOutDuration = fadeOutTime;
fadeInDuration = fadeInTime;
sustain = (fadeInTime > 0);
currentFadeTime = (fadeInTime > 0 and 0 or 1);
tick = Random.new():NextNumber(-100, 100);
_camShakeInstance = true;
}, CameraShakeInstance)
return self
end
function CameraShakeInstance:UpdateShake(dt)
local _tick = self.tick
local currentFadeTime = self.currentFadeTime
local offset = V3(
NOISE(_tick, 0) * 0.5,
NOISE(0, _tick) * 0.5,
NOISE(_tick, _tick) * 0.5
)
if (self.fadeInDuration > 0 and self.sustain) then
if (currentFadeTime < 1) then
currentFadeTime = currentFadeTime + (dt / self.fadeInDuration)
elseif (self.fadeOutDuration > 0) then
self.sustain = false
end
end
if (not self.sustain) then
currentFadeTime = currentFadeTime - (dt / self.fadeOutDuration)
end
if (self.sustain) then
self.tick = _tick + (dt * self.Roughness * self.roughMod)
else
self.tick = _tick + (dt * self.Roughness * self.roughMod * currentFadeTime)
end
self.currentFadeTime = currentFadeTime
return offset * self.Magnitude * self.magnMod * currentFadeTime
end
function CameraShakeInstance:StartFadeOut(fadeOutTime)
if (fadeOutTime == 0) then
self.currentFadeTime = 0
end
self.fadeOutDuration = fadeOutTime
self.fadeInDuration = 0
self.sustain = false
end
function CameraShakeInstance:StartFadeIn(fadeInTime)
if (fadeInTime == 0) then
self.currentFadeTime = 1
end
self.fadeInDuration = fadeInTime or self.fadeInDuration
self.fadeOutDuration = 0
self.sustain = true
end
function CameraShakeInstance:GetScaleRoughness()
return self.roughMod
end
function CameraShakeInstance:SetScaleRoughness(v)
self.roughMod = v
end
function CameraShakeInstance:GetScaleMagnitude()
return self.magnMod
end
function CameraShakeInstance:SetScaleMagnitude(v)
self.magnMod = v
end
function CameraShakeInstance:GetNormalizedFadeTime()
return self.currentFadeTime
end
function CameraShakeInstance:IsShaking()
return (self.currentFadeTime > 0 or self.sustain)
end
function CameraShakeInstance:IsFadingOut()
return ((not self.sustain) and self.currentFadeTime > 0)
end
function CameraShakeInstance:IsFadingIn()
return (self.currentFadeTime < 1 and self.sustain and self.fadeInDuration > 0)
end
function CameraShakeInstance:GetState()
if (self:IsFadingIn()) then
return CameraShakeInstance.CameraShakeState.FadingIn
elseif (self:IsFadingOut()) then
return CameraShakeInstance.CameraShakeState.FadingOut
elseif (self:IsShaking()) then
return CameraShakeInstance.CameraShakeState.Sustained
else
return CameraShakeInstance.CameraShakeState.Inactive
end
end
return CameraShakeInstance
================================================
FILE: src/ReplicatedStorage/modules/camera_shaker/CameraShakePresets.lua
================================================
-- Camera Shake Presets
-- Crazyman32
-- February 26, 2018
--[[
CameraShakePresets.Bump
CameraShakePresets.Explosion
CameraShakePresets.Earthquake
CameraShakePresets.BadTrip
CameraShakePresets.HandheldCamera
CameraShakePresets.Vibration
CameraShakePresets.RoughDriving
--]]
local CameraShakeInstance = require(script.Parent.CameraShakeInstance)
local CameraShakePresets = {
-- A high-magnitude, short, yet smooth shake.
-- Should happen once.
Bump = function()
local c = CameraShakeInstance.new(2.5, 4, 0.1, 0.75)
c.PositionInfluence = Vector3.new(0.15, 0.15, 0.15)
c.RotationInfluence = Vector3.new(1, 1, 1)
return c
end;
-- An intense and rough shake.
-- Should happen once.
Explosion = function()
local c = CameraShakeInstance.new(5, 10, 0, 1.5)
c.PositionInfluence = Vector3.new(0.25, 0.25, 0.25)
c.RotationInfluence = Vector3.new(4, 1, 1)
return c
end;
-- A continuous, rough shake
-- Sustained.
Earthquake = function()
local c = CameraShakeInstance.new(0.6, 3.5, 2, 10)
c.PositionInfluence = Vector3.new(0.25, 0.25, 0.25)
c.RotationInfluence = Vector3.new(1, 1, 4)
return c
end;
-- A bizarre shake with a very high magnitude and low roughness.
-- Sustained.
BadTrip = function()
local c = CameraShakeInstance.new(10, 0.15, 5, 10)
c.PositionInfluence = Vector3.new(0, 0, 0.15)
c.RotationInfluence = Vector3.new(2, 1, 4)
return c
end;
-- A subtle, slow shake.
-- Sustained.
HandheldCamera = function()
local c = CameraShakeInstance.new(1, 0.25, 5, 10)
c.PositionInfluence = Vector3.new(0, 0, 0)
c.RotationInfluence = Vector3.new(1, 0.5, 0.5)
return c
end;
-- A very rough, yet low magnitude shake.
-- Sustained.
Vibration = function()
local c = CameraShakeInstance.new(0.4, 20, 2, 2)
c.PositionInfluence = Vector3.new(0, 0.15, 0)
c.RotationInfluence = Vector3.new(1.25, 0, 4)
return c
end;
-- A slightly rough, medium magnitude shake.
-- Sustained.
RoughDriving = function()
local c = CameraShakeInstance.new(1, 2, 1, 1)
c.PositionInfluence = Vector3.new(0, 0, 0)
c.RotationInfluence = Vector3.new(1, 1, 1)
return c
end;
}
return setmetatable({}, {
__index = function(t, i)
local f = CameraShakePresets[i]
if (type(f) == "function") then
return f()
end
warn("No preset found with index \"" .. i .. "\"")
end;
})
================================================
FILE: src/ReplicatedStorage/modules/camera_shaker/init.lua
================================================
-- Camera Shaker
-- Crazyman32
-- February 26, 2018
--[[
CameraShaker.CameraShakeInstance
cameraShaker = CameraShaker.new(renderPriority, callbackFunction)
CameraShaker:Start()
CameraShaker:Stop()
CameraShaker:Shake(shakeInstance)
CameraShaker:ShakeSustain(shakeInstance)
CameraShaker:ShakeOnce(magnitude, roughness [, fadeInTime, fadeOutTime, posInfluence, rotInfluence])
CameraShaker:StartShake(magnitude, roughness [, fadeInTime, posInfluence, rotInfluence])
EXAMPLE:
local camShake = CameraShaker.new(Enum.RenderPriority.Camera.Value, function(shakeCFrame)
camera.CFrame = playerCFrame * shakeCFrame
end)
camShake:Start()
-- Explosion shake:
camShake:Shake(CameraShaker.Presets.Explosion)
wait(1)
-- Custom shake:
camShake:ShakeOnce(3, 1, 0.2, 1.5)
NOTE:
This was based entirely on the EZ Camera Shake asset for Unity3D. I was given written
permission by the developer, Road Turtle Games, to port this to Roblox.
Original asset link: https://assetstore.unity.com/packages/tools/camera/ez-camera-shake-33148
--]]
local CameraShaker = {}
CameraShaker.__index = CameraShaker
local profileBegin = debug.profilebegin
local profileEnd = debug.profileend
local profileTag = "CameraShakerUpdate"
local V3 = Vector3.new
local CF = CFrame.new
local ANG = CFrame.Angles
local RAD = math.rad
local v3Zero = V3()
local CameraShakeInstance = require(script.CameraShakeInstance)
local CameraShakeState = CameraShakeInstance.CameraShakeState
local defaultPosInfluence = V3(0.15, 0.15, 0.15)
local defaultRotInfluence = V3(1, 1, 1)
CameraShaker.CameraShakeInstance = CameraShakeInstance
CameraShaker.Presets = require(script.CameraShakePresets)
function CameraShaker.new(renderPriority, callback)
assert(type(renderPriority) == "number", "RenderPriority must be a number (e.g.: Enum.RenderPriority.Camera.Value)")
assert(type(callback) == "function", "Callback must be a function")
local self = setmetatable({
_running = false;
_renderName = "CameraShaker";
_renderPriority = renderPriority;
_posAddShake = v3Zero;
_rotAddShake = v3Zero;
_camShakeInstances = {};
_removeInstances = {};
_callback = callback;
}, CameraShaker)
return self
end
function CameraShaker:Start()
if (self._running) then return end
self._running = true
local callback = self._callback
game:GetService("RunService"):BindToRenderStep(self._renderName, self._renderPriority, function(dt)
profileBegin(profileTag)
local cf = self:Update(dt)
profileEnd()
callback(cf)
end)
end
function CameraShaker:Stop()
if (not self._running) then return end
game:GetService("RunService"):UnbindFromRenderStep(self._renderName)
self._running = false
end
function CameraShaker:Update(dt)
local posAddShake = v3Zero
local rotAddShake = v3Zero
local instances = self._camShakeInstances
-- Update all instances:
for i = 1,#instances do
local c = instances[i]
local state = c:GetState()
if (state == CameraShakeState.Inactive and c.DeleteOnInactive) then
self._removeInstances[#self._removeInstances + 1] = i
elseif (state ~= CameraShakeState.Inactive) then
posAddShake = posAddShake + (c:UpdateShake(dt) * c.PositionInfluence)
rotAddShake = rotAddShake + (c:UpdateShake(dt) * c.RotationInfluence)
end
end
-- Remove dead instances:
for i = #self._removeInstances,1,-1 do
local instIndex = self._removeInstances[i]
table.remove(instances, instIndex)
self._removeInstances[i] = nil
end
return CF(posAddShake) *
ANG(0, RAD(rotAddShake.Y), 0) *
ANG(RAD(rotAddShake.X), 0, RAD(rotAddShake.Z))
end
function CameraShaker:Shake(shakeInstance)
assert(type(shakeInstance) == "table" and shakeInstance._camShakeInstance, "ShakeInstance must be of type CameraShakeInstance")
self._camShakeInstances[#self._camShakeInstances + 1] = shakeInstance
return shakeInstance
end
function CameraShaker:ShakeSustain(shakeInstance)
assert(type(shakeInstance) == "table" and shakeInstance._camShakeInstance, "ShakeInstance must be of type CameraShakeInstance")
self._camShakeInstances[#self._camShakeInstances + 1] = shakeInstance
shakeInstance:StartFadeIn(shakeInstance.fadeInDuration)
return shakeInstance
end
function CameraShaker:ShakeOnce(magnitude, roughness, fadeInTime, fadeOutTime, posInfluence, rotInfluence)
local shakeInstance = CameraShakeInstance.new(magnitude, roughness, fadeInTime, fadeOutTime)
shakeInstance.PositionInfluence = (typeof(posInfluence) == "Vector3" and posInfluence or defaultPosInfluence)
shakeInstance.RotationInfluence = (typeof(rotInfluence) == "Vector3" and rotInfluence or defaultRotInfluence)
self._camShakeInstances[#self._camShakeInstances + 1] = shakeInstance
return shakeInstance
end
function CameraShaker:StartShake(magnitude, roughness, fadeInTime, posInfluence, rotInfluence)
local shakeInstance = CameraShakeInstance.new(magnitude, roughness, fadeInTime)
shakeInstance.PositionInfluence = (typeof(posInfluence) == "Vector3" and posInfluence or defaultPosInfluence)
shakeInstance.RotationInfluence = (typeof(rotInfluence) == "Vector3" and rotInfluence or defaultRotInfluence)
shakeInstance:StartFadeIn(fadeInTime)
self._camShakeInstances[#self._camShakeInstances + 1] = shakeInstance
return shakeInstance
end
return CameraShaker
================================================
FILE: src/ReplicatedStorage/modules/client_quest_util.lua
================================================
-- to be used when writing client quest npc functionality
local replicatedStorage = game:GetService("ReplicatedStorage")
local collectionService = game:GetService("CollectionService")
local runService = game:GetService("RunService")
local questLookup = require(replicatedStorage:WaitForChild("questLookupNew"))
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local tween = modules.load("tween")
local utilities = modules.load("utilities")
local mapping = modules.load("mapping")
local itemLookup = require(game.ReplicatedStorage:WaitForChild("itemData"))
for i, questDataModule in pairs(script:GetChildren()) do
local questData = require(questDataModule)
-- internal stuff
--questData.module = questDataModule
-- hook ups
--lookupTable[questData.id] = questData
--lookupTable[questDataModule.Name] = questData
end
local module = {}
local function questState(questId)
local quests = network:invoke("getCacheValueByNameTag", "quests")
for i, playerQuestData in pairs(quests.active) do
if playerQuestData.id == questId then
local objectiveSteps = 0
local objectiveStepsDone = 0
for ii, playerStepData in pairs(playerQuestData.objectives[playerQuestData.currentObjective].steps) do
objectiveSteps = objectiveSteps + 1
if playerStepData.requirement.amount <= playerStepData.completion.amount then
objectiveStepsDone = objectiveStepsDone + 1
end
end
if objectiveStepsDone > 0 and objectiveStepsDone == objectiveSteps and playerQuestData.objectives[playerQuestData.currentObjective].started then
return mapping.questState.objectiveDone
else
if playerQuestData.objectives[playerQuestData.currentObjective].started then
return mapping.questState.active
else
return mapping.questState.unassigned
end
return mapping.questState.active
end
end
end
for i, completePlayerQuestData in pairs(quests.completed) do
if completePlayerQuestData.id == questId then
return mapping.questState.completed
end
end
return mapping.questState.unassigned
end
function module.getPlayerQuestStateByQuestId(questId)
return questState(questId)
end
local function questObjective(questId)
local quests = network:invoke("getCacheValueByNameTag", "quests")
for i, playerQuestData in pairs(quests.active) do
if playerQuestData.id == questId then
if playerQuestData.objectives[playerQuestData.currentObjective] then --.started
return playerQuestData.currentObjective
end
end
end
return -1
end
function module.getQuestObjective(questId)
return questObjective(questId)
end
local function questObjectiveAndStarted(questId)
local quests = network:invoke("getCacheValueByNameTag", "quests")
local notStartedCurrentObjective
for i, playerQuestData in pairs(quests.active) do
if playerQuestData.id == questId then
if playerQuestData.objectives[playerQuestData.currentObjective].started then --.started
return playerQuestData.currentObjective
else
return playerQuestData.currentObjective *-1
end
end
end
return -1
end
function module.getQuestObjectiveAndStarted(questId)
return questObjectiveAndStarted(questId)
end
local function levelReq(questId, currentObjective)
local questData = questLookup[questId]
if currentObjective == -1 then
currentObjective = 1
end
return questData.objectives[currentObjective].requireLevel or 1
end
function module.getQuestLevelReq(questId, currentObjective)
return levelReq(questId, currentObjective)
end
local function classReq(questId)
local questData = questLookup[questId]
return questData.requireClass
end
function module.getQuestClassReq(questId)
return classReq(questId)
end
local function reqQuests(questId)
local questData = questLookup[questId]
return { unpack(questData.requireQuests) }
end
function module.getQuestRequiredQuests(questId)
return reqQuests(questId)
end
local function repeatData(questId)
local questData = questLookup[questId]
return questData.repeatableData.value, questData.repeatableData.timeInterval
end
function module.getQuestRepeatData(questId)
return repeatData(questId)
end
function module.canPlayerInventoryObtainItem(itemId)
local inventory = network:invoke("getCacheValueByNameTag", "inventory")
local category = itemLookup[itemId].category
-- if inventory is full but player already has 1 of the item
for i, itemData in pairs(inventory) do
if itemData.id == itemId then
return true
end
end
local spacesTaken = 0
for i, itemData in pairs(inventory) do
if itemData.category == category then
spacesTaken = spacesTaken + 1
end
end
-- inv full
if spacesTaken >= 20 then -- remember to change to 20 when that update comes
return false
end
-- inv not full
return true
end
-- will go through all the checks and return true/false if player can start quest. used for npcquestmarker when the reason for not being able to start the quest doesn't matter
function module.masterCanStartQuest(questId)
local currentObjective = questObjective(questId)
local playerLevel = network:invoke("getCacheValueByNameTag", "level")
local completedQuests = network:invoke("getCacheValueByNameTag", "quests").completed
local playerClass = network:invoke("getCacheValueByNameTag", "class")
--local playerLevel, completedQuests, playerClass = network:invokeServer("playerRequest_questStartStats")
local requireLevel = levelReq(questId, currentObjective)
local requireClass = classReq(questId)
local requireQuests = reqQuests(questId)
local isRepeatable, repeatInterval = repeatData(questId)
if currentObjective == -1 then
currentObjective = 1
end
-- check level
if playerLevel < requireLevel then
return false
end
-- check class
if requireClass and requireClass ~= playerClass then
return false
end
-- check quest reqs
if #requireQuests > 0 then
for i, reqQuest in pairs(requireQuests) do
local hasQuest = false
for ii, completeQuest in pairs(completedQuests) do
if completeQuest.id == reqQuest then
hasQuest = true
end
end
if not hasQuest then
return false
end
end
end
-- is repeatable
if isRepeatable then
local activeQuests = network:invoke("getCacheValueByNameTag", "quests").active
local canStartAfterTim
for i, activeData in pairs(activeQuests) do
if activeData.id == questId and activeData.canStartAfterTime then
if activeData.canStartAfterTime > os.time() then
return false
end
end
end
end
-- special check for desert quest
if questId == 22 then
local started = false
local activeQuests = network:invoke("getCacheValueByNameTag", "quests").active
for i, quest in pairs(activeQuests) do
if quest.id == 22 then
started = false
end
end
local hasNotebook = false
if not started then
local inv = network:invoke("getCacheValueByNameTag", "inventory")
for i, item in pairs(inv) do
if item.id == 187 then
hasNotebook = true
end
end
if not hasNotebook then return false end
end
end
return true
end
return module
================================================
FILE: src/ReplicatedStorage/modules/client_utilities.lua
================================================
local module = {}
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
local placeSetup = modules.load("placeSetup")
local userInputService = game:GetService("UserInputService")
local IGNORE_LIST = {placeSetup.getPlaceFoldersFolder()}
function module.raycastFromCurrentScreenPoint(customIgnoreList, range)
range = range or 2048
local mouseLocation = userInputService:GetMouseLocation()
local cameraRay = workspace.CurrentCamera:ScreenPointToRay(mouseLocation.X, mouseLocation.Y - 36)
local ray = Ray.new(cameraRay.Origin, cameraRay.Direction.unit * range)
local hitPart, hitPosition, hitNormal, hitMaterial = workspace:FindPartOnRayWithIgnoreList(ray, customIgnoreList or IGNORE_LIST)
while hitPart and (not hitPart.CanCollide or hitPart.Transparency >= 0.95) do
table.insert(customIgnoreList or IGNORE_LIST, hitPart)
hitPart, hitPosition, hitNormal, hitMaterial = workspace:FindPartOnRayWithIgnoreList(ray, customIgnoreList or IGNORE_LIST)
end
return hitPart, hitPosition, hitNormal, hitMaterial, ray
end
local function main()
end
return module
================================================
FILE: src/ReplicatedStorage/modules/configuration.lua
================================================
local module = {}
local insertService = game:GetService("InsertService")
local httpService = game:GetService("HttpService")
local runService = game:GetService("RunService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
-- above means that modules and network cannot require configuration!
-- keep this in mind.
local rawGameConfigurationAssetId = 2553891018
local rawGameConfigurationLatestAssetVersionId
local rawGameConfiguration
local clientGameConfiguration
local serverGameConfiguration
local playerGameConfigurationCache = {}
local isRunningOnServer = runService:IsServer()
local isRunningOnClient = runService:IsClient()
local function getGameConfiguration(player)
if isRunningOnClient then warn("Unable to call `getGameConfiguration` on client") return nil end
local specificGameConfiguration = {}
for configurationName, configurationData in pairs(rawGameConfiguration) do
if configurationData.overrides then
local placeOverride = nil
local playerOverride = nil
for overrideString, overrideValue in pairs(configurationData.overrides) do
local overrideType, overrideId = string.match(overrideString, "([puid]+):(%d+)")
if overrideType == "pid" and overrideId and game.PlaceId == tonumber(overrideId) then
placeOverride = overrideValue
elseif overrideType == "uid" and overrideId and player and player.userId == tonumber(overrideId) then
playerOverride = overrideValue
end
end
if playerOverride ~= nil then
specificGameConfiguration[configurationName] = playerOverride
elseif placeOverride ~= nil then
specificGameConfiguration[configurationName] = placeOverride
else
specificGameConfiguration[configurationName] = configurationData.value
end
else
specificGameConfiguration[configurationName] = configurationData.value
end
end
return specificGameConfiguration
end
local function updateGameConfiguration()
if isRunningOnClient then warn("Unable to call `updateGameConfiguration` on client") return nil end
local newRawGameConfiguration
local success, returnValue = pcall(function()
return game:GetService("InsertService"):GetLatestAssetVersionAsync(rawGameConfigurationAssetId)
end)
if success and returnValue then
if not rawGameConfigurationLatestAssetVersionId or rawGameConfigurationLatestAssetVersionId ~= returnValue then
local success2, returnValue2 = pcall(function()
return game:GetService("InsertService"):LoadAssetVersion(returnValue)
end)
if success2 and returnValue2 and returnValue2:GetChildren()[1] then
newRawGameConfiguration = require(returnValue2:GetChildren()[1])
rawGameConfigurationLatestAssetVersionId = returnValue
else
if not rawGameConfiguration then
warn("ERR2: gameConfiguration failed to load, defaulting to embedded gameConfiguration")
newRawGameConfiguration = require(game.ServerStorage.gameConfiguration)
end
end
end
else
if not rawGameConfiguration then
warn("ERR1: gameConfiguration failed to load, defaulting to embedded gameConfiguration")
newRawGameConfiguration = require(game.ServerStorage.gameConfiguration)
end
end
-- unload gameConfiguration
if newRawGameConfiguration then
rawGameConfiguration = newRawGameConfiguration
local newPlayerGameConfigurationCache = {}
for i, player in pairs(game.Players:GetPlayers()) do
newPlayerGameConfigurationCache[player] = getGameConfiguration(player)
-- strip away all server exclusive configurations
for configurationName, configurationValue in pairs(newPlayerGameConfigurationCache[player]) do
if configurationName:sub(1, 7) == "server_" then
newPlayerGameConfigurationCache[player][configurationName] = nil
end
end
end
serverGameConfiguration = getGameConfiguration(nil)
playerGameConfigurationCache = newPlayerGameConfigurationCache
for i, player in pairs(game.Players:GetPlayers()) do
if playerGameConfigurationCache[player] then
network:fireClient("signal_playerGameConfigurationUpdated", player, playerGameConfigurationCache[player], serverGameConfiguration)
end
end
network:fire("gameConfigurationUpdated", serverGameConfiguration)
end
end
local function playerRequest_getPlayerGameConfiguration(player)
return playerGameConfigurationCache[player], serverGameConfiguration
end
function module.getConfigurationValue(configurationValueName, invokingPlayer)
if isRunningOnServer then
-- check for config to exist, and if it doesnt wait!
if not serverGameConfiguration then
while not serverGameConfiguration do
wait(0.1)
end
end
if invokingPlayer then
local resp = playerGameConfigurationCache[invokingPlayer][configurationValueName]
-- make sure serverGameConfiguration isn't nil, might be a
-- generic configuration!
if resp == nil then
return serverGameConfiguration[configurationValueName]
end
return resp
else
return serverGameConfiguration[configurationValueName]
end
elseif isRunningOnClient then
-- check for config to exist, and if it doesnt wait!
if not clientGameConfiguration and not serverGameConfiguration then
while not clientGameConfiguration and not serverGameConfiguration do
wait(0.1)
end
end
local resp = clientGameConfiguration[configurationValueName]
-- make sure serverGameConfiguration isn't nil, might be a
-- generic configuration!
if resp == nil then
return serverGameConfiguration[configurationValueName]
end
return resp
end
end
local function onPlayerAdded(player)
playerGameConfigurationCache[player] = getGameConfiguration(player)
end
local function onPlayerRemoving(player)
playerGameConfigurationCache[player] = nil
end
local function main()
if isRunningOnServer then
-- used internally to let clients update their configuration
network:create("playerRequest_getPlayerGameConfiguration", "RemoteFunction", "OnServerInvoke", playerRequest_getPlayerGameConfiguration)
network:create("signal_playerGameConfigurationUpdated", "RemoteEvent")
network:create("gameConfigurationUpdated", "BindableEvent")
updateGameConfiguration()
for i, player in pairs(game.Players:GetPlayers()) do
onPlayerAdded(player)
end
game.Players.PlayerAdded:connect(onPlayerAdded)
game.Players.PlayerRemoving:connect(onPlayerRemoving)
while wait(module.getConfigurationValue("gameConfigurationRefreshTimeInSeconds", nil)) do
updateGameConfiguration()
end
elseif isRunningOnClient then
network:create("gameConfigurationUpdated", "BindableEvent")
network:connect("signal_playerGameConfigurationUpdated", "OnClientEvent", function(newClientGameConfiguration, newServerGameConfiguration)
clientGameConfiguration = newClientGameConfiguration
serverGameConfiguration = newServerGameConfiguration
network:fire("gameConfigurationUpdated", clientGameConfiguration, serverGameConfiguration)
end)
clientGameConfiguration, serverGameConfiguration = network:invokeServer("playerRequest_getPlayerGameConfiguration")
end
end
spawn(main)
return module
================================================
FILE: src/ReplicatedStorage/modules/contains.lua
================================================
return function(t, lookfor)
for k,v in pairs(t) do
if v == lookfor then
return true, k
end
end
return false
end
================================================
FILE: src/ReplicatedStorage/modules/damage.lua
================================================
local module = {}
-- hurts but we need to stop circular dependencies
local network = require(script.Parent:WaitForChild("network"))
local utilities = require(script.Parent:WaitForChild("utilities"))
local placeSetup = require(script.Parent:WaitForChild("placeSetup"))
local entityManifestCollectionFolder = placeSetup.getPlaceFolder("entityManifestCollection")
local runService = game:GetService("RunService")
local rand = Random.new(os.time())
local function getNonSerializeData(player)
local nonSerializeData do
if runService:IsServer() and player then
local playerData = network:invoke("getPlayerData", player)
if playerData then
nonSerializeData = playerData.nonSerializeData
end
elseif runService:IsClient() then
nonSerializeData = network:invoke("getCacheValueByNameTag", "nonSerializeData")
end
end
return nonSerializeData
end
-- TODO(dnurkkala, 8/28/2019): maybe this function is worthless
-- but I'm leaving it here in case we decide to do something
-- with it. not like it does any harm just sitting here uncalled
function module.getNonHostileTargets(playerDoingDamage)
local nonHostileTargets = {}
for _, entityManifest in pairs(entityManifestCollectionFolder:GetChildren()) do
if
entityManifest:IsA("BasePart") and
entityManifest:FindFirstChild("pet") and
entityManifest:FindFirstChild("state") and
entityManifest.state.Value ~= "dead" and
entityManifest:FindFirstChild("isTargetImmune") == nil
then
table.insert(nonHostileTargets, entityManifest)
end
end
local nonSerializeData = getNonSerializeData(playerDoingDamage)
if nonSerializeData.isGlobalPVPEnabled or #nonSerializeData.whitelistPVPEnabled > 0 then
local players = nonSerializeData.isGlobalPVPEnabled and game.Players:GetPlayers() or nonSerializeData.whitelistPVPEnabled
for _, player in pairs(players) do
if
player ~= playerDoingDamage and
player:FindFirstChild("isInPVP") and
player.Character and
player.Character.PrimaryPart
then
table.insert(nonHostileTargets, player.Character.PrimaryPart)
end
end
end
-- you're always non-hostile to yourself
if playerDoingDamage.Character then
table.insert(nonHostileTargets, playerDoingDamage.Character.PrimaryPart)
end
return nonHostileTargets
end
function module.getFriendlies(playerDoingDamage)
local friendlies = {}
-- our party is friendly!
local partyData
if runService:IsClient() then
partyData = network:invokeServer("playerRequest_getMyPartyData")
elseif runService:IsServer() then
partyData = network:invoke("getPartyDataByPlayer", playerDoingDamage)
end
if partyData then
for _, partyMemberData in pairs(partyData.members) do
if
partyMemberData.player and
partyMemberData.player ~= playerDoingDamage and
partyMemberData.player.Character
then
table.insert(friendlies, partyMemberData.player.Character.PrimaryPart)
end
end
end
-- you're always friendly to yourself
if playerDoingDamage.Character then
table.insert(friendlies, playerDoingDamage.Character.PrimaryPart)
end
return friendlies
end
-- at some point, do some calculations
function module.getDamagableTargets(playerDoingDamage)
local damagableTargets = {}
for i, entityManifest in pairs(entityManifestCollectionFolder:GetChildren()) do
if entityManifest:IsA("BasePart") then
if not entityManifest:FindFirstChild("pet") and entityManifest:FindFirstChild("entityType") and entityManifest.entityType.Value == "monster" and entityManifest:FindFirstChild("state") and entityManifest.state.Value ~= "dead" and not entityManifest:FindFirstChild("isTargetImmune") then
table.insert(damagableTargets, entityManifest)
end
end
end
local nonSerializeData do
if runService:IsServer() and playerDoingDamage then
local playerData = network:invoke("getPlayerData", playerDoingDamage)
if playerData then
nonSerializeData = playerData.nonSerializeData
end
elseif runService:IsClient() then
-- dnurkkala removed this check and it looks like it won't cause issues but if
-- shit goes down then y'all can blame me I'm ready to take responsibility
-- elseif runService:IsClient() and playerDoingDamage == game.Players.LocalPlayer then
nonSerializeData = network:invoke("getCacheValueByNameTag", "nonSerializeData")
end
end
if not nonSerializeData then return damagableTargets end
if nonSerializeData.isGlobalPVPEnabled or #nonSerializeData.whitelistPVPEnabled > 0 then
local playersToScan = nonSerializeData.isGlobalPVPEnabled and game.Players:GetPlayers() or nonSerializeData.whitelistPVPEnabled
for i, playerToScan in pairs(playersToScan) do
if playerToScan ~= playerDoingDamage and playerToScan:FindFirstChild("isInPVP") and playerToScan.isInPVP.Value and playerToScan.Character and playerToScan.Character.PrimaryPart then
table.insert(damagableTargets, playerToScan.Character.PrimaryPart)
end
end
end
return damagableTargets
end
function module.canPlayerDamageTarget(playerDoingDamage, target)
if not target then return false, nil end
local damagableTargets = module.getDamagableTargets(playerDoingDamage)
local trueTarget = target do
if runService:IsClient() then
if target:IsDescendantOf(workspace.placeFolders.entityRenderCollection) then
local currentLocation = target
local clientHitboxToServerHitboxReference
while not currentLocation:FindFirstChild("clientHitboxToServerHitboxReference") and currentLocation ~= workspace.placeFolders.entityRenderCollection do
currentLocation = currentLocation.Parent
end
if currentLocation:FindFirstChild("clientHitboxToServerHitboxReference") then
trueTarget = currentLocation.clientHitboxToServerHitboxReference.Value
end
end
end
end
for i, damagableTarget in pairs(damagableTargets) do
if trueTarget == damagableTarget then
return true, trueTarget
end
end
return false, trueTarget
end
function module.getNeutrals(playerDoingDamage)
local neutrals = {}
for i, player in pairs(game.Players:GetPlayers()) do
if player.Character and player.Character.PrimaryPart then
local target = player.Character.PrimaryPart
if not module.canPlayerDamageTarget(playerDoingDamage, target) then
table.insert(neutrals, player.Character.PrimaryPart)
end
end
end
return neutrals
end;
return module
================================================
FILE: src/ReplicatedStorage/modules/debug.lua
================================================
local module = {}
local debugPlaceIds = {[2061558182] = true}
local IS_DEBUG_PLACE = debugPlaceIds[game.PlaceId]
function module.print(...)
if IS_DEBUG_PLACE then
print(...)
end
end
return module
================================================
FILE: src/ReplicatedStorage/modules/detection.lua
================================================
local module = {}
function module.boxcast_singleTarget(boundingBoxCFrame, boundingBoxSize, targetPosition)
local cf = boundingBoxCFrame:pointToObjectSpace(targetPosition) / boundingBoxSize
return
cf.X <= 0.5 and cf.X >= -0.5
and cf.Y <= 0.5 and cf.Y >= -0.5
and cf.Z <= 0.5 and cf.Z >= -0.5
end
function module.boxcast_closest(targets, hitboxCFrame, hitboxSize, point)
local closest = nil
local closestDistanceSq = math.huge
for _, target in pairs(targets) do
local targetPosition = module.projection_Box(target.CFrame, target.Size, hitboxCFrame.Position)
if module.boxcast_singleTarget(hitboxCFrame, hitboxSize, targetPosition) then
local delta = targetPosition - point
local distanceSq =
delta.X * delta.X +
delta.Y * delta.Y +
delta.Z * delta.Z
if distanceSq < closestDistanceSq then
closest = target
closestDistanceSq = distanceSq
end
end
end
return closest
end
function module.boxcast_all(targets, hitboxCFrame, hitboxSize)
local hits = {}
for _, target in pairs(targets) do
local targetPosition = module.projection_Box(target.CFrame, target.Size, hitboxCFrame.Position)
if module.boxcast_singleTarget(hitboxCFrame, hitboxSize, targetPosition) then
table.insert(hits, target)
end
end
return hits
end
local function sphereCastShow(boundingSpherePosition, boundingSphereRadius)
local p = Instance.new("Part")
p.Shape = Enum.PartType.Ball
p.Anchored = true
p.CanCollide = false
p.Size = Vector3.new(2, 2, 2) * boundingSphereRadius
p.Material = Enum.Material.Neon
p.BrickColor = BrickColor.new("Hot pink")
p.CFrame = CFrame.new(boundingSpherePosition)
p.Parent = workspace
game:GetService("Debris"):AddItem(p, 1.5)
end
function module.spherecast_singleTarget(boundingSpherePosition, boundingSphereRadius, targetPosition)
return (boundingSpherePosition - targetPosition).magnitude <= boundingSphereRadius
end
function module.projection_Box(boxCFrame, boxSize, targetPosition)
local offset = boxCFrame:pointToObjectSpace(targetPosition)
return (boxCFrame * CFrame.new(
math.clamp(offset.X, -boxSize.X / 2, boxSize.X / 2),
math.clamp(offset.Y, -boxSize.Y / 2, boxSize.Y / 2),
math.clamp(offset.Z, -boxSize.Z / 2, boxSize.Z / 2)
)).p
end
function module.projection_Sphere(spherePosition, sphereRadius, targetPosition)
return spherePosition + (targetPosition - spherePosition).unit * sphereRadius
end
return module
================================================
FILE: src/ReplicatedStorage/modules/economy.lua
================================================
local runService = game:GetService("RunService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local itemLookup = require(game.ReplicatedStorage:WaitForChild("itemData"))
local module = {}
function module.getSellValue(itemBaseData, inventorySlotData)
local baseSellValue = itemBaseData.sellValue or 0
if inventorySlotData and inventorySlotData.enchantments then
for i,enchantment in pairs(inventorySlotData.enchantments) do
local enchantmentBaseData = itemLookup[enchantment.id]
baseSellValue = baseSellValue + enchantmentBaseData.sellValue
end
end
return baseSellValue
end
return module
================================================
FILE: src/ReplicatedStorage/modules/effects.lua
================================================
-- written by Davidii
-- utility functions to help out with making cool-looking effects
local runService = game:GetService("RunService")
local module = {}
function module.onHeartbeatFor(duration, callback)
local connection
local timer = 0
local function onHeartbeat(dt)
timer = timer + dt
-- if we hit the timer ending disconnect
if (duration > 0) and (timer >= duration) then
connection:Disconnect()
end
-- ternary operation
local progress = (duration > 0) and math.min(timer / duration, 1) or 0
-- call the callback, if it returns true then we end early
if callback(dt, timer, progress) then
connection:Disconnect()
end
end
connection = runService.Heartbeat:Connect(onHeartbeat)
onHeartbeat(0)
return connection
end
function module.hideWeapons(entity)
local network = require(game.ReplicatedStorage.modules).load("network")
local weapons = network:invoke("getCurrentlyEquippedForRenderCharacter", entity)
if not weapons then return end
local restores = {}
for _, weapon in pairs(weapons) do
local manifest = weapon.manifest
if manifest:IsA("BasePart") then
table.insert(restores, {
part = manifest,
transparency = manifest.Transparency
})
manifest.Transparency = 1
end
for _, object in pairs(manifest:GetDescendants()) do
if object:IsA("BasePart") then
table.insert(restores, {
part = object,
transparency = object.Transparency
})
object.Transparency = 1
end
end
end
local function callback()
for _, restore in pairs(restores) do
restore.part.Transparency = restore.Transparency
end
end
return callback
end
return module
================================================
FILE: src/ReplicatedStorage/modules/enchantment.lua
================================================
local module = {}
local runService = game:GetService("RunService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local itemLookup = require(game.ReplicatedStorage:WaitForChild("itemData"))
module.tierColors = {
[-1] = Color3.new(0.7,0.7,0.7);
[1] = Color3.new(1,1,1);
[2] = Color3.fromRGB(112, 241, 255);
[3] = Color3.fromRGB(165, 55, 255);
[4] = Color3.fromRGB(235, 42, 87);
[5] = Color3.fromRGB(255, 238, 0);
[6] = Color3.fromRGB(0, 255, 0);
}
function module.enchantmentCanBeAppliedToItem(enchantmentSlotData, equipmentSlotData)
-- returns bool canEnchant, int enchantmentIndexToRemove
local enchantmentBaseData = itemLookup[enchantmentSlotData.id]
local equipmentBaseData = itemLookup[equipmentSlotData.id]
if equipmentSlotData.notUpgradable or equipmentBaseData.notUpgradable then
return false
end
if enchantmentBaseData.validation then
if not enchantmentBaseData.validation(equipmentBaseData, equipmentSlotData) then
-- blocked by scroll validation function
return false
end
end
local upgradeCost = enchantmentBaseData.upgradeCost or 1
local maxUpgrades = (equipmentBaseData.maxUpgrades or 0) + (equipmentSlotData.bonusUpgrades or 0)
local enchantments = equipmentSlotData.enchantments or {}
local existingUpgrades = equipmentSlotData.upgrades or 0
if existingUpgrades + upgradeCost <= maxUpgrades then
return true
--[[
else
-- allow higher-tier scrolls to override weaker upgrades
local lowestTier = 999
local index
for i,enchantment in pairs(enchantments) do
local existingEnchantmentBaseData = itemLookup[enchantment.id]
local tier = existingEnchantmentBaseData.enchantments[enchantment.state].tier or 1
if tier < lowestTier then
lowestTier = tier
index = i
end
end
if (enchantmentBaseData.tier - 1) > lowestTier then
return true, index
end
]]
end
end
-- everything below this is pretty much defunct
local costMulti = {2/3, 3/2, 3, 5, 8, 10, 15}
module.enchantmentPrice = function(itemInventorySlotData)
if not itemInventorySlotData then
return false
end
local previousEnchants = itemInventorySlotData.enchantments or 0
local itemLookup = require(game:GetService("ReplicatedStorage"):WaitForChild("itemData"))
local itemBaseData = itemLookup[itemInventorySlotData.id]
if itemInventorySlotData.upgrades and itemInventorySlotData.upgrades >= 7 then
return false
end
if not itemBaseData.buyValue then
return false
end
if itemBaseData.category == "equipment" then
return math.ceil(itemBaseData.buyValue * costMulti [(itemInventorySlotData.upgrades or 0) + 1])
end
end
module.applyEnchantment = function(itemInventorySlotData)
local itemLookup = require(game:GetService("ReplicatedStorage"):WaitForChild("itemData"))
local itemBaseData = itemLookup[itemInventorySlotData.id]
if itemInventorySlotData.upgrades and itemInventorySlotData.upgrades >= 7 then
return false
end
if itemBaseData.category == "equipment" then
if not itemInventorySlotData.modifierData then
itemInventorySlotData.modifierData = {}
end
if not itemBaseData.buyValue then
return false
end
local modifierData = itemBaseData.modifierData and itemBaseData.modifierData[1] or {}
local upgrades = {}
local doBlessItem = itemInventorySlotData.enchantments and (itemInventorySlotData.enchantments + 1) == 7
if not itemInventorySlotData.modifierData then
itemInventorySlotData.modifierData = {}
end
if itemBaseData.equipmentSlot == 1 then
local modifierData = itemBaseData.modifierData and itemBaseData.modifierData[1] or {}
local damage = (itemBaseData.baseDamage and itemBaseData.baseDamage > 0 and itemBaseData.baseDamage or 1)
+ (modifierData.rangedDamage or 0) * 0.65
+ (modifierData.magicalDamage or 0) * 0.65
+ (modifierData.physicalDamage or 0) * 0.65
local multi = 0.06
if doBlessItem and not itemBaseData.blessedUpgrade then
itemInventorySlotData.blessed = true
multi = 0.09
end
local damageBuff = math.clamp(math.floor((damage or 1) * multi), 1, math.huge)
upgrades["baseDamage"] = damageBuff;
elseif itemBaseData.equipmentSlot == 2 or itemBaseData.equipmentSlot == 8 or itemBaseData.equipmentSlot == 9 then
local modifierData = itemBaseData.modifierData and itemBaseData.modifierData[1] or {}
local baseDefense = (modifierData.defense and modifierData.defense > 0 and modifierData.defense or 1)
+ (modifierData.rangedDefense or 0) * 0.65
+ (modifierData.magicalDefense or 0) * 0.65
+ (modifierData.physicalDefense or 0) * 0.65
+ (modifierData.rangedDamage or 0) * 0.55
+ (modifierData.magicalDamage or 0) * 0.55
+ (modifierData.physicalDamage or 0) * 0.55
+ (modifierData.equipmentDamage or 0) * 0.75
local multi = 0.06
if doBlessItem and not itemBaseData.blessedUpgrade then
itemInventorySlotData.blessed = true
multi = 0.09
end
local defensebuff = math.clamp(math.floor((baseDefense or 1) * multi), 1, math.huge)
upgrades["defense"] = defensebuff;
else
return false
end
if doBlessItem and itemBaseData.blessedUpgrade then
for upgradeName, upgradeValue in pairs(itemBaseData.blessedUpgrade) do
upgrades[upgradeName] = (upgrades[upgradeName] or 0) + upgradeValue
end
itemInventorySlotData.blessed = true
end
--[[
local baseDefense = (modifierData.defense and modifierData.defense > 0 and modifierData.defense or 1)
+ (modifierData.rangedDefense or 0) * 0.65
+ (modifierData.magicalDefense or 0) * 0.65
+ (modifierData.physicalDefense or 0) * 0.65
+ (modifierData.rangedDamage or 0) * 0.55
+ (modifierData.magicalDamage or 0) * 0.55
+ (modifierData.physicalDamage or 0) * 0.55
+ (modifierData.equipmentDamage or 0) * 0.75
]]
--[[
table.insert(itemInventorySlotData.modifierData, {
defense = math.clamp(math.floor((baseDefense or 1) * 0.05), 1, math.huge)
})
]]
table.insert(itemInventorySlotData.modifierData, upgrades)
itemInventorySlotData.upgrades = (itemInventorySlotData.upgrades or 0) + 1
itemInventorySlotData.successfulUpgrades = (itemInventorySlotData.successfulUpgrades or 0) + 1
itemInventorySlotData.enchantments = (itemInventorySlotData.enchantments or 0) + 1
return itemInventorySlotData
end
return nil
end;
return module
================================================
FILE: src/ReplicatedStorage/modules/entityUtilities.lua
================================================
local module = {}
local lookupTable = {}
function module.getEntityGUIDByEntityManifest(entityManifest)
if entityManifest:IsA("Model") then
if not entityManifest.PrimaryPart then
return
end
entityManifest = entityManifest.PrimaryPart
end
local player
if entityManifest.Parent and entityManifest.Parent:IsA("Model") then
player = game.Players:GetPlayerFromCharacter(entityManifest.Parent)
end
-- this is important because 1 player will always have the same GUID, but the character
-- might change!
if player then
return player.entityGUID.Value
elseif entityManifest:FindFirstChild("entityGUID") then
return entityManifest.entityGUID.Value
end
return nil
end
function module.getEntityManifestByEntityGUID(entityGUID)
return lookupTable[entityGUID]
end
local function onEntityManifestAdded(entityManifest)
if entityManifest:IsA("Model") then
if not entityManifest.PrimaryPart then
return
end
entityManifest = entityManifest.PrimaryPart
end
local entityGUID = module.getEntityGUIDByEntityManifest(entityManifest)
if entityGUID then
lookupTable[entityGUID] = entityManifest
end
end
local function onEntityManifestRemoved(entityManifest)
if entityManifest:IsA("Model") then
if not entityManifest.PrimaryPart then
return
end
entityManifest = entityManifest.PrimaryPart
end
-- clear up references
for entityGUID, _entityManifest in pairs(lookupTable) do
if _entityManifest == entityManifest then
lookupTable[entityGUID] = nil
end
end
end
-- hookup events to stalk the entities folder
coroutine.wrap(function()
workspace:WaitForChild("placeFolders"):WaitForChild("entityManifestCollection")
for i, entityManifest in pairs(workspace.placeFolders.entityManifestCollection:GetChildren()) do
onEntityManifestAdded(entityManifest)
end
workspace.placeFolders.entityManifestCollection.ChildAdded:connect(onEntityManifestAdded)
workspace.placeFolders.entityManifestCollection.ChildRemoved:connect(onEntityManifestRemoved)
end)()
return module
================================================
FILE: src/ReplicatedStorage/modules/events.lua
================================================
local module = {}
local registrationsByGuid = {}
local registrationGuidsByEventName = {}
local httpService = game:GetService("HttpService")
local runService = game:GetService("RunService")
local modules = require(game.ReplicatedStorage.modules)
local network = modules.load("network")
function module:registerForEvent(eventName, callback)
local guid = httpService:GenerateGUID()
local registration = {
callback = callback,
guid = guid,
eventName = eventName,
}
registrationsByGuid[guid] = registration
if not registrationGuidsByEventName[eventName] then
registrationGuidsByEventName[eventName] = {}
end
table.insert(registrationGuidsByEventName[eventName], guid)
return guid
end
function module:deregisterEventByGuid(guid)
if not registrationsByGuid[guid] then return end
local eventName = registrationsByGuid[guid].eventName
registrationsByGuid[guid] = nil
local registrationGuids = registrationGuidsByEventName[eventName]
if not registrationGuids then return end
for index = #registrationGuids, 1, -1 do
local registrationGuid = registrationGuids[index]
if registrationGuid == guid then
table.remove(registrationGuids, index)
end
end
end
module.deregisterFromEvent = module.deregisterEventByGuid
function module:__invokeCallbacks(eventName, ...)
local registrationGuids = registrationGuidsByEventName[eventName]
if not registrationGuids then return end
for _, registrationGuid in pairs(registrationGuids) do
local registration = registrationsByGuid[registrationGuid]
if registration then
registration.callback(...)
end
end
end
function module:fireEventAll(eventName, ...)
self:__invokeCallbacks(eventName, ...)
local network = require(script.Parent).load("network")
if runService:IsServer() then
network:fireAllClients("fireEvent", eventName, ...)
else
network:fireServer("fireEvent", eventName, ...)
end
end
function module:fireEventLocal(eventName, ...)
self:__invokeCallbacks(eventName, ...)
end
function module:fireEventPlayer(eventName, player, ...)
if not runService:IsServer() then
error("events:fireEventPlayer can only be called from the server.")
end
self:__invokeCallbacks(eventName, player, ...)
local network = require(script.Parent).load("network")
network:fireClient("fireEvent", player, eventName, ...)
end
function module:fireEventPlayers(eventName, players, ...)
if not runService:IsServer() then
error("events:fireEventPlayers can only be called from the server.")
end
self:__invokeCallbacks(eventName, players, ...)
local network = require(script.Parent).load("network")
for _, player in pairs(players) do
network:fireClient("fireEvent", player, eventName, ...)
end
end
function module:fireEventExcluding(eventName, playerExcluded, ...)
if not runService:IsServer() then
error("events:fireEventExcluding can only be called from the server.")
end
self:__invokeCallbacks(eventName, playerExcluded, ...)
local network = require(script.Parent).load("network")
local players = game:GetService("Players"):GetPlayers()
for _, player in pairs(players) do
if player ~= playerExcluded then
network:fireClient("fireEvent", player, eventName, ...)
end
end
end
return module
================================================
FILE: src/ReplicatedStorage/modules/init.lua
================================================
local module = {}
function module.load(moduleName, doClone)
if script:FindFirstChild(moduleName) then
return
doClone and require(script[moduleName]:Clone())
or require(script[moduleName])
else
-- if we're on the client, wait for the module in case
-- it's still replicating, if it doesn't show up in a
-- reasonable amount of time, assume it was scripter
-- error and notify them as such
if game:GetService("RunService"):IsClient() then
local scr = script:WaitForChild(moduleName, 15)
if not scr then
error("Requesting module that probably doesn't exist: "..moduleName)
end
if doClone then
return require(scr:Clone())
else
return require(scr)
end
end
end
return nil
end
return module
================================================
FILE: src/ReplicatedStorage/modules/init.meta.json
================================================
{
"ignoreUnknownInstances": false
}
================================================
FILE: src/ReplicatedStorage/modules/levels.lua
================================================
local runService = game:GetService("RunService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local module = {}
-- returns the total exp needed to reach this level
function module.getEXPForLevel(level)
return 7 + 5*(level - 2) + 4*(level - 2)^2
end
function module.getTotalAP(playerData)
return playerData.level - 1
end
-- returns the exp granted from a quest at a specific level
function module.getQuestGoldFromLevel(questLevel)
return 100 + math.floor(questLevel ^ 1.18 * 300)
end
-- returns the exp needed to gain the level above the given
function module.getEXPToNextLevel(currentLevel)
return module.getEXPForLevel(currentLevel + 1)
end
-- returns the exp granted from a quest at a specific level
function module.getQuestEXPFromLevel(questLevel)
return 10 + (module.getEXPToNextLevel(questLevel) * (1/1.5) * questLevel^(-1/6))
end
function module.getMonsterEXPFromLevel(questLevel)
local killsToLevel = 7 * (1 + questLevel^1.21 - questLevel^1.1)
return module.getEXPToNextLevel(questLevel) * killsToLevel^-1
end
-- let andrew decide whatever
function module.getBaseStatInfoForMonster(monsterLevel)
monsterLevel = 0--monsterLevel or 1
return {
str = monsterLevel * 1;
int = monsterLevel * 1;
dex = monsterLevel * 1;
vit = monsterLevel * 1;
}
end
-- returns main stat and cost of equipment based on the itemBaseData
function module.getEquipmentInfo(itemBaseData)
if itemBaseData then
local level = itemBaseData.level or itemBaseData.minLevel
if level then
local stat = getStatForLevel(level)
-- local stat = 0.009*level^2 + 3*level + 12
-- me: can we get good cost algorithm
-- mom: we have good cost algorithm at home
-- cost algorithm at home:
local function sig(x)
local e = 2.7182818284590452353602874713527
return 15 / (1 + e^(-1 * (0.25 * x - 9)))
end
local cost = 1.35 * module.getQuestGoldFromLevel(level) * level^(1/3) * math.max(sig(level), 1)
local rarity = itemBaseData.rarity or "Common"
if rarity == "Legendary" then
cost = cost * 2
elseif rarity == "Rare" then
cost = cost * 1.5
end
local modifierData
if itemBaseData.equipmentSlot then
if itemBaseData.equipmentSlot == 1 then
-- weapons
local damage = stat
cost = cost * 0.7
if rarity == "Legendary" then
damage = damage + 10
elseif rarity == "Rare" then
damage = damage + 5
end
return {damage = math.ceil(damage); cost = math.floor(cost); }
elseif itemBaseData.equipmentSlot == 11 then
return {cost = math.floor(cost * 0.6)}
else
local statUpgrade
-- armor
local defense
if itemBaseData.equipmentSlot == 8 then
-- body
defense = stat
if rarity == "Legendary" then
defense = defense + 10
elseif rarity == "Rare" then
defense = defense + 5
end
cost = cost * 1
defense = defense * (itemBaseData.defenseModifier or 1)
elseif itemBaseData.equipmentSlot == 9 then
-- lower
defense = 0
cost = cost * 0.35
elseif itemBaseData.equipmentSlot == 2 then
-- hat
defense = 0
cost = cost * 0.5
local value = stat/5
local distribution = itemBaseData.statDistribution
if itemBaseData.minimumClass == "hunter" then
distribution = distribution or {
str = 0;
dex = 1;
int = 0;
vit = 0;
}
statUpgrade = {
str = 0;
dex = 1;
int = 0;
vit = 0;
}
elseif itemBaseData.minimumClass == "warrior" then
distribution = distribution or {
str = 1;
dex = 0;
int = 0;
vit = 0;
}
statUpgrade = {
str = 1;
dex = 0;
int = 0;
vit = 0;
}
elseif itemBaseData.minimumClass == "mage" then
distribution = distribution or {
str = 0;
dex = 0;
int = 1;
vit = 0;
}
statUpgrade = {
str = 0;
dex = 0;
int = 1;
vit = 0;
}
end
if distribution then
modifierData = {}
for stat, coefficient in pairs(distribution) do
modifierData[stat] = math.floor(value * coefficient)
end
end
-- modifierData = {int = math.floor(value)}
end
if defense then
return {defense = math.ceil(defense); cost = math.floor(cost); modifierData = modifierData; statUpgrade = statUpgrade}
end
return false
end
end
end
end
return false
end
function module.getPlayerTickHealing(player)
if player and player.Character and player:FindFirstChild("level") and player.Character.PrimaryPart then
local level = player.level.Value
local vit = player.vit.Value
return (0.24 * level) + (0.1 * vit)
end
return 0
end
-- warning: this is just base crit chance. Doesn't include bonus crit chance from equipment
function module.calculateCritChance(dex, playerLevel)
return 0 --math.clamp(0.4 * (dex / (3 * playerLevel)), 0, 1)
end
function module.getPlayerCritChance(player)
if runService:IsServer() then
local playerData = network:invoke("getPlayerData", player)
if playerData then
return module.calculateCritChance(
playerData.nonSerializeData.statistics_final.dex,
playerData.level
) + (playerData.nonSerializeData.statistics_final.criticalStrikeChance or 0)
end
else
warn("attempt to call getPlayerCritChance on client")
end
return 0
end
function module.getAttackSpeed(dex)
return dex * 0.5 / 2
end
function module.getMonsterGoldForLevel(level)
return 10 + (3 * (level - 1))
end
function module.getStatPointsForLevel(level)
return 3 * (level-1)
end
-- returns the level associated with the exp
function module.getLevelForEXP(exp)
return math.floor((5 * exp / 10) ^ (1 / 3))
end
-- returns how much exp you are past the exp required to earn your current level
function module.getEXPPastCurrentLevel(currentEXP)
return currentEXP
--return currentEXP - module.getEXPForLevel(math.floor(module.getLevelForEXP(currentEXP)))
end
-- returns fraction of how close you are until next level
function module.getFractionForNextLevel(currentEXP)
return module.getEXPPastCurrentLevel(currentEXP) / module.getEXPToNextLevel(math.floor(module.getLevelForEXP(currentEXP)))
end
return module
================================================
FILE: src/ReplicatedStorage/modules/levels_old.lua
================================================
local runService = game:GetService("RunService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local module = {}
-- returns the total exp needed to reach this level
function module.getBaseEXPForLevel(level)
return ((0.08/level)*level^6)/1.15
-- return 0.0444+(1/level)*level^5
-- return (10.5 * (level) ^ (3.24))
end
function module.getEXPForLevel(level)
return 70 + 100*(level-1)^1.3 + module.getBaseEXPForLevel(level) - module.getBaseEXPForLevel(level - 1)
end
local function getStatForLevel(level)
return 0.008*level^2 + 13*level^(1/2.1) + level
end
function module.getMonsterHealthForLevel(level)
return math.floor(70 + (60 * (level - 1)^1.07))
end
function module.getMonsterDefenseForLevel(level)
return getStatForLevel(level^1.02)
end
function module.getMonsterDamageForLevel(level)
return getStatForLevel(level^1.02) - 7
end
-- bounty data
module.bountyPageInfo = {
["1"] = {
{kills = 10;};
{kills = 30;};
{kills = 80;};
};
["2"] = {
{kills = 20;};
{kills = 70;};
{kills = 150;};
};
["3"] = {
{kills = 30;};
{kills = 100;};
{kills = 250;};
};
["99"] = {
{kills = 1;};
{kills = 3;};
{kills = 10;};
};
}
function module.getBountyGoldReward(bountyInfo, monster)
return bountyInfo.kills * (module.goldMulti or 1) * module.getQuestGoldFromLevel(monster.level or 1)/25
end
-- returns the exp granted from a quest at a specific level
function module.getQuestGoldFromLevel(questLevel)
return 100 + math.floor(questLevel ^ 1.18 * 300)
end
-- returns the exp needed to gain the level above the given
function module.getEXPToNextLevel(currentLevel)
return module.getEXPForLevel(currentLevel + 1)
end
-- returns the exp granted from a quest at a specific level
function module.getQuestEXPFromLevel(questLevel)
return 10 + (module.getEXPToNextLevel(questLevel) * (1/1.5) * questLevel^(-1/6))
end
-- let andrew decide whatever
function module.getBaseStatInfoForMonster(monsterLevel)
monsterLevel = 0--monsterLevel or 1
return {
str = monsterLevel * 1;
int = monsterLevel * 1;
dex = monsterLevel * 1;
vit = monsterLevel * 1;
}
end
-- returns main stat and cost of equipment based on the itemBaseData
function module.getEquipmentInfo(itemBaseData)
if itemBaseData then
local level = itemBaseData.level or itemBaseData.minLevel
if level then
local stat = getStatForLevel(level)
-- local stat = 0.009*level^2 + 3*level + 12
local cost = 1.35 * module.getQuestGoldFromLevel(level) * level^(1/3)
local modifierData
if itemBaseData.equipmentSlot then
if itemBaseData.equipmentSlot == 1 then
-- weapons
local damage = stat
cost = cost * 0.7
return {damage = math.ceil(damage); cost = math.floor(cost); }
elseif itemBaseData.equipmentSlot == 11 then
return {cost = math.floor(cost * 0.6)}
else
local upgradeStat
-- armor
local defense
if itemBaseData.equipmentSlot == 8 then
-- body
defense = stat
cost = cost * 1
if itemBaseData.noDefense == true then
defense = 0
end
elseif itemBaseData.equipmentSlot == 9 then
-- lower
defense = 0
cost = cost * 0.35
elseif itemBaseData.equipmentSlot == 2 then
-- hat
defense = 0
cost = cost * 0.5
local value = stat/5
local distribution = itemBaseData.statDistribution
if itemBaseData.minimumClass == "hunter" then
distribution = distribution or {
str = 0;
dex = 1;
int = 0;
vit = 0;
}
upgradeStat = {
str = 0;
dex = 1;
int = 0;
vit = 0;
}
elseif itemBaseData.minimumClass == "warrior" then
distribution = distribution or {
str = 1;
dex = 0;
int = 0;
vit = 0;
}
upgradeStat = {
str = 1;
dex = 0;
int = 0;
vit = 0;
}
elseif itemBaseData.minimumClass == "mage" then
distribution = distribution or {
str = 0;
dex = 0;
int = 1;
vit = 0;
}
upgradeStat = {
str = 0;
dex = 0;
int = 1;
vit = 0;
}
end
if distribution then
modifierData = {}
for stat, coefficient in pairs(distribution) do
modifierData[stat] = math.floor(value * coefficient)
end
end
-- modifierData = {int = math.floor(value)}
end
if defense then
return {defense = math.ceil(defense); cost = math.floor(cost); modifierData = modifierData; upgradeStat = upgradeStat}
end
return false
end
end
end
end
return false
end
function module.getPlayerTickHealing(player)
if player and player.Character and player:FindFirstChild("level") and player.Character.PrimaryPart then
local level = player.level.Value
local vit = player.vit.Value
return (0.24 * level) + (0.1 * vit)
end
return 0
end
-- warning: this is just base crit chance. Doesn't include bonus crit chance from equipment
function module.calculateCritChance(dex, playerLevel)
return 0 --math.clamp(0.4 * (dex / (3 * playerLevel)), 0, 1)
end
function module.getPlayerCritChance(player)
if runService:IsServer() then
local playerData = network:invoke("getPlayerData", player)
if playerData then
return module.calculateCritChance(
playerData.nonSerializeData.statistics_final.dex,
playerData.level
) + (playerData.nonSerializeData.statistics_final.criticalStrikeChance or 0)
end
else
warn("attempt to call getPlayerCritChance on client")
end
return 0
end
function module.getAttackSpeed(dex)
return dex * 0.5 / 2
end
function module.getMonsterGoldForLevel(level)
return 10 + (3 * (level - 1))
end
function module.getStatPointsForLevel(level)
return 3 * (level-1)
end
-- returns the level associated with the exp
function module.getLevelForEXP(exp)
return math.floor((5 * exp / 10) ^ (1 / 3))
end
-- returns how much exp you are past the exp required to earn your current level
function module.getEXPPastCurrentLevel(currentEXP)
return currentEXP
--return currentEXP - module.getEXPForLevel(math.floor(module.getLevelForEXP(currentEXP)))
end
-- returns fraction of how close you are until next level
function module.getFractionForNextLevel(currentEXP)
return module.getEXPPastCurrentLevel(currentEXP) / module.getEXPToNextLevel(math.floor(module.getLevelForEXP(currentEXP)))
end
-- calculates the exp gained for a monster kill
function module.getEXPGainedFromMonsterKill(baseMonsterEXP, monsterLevel, playerCurrentLevel)
-- general formula for this seems to be (baseMultiFactor * levelDifferenceFactor + minimumEXP) * totalMultiFactor
-- we should always at least grant the player 1 EXP
-- the formula below is just pulled from Pokemon, we'll customize it later
-- return ((baseMonsterEXP * monsterLevel ^ 0.9) / 8) * ((2 * monsterLevel + 10) ^ 2.5 / (monsterLevel + playerCurrentLevel + 10) ^ 2.5) + 1
local levelDifference = playerCurrentLevel - monsterLevel
local levelMulti = math.clamp(1 - levelDifference / 7, 0.25, 1.5)
return baseMonsterEXP * levelMulti
end
return module
================================================
FILE: src/ReplicatedStorage/modules/levels_older.lua
================================================
local module = {}
local runService = game:GetService("RunService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
-- returns the total exp needed to reach this level
function module.getEXPForLevel(level)
return 70 + (10 * level ^ (3.35)) - (10 * (level - 1) ^ (3.35))
end
function module.getMonsterHealthForLevel(level)
return 100 + (70 * (level - 1))
end
function module.getMonsterDamageForLevel(level)
return 8 + (12 * (level - 1))
end
function module.getPlayerTickHealing(player)
if player and player.Character and player:FindFirstChild("level") and player.Character.PrimaryPart then
local level = player.level.Value
local vit = player.vit.Value
return (0.24 * level) + (0.1 * vit)
end
return 0
end
-- warning: this is just base crit chance. Doesn't include bonus crit chance from equipment
function module.calculateCritChance(dex, playerLevel)
return math.clamp(0.4 * (dex / (3 * playerLevel)), 0, 1)
end
function module.getPlayerCritChance(player)
if runService:IsServer() then
local playerData = network:invoke("getPlayerData", player)
if playerData then
return module.calculateCritChance(
playerData.nonSerializeData.statistics_final.dex,
playerData.level
) + (playerData.nonSerializeData.statistics_final.criticalStrikeChance or 0)
end
else
warn("attempt to call getPlayerCritChance on client")
end
return 0
end
function module.getAttackSpeed(dex)
return dex * 0.5 / 2
end
function module.getMonsterGoldForLevel(level)
return 10 + (3 * (level - 1))
end
function module.getStatPointsForLevel(level)
return 3 * level
end
-- returns the level associated with the exp
function module.getLevelForEXP(exp)
return math.floor((5 * exp / 10) ^ (1 / 3))
end
-- returns the exp needed to gain the level above the given
function module.getEXPToNextLevel(currentLevel)
return module.getEXPForLevel(currentLevel + 1)
end
-- returns the exp granted from a quest at a specific level
function module.getQuestEXPFromLevel(questLevel)
return 30 + ((7 * questLevel ^ (3.35)) - (7 * (questLevel - 1) ^ (3.35)))
end
-- returns the exp granted from a quest at a specific level
function module.getQuestGoldFromLevel(questLevel)
return questLevel * 50
end
-- returns how much exp you are past the exp required to earn your current level
function module.getEXPPastCurrentLevel(currentEXP)
return currentEXP
--return currentEXP - module.getEXPForLevel(math.floor(module.getLevelForEXP(currentEXP)))
end
-- returns fraction of how close you are until next level
function module.getFractionForNextLevel(currentEXP)
return module.getEXPPastCurrentLevel(currentEXP) / module.getEXPToNextLevel(math.floor(module.getLevelForEXP(currentEXP)))
end
-- calculates the exp gained for a monster kill
function module.getEXPGainedFromMonsterKill(baseMonsterEXP, monsterLevel, playerCurrentLevel)
-- general formula for this seems to be (baseMultiFactor * levelDifferenceFactor + minimumEXP) * totalMultiFactor
-- we should always at least grant the player 1 EXP
-- the formula below is just pulled from Pokemon, we'll customize it later
-- return ((baseMonsterEXP * monsterLevel ^ 0.9) / 8) * ((2 * monsterLevel + 10) ^ 2.5 / (monsterLevel + playerCurrentLevel + 10) ^ 2.5) + 1
local levelDifference = playerCurrentLevel - monsterLevel
local levelMulti = math.clamp(1 - levelDifference / 10, 0.1, 1.5)
return baseMonsterEXP * levelMulti
end
return module
================================================
FILE: src/ReplicatedStorage/modules/localization.lua
================================================
local module = {}
local lookup = {}
lookup.color = {
b = BrickColor.Blue().Color;
}
lookup.font = {
sourceSansBold = Enum.Font.SourceSansBold;
}
local localizationService = game:GetService("LocalizationService")
-- this module is client-only, shouldnt even be in replicated storage, but I cant move it right now
if game:GetService("RunService"):IsServer() then
return module
end
-- this is a temp. translator with no yield, does not include web translations
local translator = localizationService:GetTranslatorForPlayer(game.Players.LocalPlayer)
-- fetch the web translations
spawn(function()
local success, err
local n = 0
repeat
if err then
warn("Failed to access cloud translations:", err)
wait(n * 3)
end
success, err = pcall(function()
translator = localizationService:GetTranslatorForPlayerAsync(game.Players.LocalPlayer)
end)
n = n + 1
until success
end)
function module.translate(str, context)
context = context or game
return translator:Translate(context, str) or str
end
function module.convertToVesteriaDialogueTable(str)
local dialogueTable = {}
local currentSubDialogue = {}
if str:sub(1, 1) ~= "[" then
str = "[]" .. str
end
if str:sub(#str, #str) ~= "]" then
str = str .. "[]"
end
for stylingData, text in string.gmatch(str, "([%w%;%:%=]-)%](.-)%[") do
local block = {}
block.text = text
for styleType, style in string.gmatch(stylingData, "(%w+)%=(%w+)") do
if (styleType == "font" or styleType == "f") then
block.font = lookup.font[style] or Enum.Font.SourceSans
elseif (styleType == "color" or styleType == "c") then
block.textColor3 = lookup.color[style] or Color3.fromRGB(255, 255, 255)
end
end
table.insert(dialogueTable, block)
end
return dialogueTable
end
return module
================================================
FILE: src/ReplicatedStorage/modules/mapping.lua
================================================
local module = {}
module.equipmentPosition = {}
module.equipmentPosition.weapon = 1
module.equipmentPosition.head = 2
-- unused --
module.equipmentPosition.body = 3
module.equipmentPosition["left arm"] = 4
module.equipmentPosition["right arm"] = 5
module.equipmentPosition["left leg"] = 6
module.equipmentPosition["right leg"] = 7
-- /unused --
module.equipmentPosition["upper"] = 8
module.equipmentPosition["lower"] = 9
module.equipmentPosition["pet"] = 10
module.equipmentPosition["offhand"] = 11
module.equipmentPosition["arrow"] = 12
module.dataType = {}
module.dataType.item = 1
module.dataType.ability = 2
module.dataType.abilitySlot = 3
module.gripType = {}
module.gripType.right = 1
module.gripType.left = 2
module.gripType.both = 2
module.equipmentHairType = {}
module.equipmentHairType.all = 1
module.equipmentHairType.partial = 2
module.equipmentHairType.none = 3
module.accountBanState = {}
module.accountBanState.warned = 1
module.accountBanState.day1 = 2
module.accountBanState.day3 = 3
module.accountBanState.day7 = 4
module.accountBanState.permanent = 5
module.questState = {}
module.questState.accepted = 1
module.questState.active = 2
module.questState.completed = 3
module.questState.cooldown = 4
module.questState.insufficient = 5
module.questState.denied = 6
module.questState.handing = 7
module.questState.unassigned = 8
module.questState.objectiveDone = 9
--hat, skin, undershirt, underwear, eyebrow
module.accessoryType = {}
module.accessoryType.hair = 1
module.accessoryType.skin = 2
module.accessoryType.eyebrow = 3
module.accessoryType.undershirt = 4
module.accessoryType.underwear = 5
function module.getMappingByValue(mapSection, value)
for i, v in pairs(module[mapSection]) do
if v == value then
return i
end
end
return nil
end
return module
================================================
FILE: src/ReplicatedStorage/modules/network.lua
================================================
local RunService = game:GetService("RunService")
local module = {}
local RemoteEvent = Instance.new("RemoteEvent")
local RemoteFunction = Instance.new("RemoteFunction")
local BindableEvent = Instance.new("BindableEvent")
local BindableFunction = Instance.new("BindableFunction")
local log = {}
module.log = log
local methods = {
-- camelCase method pointers
fireServer = RemoteEvent.FireServer;
fireClient = RemoteEvent.FireClient;
fireAllClients = RemoteEvent.FireAllClients;
-- invokeServer = RemoteFunction.InvokeServer;
invokeClient = RemoteFunction.InvokeClient;
fire = BindableEvent.Fire;
invoke = BindableFunction.Invoke;
-- fire for table of players
fireClients = function(obj, players, ...)
for i, player in pairs(players) do
obj:FireClient(player, ...)
end
end;
-- fire for all players but one
fireAllClientsExcludingPlayer = function(obj, excludedPlayer, ...)
for i, player in pairs(game.Players:GetPlayers()) do
if player ~= excludedPlayer then
obj:FireClient(player, ...)
end
end
end;
-- fire based on custom checker function
fireAllClientsCustomCheck = function(obj, checker, ...)
for i, player in pairs(game.Players:GetPlayers()) do
if checker(player) == true then
obj:FireClient(player, ...)
end
end
end;
-- invoke table of players and gather their responses
invokeClients = function(obj, players, ...)
local output = {}
for i, player in pairs(players) do
output[player] = obj:InvokeClient(player, ...)
end
return output
end;
-- invoke all clients and gather their responses
invokeAllClients = function(obj, ...)
local output = {}
for i, player in pairs(game.Players:GetPlayers()) do
output[player] = obj:InvokeClient(player, ...)
end
return output
end;
-- invoke all clients excluding one player and gather their responses
invokeAllClientsExcludingPlayer = function(obj, excludedPlayer, ...)
local output = {}
for i, player in pairs(game.Players:GetPlayers()) do
if player ~= excludedPlayer then
output[player] = obj:InvokeClient(player, ...)
end
end
return output
end;
-- invoke based on custom checker and gather their responses
invokeAllClientsCustomCheck = function(obj, checker, ...)
local output = {}
for i, player in pairs(game.Players:GetPlayers()) do
if checker(player) == true then
output[player] = obj:InvokeClient(player, ...)
end
end
return output
end;
}
-- creates new remote/bindable events/functions and allows immediate connection
function module:create(objName, objClass, connection, func)
local parent = script
if RunService:IsServer() then
-- if objClass == "RemoteEvent" and connection == "OnServerEvent" then
-- objClass = "BindableEvent"
-- connection = "Event"
-- parent = game.ServerStorage.serverNetwork
if objClass == "RemoteFunction" and connection == "OnServerInvoke" then
objClass = "BindableFunction"
connection = "OnInvoke"
parent = game.ServerStorage.serverNetwork
elseif objClass == "BindableEvent" then
parent = game.ServerStorage.serverNetwork
elseif objClass == "BindableFunction" then
parent = game.ServerStorage.serverNetwork
end
end
if parent:FindFirstChild(objName) == nil then
local obj = Instance.new(objClass)
obj.Name = objName
if connection and func then
if objClass == "BindableEvent" or objClass == "RemoteEvent" then
local event = obj[connection]:connect(func)
obj.Parent = parent
return obj, event
elseif objClass == "BindableFunction" or objClass == "RemoteFunction" then
obj[connection] = func
obj.Parent = parent
return obj
end
end
obj.Parent = parent
else
local obj = parent[objName]
if connection ~= nil and func ~= nil then
if objClass == "BindableEvent" or objClass == "RemoteEvent" then
local event = obj[connection]:connect(func)
obj.Parent = parent
return obj, event
elseif objClass == "BindableFunction" or objClass == "RemoteFunction" then
obj[connection] = func
obj.Parent = parent
return obj
end
end
return obj
end
end
-- connection to bindable/remote event/functions
function module:connect(objName, connection, func)
local obj
if RunService:IsServer() then
-- if connection == "OnServerEvent" then
-- connection = "Event"
-- obj = game.ServerStorage.serverNetwork:WaitForChild(objName)
if connection == "OnServerInvoke" then
connection = "OnInvoke"
obj = game.ServerStorage.serverNetwork:WaitForChild(objName)
elseif connection == "Event" or connection == "OnInvoke" then
obj = game.ServerStorage.serverNetwork:WaitForChild(objName)
else
obj = script:WaitForChild(objName)
end
else
obj = script:WaitForChild(objName)
end
if obj.ClassName == "BindableEvent" or obj.ClassName == "RemoteEvent" then
local event = obj[connection]:connect(func)
return obj, event
elseif obj.ClassName == "BindableFunction" or obj.ClassName == "RemoteFunction" then
obj[connection] = func
return obj
end
end
local function report(objName, method)
log[method] = log[method] or {}
log[method][objName] = (log[method][objName] or 0) + 1
end
function module:invokeServer(objName, ...)
report(objName, "invokeServer")
local playerRequest = game.ReplicatedStorage:WaitForChild("playerRequest")
if playerRequest then
return playerRequest:InvokeServer(objName, ...)
else
error("playerRequest not found")
end
end
--[[
function module:fireServer(objName, ...)
local signal = game.ReplicatedStorage:WaitForChild("signal")
if signal then
return signal:FireServer(objName, ...)
else
error("signal not found")
end
end
]]
local function main()
-- setup primary module metatable that hooks the methods up
setmetatable(module, {
__index = function(self, method)
return function(self, objName, ...)
local parent = script
if RunService:IsServer() and (method == "invoke" or method == "fire") then
parent = game.ServerStorage.serverNetwork
end
parent:WaitForChild(objName, 60)
report(objName, method)
-- if method == "fireClient" or method == "fireAllClients" or method == "fireServer" then
-- warn("$net",objName,method,...)
-- end
return methods[method](parent[objName], ...)
end
end;
})
end
main()
return module
================================================
FILE: src/ReplicatedStorage/modules/pathfinding.lua
================================================
local module = {}
function module.isPathValid(monster)
return true
end
function module.isBetweenPathfindingNodes(previousPosition, currentPosition, nextPosition)
local projection = (previousPosition - nextPosition):Dot((currentPosition - nextPosition))
local magnitude = (previousPosition - nextPosition).magnitude
return projection > 0 and magnitude ^ 2 > projection
end
function module.isPastNextPathfindingNodeNode(previousPosition, currentPosition, nextPosition)
local adjustPreviousPosition = previousPosition - Vector3.new(0, previousPosition.Y, 0)
local adjustCurrentPosition = currentPosition - Vector3.new(0, currentPosition.Y, 0)
local adjustNextPosition = nextPosition - Vector3.new(0, nextPosition.Y, 0)
return (adjustPreviousPosition - adjustNextPosition):Dot((adjustCurrentPosition - adjustNextPosition)) <= 2
end
function module.dropPosition(pos, IGNORE_LIST)
local ray = Ray.new(
pos + Vector3.new(0, 1, 0),
Vector3.new(0, -999, 0)
)
local hitPart, hitPosition = workspace:FindPartOnRayWithIgnoreList(ray, IGNORE_LIST)
return hitPosition
end
function module.didReachPosition(previousPosition, currentPosition, nextPosition)
return (previousPosition - currentPosition):Dot(nextPosition - currentPosition) >= 0
end
function module.adjustPathForProjectors(path, IGNORE_LIST)
local waypoints = path:GetWaypoints()
local fixWaypoints = {}
local doesNeedToJump = false
for i, pathWaypoint in pairs(waypoints) do
if pathWaypoint.Action == Enum.PathWaypointAction.Jump then
doesNeedToJump = true
end
--table.insert(fixWaypoints, PathWaypoint.new(pathWaypoint.Position, pathWaypoint.Action))
end
return waypoints
end
return module
================================================
FILE: src/ReplicatedStorage/modules/physics.lua
================================================
local module = {}
local physicsService = game:GetService("PhysicsService")
local valid = {}
-- returns a valid group id (int) or nil
function module:getCollisionGroup(name)
local ok, groupId = pcall(physicsService.GetCollisionGroupId, physicsService, name)
if not ok then
-- Create may fail if we have hit the maximum of 32 different groups
ok, groupId = pcall(physicsService.CreateCollisionGroup, physicsService, name)
end
if ok and not valid[name] then
valid[name] = true
end
return ok and groupId or nil
end
function module:setWholeCollisionGroup(obj, name)
-- if valid[name] then
if obj:IsA("BasePart") then
physicsService:SetPartCollisionGroup(obj, name)
end
for i, v in pairs(obj:GetChildren()) do
self:setWholeCollisionGroup(v, name)
end
-- else
-- error("invalid collision group")
-- end
end
function module:removeWholeCollisionGroup(obj, name)
-- if valid[name] then
if obj:IsA("BasePart") then
physicsService:RemoveCollisionGroup(obj, name)
end
for i, v in pairs(obj:GetChildren()) do
self:removeWholeCollisionGroup(v, name)
end
-- else
-- error("invalid collision group")
-- end
end
local function main()
if game:GetService("RunService"):IsClient() then
return
end
module:getCollisionGroup("passthrough")
module:getCollisionGroup("items")
module:getCollisionGroup("characters")
module:getCollisionGroup("pvpCharacters")
module:getCollisionGroup("monsters")
module:getCollisionGroup("monstersLocal")
module:getCollisionGroup("npcs")
module:getCollisionGroup("antiJumpHitbox")
module:getCollisionGroup("fishingSpots")
physicsService:CollisionGroupSetCollidable("items", "items", false)
physicsService:CollisionGroupSetCollidable("items", "characters", false)
physicsService:CollisionGroupSetCollidable("items", "monsters", false)
physicsService:CollisionGroupSetCollidable("items", "npcs", false)
physicsService:CollisionGroupSetCollidable("items", "fishingSpots", false)
physicsService:CollisionGroupSetCollidable("antiJumpHitbox", "antiJumpHitbox", true)
physicsService:CollisionGroupSetCollidable("characters", "Default", true)
physicsService:CollisionGroupSetCollidable("characters", "monsters", false)
physicsService:CollisionGroupSetCollidable("characters", "characters", true)
physicsService:CollisionGroupSetCollidable("characters", "fishingSpots", false)
physicsService:CollisionGroupSetCollidable("pvpCharacters", "Default", true)
physicsService:CollisionGroupSetCollidable("pvpCharacters", "monsters", false)
physicsService:CollisionGroupSetCollidable("pvpCharacters", "pvpCharacters", true)
physicsService:CollisionGroupSetCollidable("passthrough", "items", false)
physicsService:CollisionGroupSetCollidable("passthrough", "npcs", false)
physicsService:CollisionGroupSetCollidable("passthrough", "monsters", false)
physicsService:CollisionGroupSetCollidable("passthrough", "monstersLocal", false)
physicsService:CollisionGroupSetCollidable("passthrough", "characters", false)
end
main()
return module
================================================
FILE: src/ReplicatedStorage/modules/placeSetup.lua
================================================
local module = {}
-- wait for the server to create placeFolders
local placeFoldersFolder = workspace:WaitForChild("placeFolders", 60)
local lookupCache = {}
local runService = game:GetService("RunService")
-- waits for placeFolder by name placeFolder to be created
function module.awaitPlaceFolder(placeFolderName)
if not lookupCache[placeFolderName] then
local placeFolder = placeFoldersFolder:WaitForChild(placeFolderName)
if placeFolder then
lookupCache[placeFolderName] = placeFolder
end
end
return lookupCache[placeFolderName]
end
-- returns placeFolder placeFolderName and creates it if it does not exist
function module.getPlaceFolder(placeFolderName, doNotCreate)
if not lookupCache[placeFolderName] then
if runService:IsClient() then
module.awaitPlaceFolder(placeFolderName)
else
local placeFolder = placeFoldersFolder:FindFirstChild(placeFolderName)
if not placeFolder and not doNotCreate then
placeFolder = Instance.new("Folder", placeFoldersFolder)
placeFolder.Name = placeFolderName
end
lookupCache[placeFolderName] = placeFolder
end
end
return lookupCache[placeFolderName]
end
function module.getPlaceFoldersFolder()
return placeFoldersFolder
end
return module
================================================
FILE: src/ReplicatedStorage/modules/projectile.lua
================================================
local module = {}
local projectileQueue = {}
local projectileUpdateConnection
local runService = game:GetService("RunService")
local player = game.Players.LocalPlayer
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local configuration = modules.load("configuration")
local placeSetup = modules.load("placeSetup")
local detection = modules.load("detection")
local entitiesPlaceFolder = placeSetup.getPlaceFolder("entities")
local entityManifestCollectionFolder = placeSetup.getPlaceFolder("entityManifestCollection")
local LAST_UPDATE = tick()
local GRAVITY = Vector3.new(0, -60, 0)
module.GRAVITY = GRAVITY
function module.calculateBeamProjectile(x0, v0, t1, gMulti)
gMulti = gMulti or 1
-- calculate the bezier points
local c = 0.5*0.5*0.5;
local p3 = 0.5*GRAVITY*gMulti*t1*t1 + v0*t1 + x0;
local p2 = p3 - (GRAVITY*gMulti*t1*t1 + v0*t1)/3;
local p1 = (c*GRAVITY*gMulti*t1*t1 + 0.5*v0*t1 + x0 - c*(x0+p3))/(3*c) - p2;
-- the curve sizes
local curve0 = (p1 - x0).magnitude;
local curve1 = (p2 - p3).magnitude;
-- build the world CFrames for the attachments
local b = (x0 - p3).unit;
local r1 = (p1 - x0).unit;
local u1 = r1:Cross(b).unit;
local r2 = (p2 - p3).unit;
local u2 = r2:Cross(b).unit;
b = u1:Cross(r1).unit;
local cf1 = CFrame.new(
x0.x, x0.y, x0.z,
r1.x, u1.x, b.x,
r1.y, u1.y, b.y,
r1.z, u1.z, b.z
)
local cf2 = CFrame.new(
p3.x, p3.y, p3.z,
r2.x, u2.x, b.x,
r2.y, u2.y, b.y,
r2.z, u2.z, b.z
)
return curve0, -curve1, cf1, cf2;
end
function module.showProjectilePath(x0, v0, t, gMulti)
gMulti = gMulti or 1
local attach0 = Instance.new("Attachment", workspace.Terrain)
local attach1 = Instance.new("Attachment", workspace.Terrain)
local beam = Instance.new("Beam", workspace.Terrain)
beam.Attachment0 = attach0
beam.Attachment1 = attach1
local curve0, curve1, cf1, cf2 = module.calculateBeamProjectile(x0, v0, t, gMulti)
beam.CurveSize0 = curve0
beam.CurveSize1 = curve1
beam.Segments = 50
-- convert world space CFrames to be relative to the attachment parent
attach0.CFrame = attach0.Parent.CFrame:inverse() * cf1
attach1.CFrame = attach1.Parent.CFrame:inverse() * cf2
return beam, attach0, attach1
end
function module.updateProjectilePath(beam, attach0, attach1, x0, v0, t, gravMulti)
gravMulti = gravMulti or 1
local curve0, curve1, cf1, cf2 = module.calculateBeamProjectile(x0, v0, t, gravMulti)
beam.CurveSize0 = curve0
beam.CurveSize1 = curve1
beam.Segments = 50
-- convert world space CFrames to be relative to the attachment parent
attach0.CFrame = attach0.Parent.CFrame:inverse() * cf1
attach1.CFrame = attach1.Parent.CFrame:inverse() * cf2
end
local function raycast(ray, ignoreList)
local hitPart, hitPosition, hitNormal, hitMaterial = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
while hitPart and not hitPart.CanCollide do
ignoreList[#ignoreList + 1] = hitPart
hitPart, hitPosition, hitNormal, hitMaterial = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
end
return hitPart, hitPosition, hitNormal, hitMaterial
end
-- oh wow ;o
module.raycastForProjectile = raycast
module.raycast = raycast
local function getManifests()
local m = {}
for _, v in pairs(entityManifestCollectionFolder:GetChildren()) do
if v:IsA("BasePart") then
table.insert(m, v)
elseif v:IsA("Model") and v.PrimaryPart then
table.insert(m, v.PrimaryPart)
end
end
return m
end
local function int__updateProjectiles(step)
local currTime = LAST_UPDATE + step
local targets = getManifests()
for i, projectileData in pairs(projectileQueue) do
local t = currTime - projectileData.startTime
local nextPosition = projectileData.origin + projectileData.velocity * t + 0.5 * (GRAVITY * projectileData.projectileGravityMultipler) * t * t
local dir = nextPosition - projectileData.lastPosition
local hitPart, hitPosition, hitNormal, hitMaterial = raycast(
Ray.new(
projectileData.lastPosition,
dir + dir.unit * 0.15
), projectileData.ignoreList or {entitiesPlaceFolder, placeSetup.getPlaceFolder("items"), placeSetup.getPlaceFolder("foilage"), game.Players.LocalPlayer.Character}
)
local alterationCF
if projectileData.stepFunction then
alterationCF = projectileData.stepFunction(t)
end
if projectileData.trackerPart then
if projectileData.pointToNextPosition then
if alterationCF then
projectileData.trackerPart.CFrame = CFrame.new(hitPosition, hitPosition + dir) * alterationCF
else
projectileData.trackerPart.CFrame = CFrame.new(hitPosition, hitPosition + dir)
end
else
if alterationCF then
projectileData.trackerPart.CFrame = CFrame.new(hitPosition) * alterationCF
else
projectileData.trackerPart.CFrame = CFrame.new(hitPosition)
end
end
if configuration.getConfigurationValue("doUseTrackerPartAsHitbox", player) then
if not hitPart then
for i, manifest in pairs(targets) do
if not projectileData.reverseIgnoreList or not projectileData.reverseIgnoreList[manifest] then
local adjustPos = detection.projection_Box(manifest.CFrame, manifest.Size, projectileData.trackerPart.CFrame.p)
if detection.boxcast_singleTarget(projectileData.trackerPart.CFrame, projectileData.trackerPart.Size, adjustPos) then
hitPart = manifest
end
end
end
end
end
end
if hitPart or t > (projectileData.projectileLifetime or 3) then
table.remove(projectileQueue, i)
local clamp_t = math.clamp(t, 0, projectileData.projectileLifetime or 3)
if t <= (projectileData.projectileLifetime or 3) and (not projectileData.collisionFunction or not projectileData.collisionFunction(hitPart, hitPosition, hitNormal, hitMaterial, clamp_t)) then
-- keep it removed
elseif t <= (projectileData.projectileLifetime or 3) and projectileData.collisionFunction then
-- returned true, so it means we should ignore the part we hit
projectileData.lastPosition = nextPosition
table.insert(projectileQueue, projectileData)
table.insert(projectileData.ignoreList, hitPart)
else
projectileData.collisionFunction(hitPart, hitPosition, hitNormal, hitMaterial, clamp_t)
end
else
projectileData.lastPosition = nextPosition
end
end
if #projectileQueue == 0 and projectileUpdateConnection then
projectileUpdateConnection:disconnect()
projectileUpdateConnection = nil
end
LAST_UPDATE = tick()
end
function module.createProjectile(origin, direction, speed, trackerPart, collisionFunction, stepFunction, ignoreList, pointToNextPosition, projectileGravityMultipler, projectileLifetime)
local projectileData = {
origin = origin;
direction = direction;
speed = speed;
velocity = direction * speed;
collisionFunction = collisionFunction;
trackerPart = trackerPart;
stepFunction = stepFunction;
lastPosition = origin;
startTime = tick();
ignoreList = ignoreList;
pointToNextPosition = pointToNextPosition or false;
projectileGravityMultipler = projectileGravityMultipler or 1;
projectileLifetime = projectileLifetime or 3;
}
if projectileData.ignoreList then
projectileData.reverseIgnoreList = {}
for i,v in pairs(projectileData.ignoreList) do
projectileData.reverseIgnoreList[v] = true
end
end
table.insert(projectileQueue, projectileData)
if not projectileUpdateConnection then
LAST_UPDATE = tick()
projectileUpdateConnection = runService.Heartbeat:connect(int__updateProjectiles)
end
end
function module.createProjectileByProjectileData(projectileData)
assert(projectileData.origin, "projectileData lacking origin")
assert(projectileData.origin, "projectileData lacking direction")
assert(projectileData.origin, "projectileData lacking speed")
-- start at origin
projectileData.lastPosition = projectileData.origin
projectileData.startTime = tick()
projectileData.ignoreList = projectileData.ignoreList;
projectileData.pointToNextPosition = projectileData.pointToNextPosition or false;
projectileData.projectileGravityMultipler = projectileData.projectileGravityMultipler or 1;
projectileData.projectileLifetime = projectileData.projectileLifetime or 3;
projectileData.collisionFunction = projectileData.collisionFunction or nil;
projectileData.trackerPart = projectileData.trackerPart or nil;
projectileData.stepFunction = projectileData.stepFunction or nil;
end
function module.makeIgnoreList(additions)
local ignoreList = {
placeSetup.getPlaceFolder("entities"),
placeSetup.getPlaceFolder("spawnRegionCollections"),
placeSetup.getPlaceFolder("items"),
placeSetup.getPlaceFolder("foilage"),
}
for _, addition in pairs(additions or {}) do
table.insert(ignoreList, addition)
end
return ignoreList
end
local sqrt = math.sqrt
function module.getUnitVelocityToImpact_predictive(projecileStartPosition, projectileSpeed, targetPosition, targetVelocity, projectileGravityMultipler, projectileLifetime)
projectileLifetime = projectileLifetime or 3
projectileGravityMultipler = projectileGravityMultipler or 1
local p1x = projecileStartPosition.X
local p1z = projecileStartPosition.Z
local p2x = targetPosition.X
local p2z = targetPosition.Z
local v2x = targetVelocity.X
local v2z = targetVelocity.Z
-- 0 = at^2 + bt + c
local a = (v2x * v2x + v2z * v2z - projectileSpeed * projectileSpeed)
local b = (2*p2x*v2x - 2*p1x*v2x+ 2*p2z*v2z - 2*p1z*v2z)
local c = (p2x*p2x - 2*p2x*p1x + p1x*p1x + p2z*p2z - 2*p2z*p1z + p1z*p1z)
if b * b - 4 * a * c < 0 then
-- solution is a complex number, not real
return nil, nil
end
local t1 = (-b + math.sqrt(b * b - 4 * a * c)) / (2 * a)
local t2 = (-b - math.sqrt(b * b - 4 * a * c)) / (2 * a)
if t1 < 0 and t2 < 0 then
-- no valid solution in the future, both solutions in the past
return nil, nil
end
-- use smallest (postitive) time value
local t
if t1 > 0 and t2 < 0 then
t = t1
elseif t2 > 0 and t1 < 0 then
t = t2
elseif t1 > 0 and t2 > 0 then
t = t1 < t2 and t1 or t2
end
if t > projectileLifetime then
return nil, nil
end
-- find where we think the target will be after time `t`
local adjusted_targetPosition = targetPosition + t * targetVelocity
local unitDirection = (adjusted_targetPosition - projecileStartPosition - 0.5 * (module.GRAVITY * projectileGravityMultipler) * t^2) / (projectileSpeed * t)
return unitDirection, adjusted_targetPosition
end
-- added scalable predicivity to find a nice balance on a per mob basis
--[[function module.getUnitVelocityToImpact_slightpredictive(projecileStartPosition, projectileSpeed, targetPosition, targetVelocity, projectileGravityMultipler, predictivityMultiplier)
projectileGravityMultipler = projectileGravityMultipler or 1
local p1x = projecileStartPosition.X
local p1z = projecileStartPosition.Z
local p2x = targetPosition.X
local p2z = targetPosition.Z
local v2x = targetVelocity.X
local v2z = targetVelocity.Z
-- 0 = at^2 + bt + c
local a = (v2x * v2x + v2z * v2z - projectileSpeed * projectileSpeed)
local b = (2*p2x*v2x - 2*p1x*v2x+ 2*p2z*v2z - 2*p1z*v2z)
local c = (p2x*p2x - 2*p2x*p1x + p1x*p1x + p2z*p2z - 2*p2z*p1z + p1z*p1z)
if b * b - 4 * a * c < 0 then
-- solution is a complex number, not real
return nil, nil
end
local t1 = (-b + math.sqrt(b * b - 4 * a * c)) / (2 * a)
local t2 = (-b - math.sqrt(b * b - 4 * a * c)) / (2 * a)
if t1 < 0 and t2 < 0 then
-- no valid solution in the future, both solutions in the past
return nil, nil
end
-- find the largest time value and use it (incase one was negative, and one isn't
local t = t1 > t2 and t1 or t2
-- find where we think the target will be after time `t`
local adjusted_targetPosition = targetPosition + t * Vector3.new(targetVelocity.X * predictivityMultiplier, targetVelocity.Y * predictivityMultiplier, targetVelocity.Z * predictivityMultiplier)
local unitDirection = (adjusted_targetPosition - projecileStartPosition - 0.5 * (module.GRAVITY * projectileGravityMultipler) * t^2) / (projectileSpeed * t)
return unitDirection, adjusted_targetPosition
end]]--
function module.getTargetPositionByAbilityExecutionData(abilityExecutionData)
return
abilityExecutionData["target-casting-position"]
or abilityExecutionData["target-position"]
or abilityExecutionData["mouse-world-position"]
end
function module.getUnitVelocityToImpact_predictiveByAbilityExecutionData(projecileStartPosition, projectileSpeed, abilityExecutionData, projectileGravityMultipler)
return module.getUnitVelocityToImpact_predictive(
projecileStartPosition,
projectileSpeed,
module.getTargetPositionByAbilityExecutionData(abilityExecutionData),
(abilityExecutionData["target-casting-position"] and Vector3.new()) or abilityExecutionData["target-velocity"] or Vector3.new(),
projectileGravityMultipler
)
end
local clientCharacter
local function onCharacterAdded(character)
local IGNORE_LIST = {character; entitiesPlaceFolder}
end
local function main()
if runService:IsClient() then
while not game.Players.LocalPlayer do wait() end
player = game.Players.LocalPlayer
if player.Character then
onCharacterAdded(player.Character)
end
player.CharacterAdded:connect(onCharacterAdded)
end
end
spawn(main)
return module
================================================
FILE: src/ReplicatedStorage/modules/projectile_predictive.lua
================================================
local module = {}
local projectileQueue = {}
local projectileUpdateConnection
local runService = game:GetService("RunService")
local player = game.Players.LocalPlayer
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
local placeSetup = modules.load("placeSetup")
local utilities = modules.load("utilities")
local entitiesPlaceFolder = placeSetup.getPlaceFolder("entities")
local myClientCharacterContainer
-- how long each segment of 'raycasting' is, this number is needed
-- to ensure raycast is approximate to the real function. smaller = better
local segmentLength = 3
local GRAVITY = Vector3.new(0, 0.5 * 60, 0)
local LAST_UPDATE = tick()
local IGNORE_LIST = {}
local function raycastBetween_accomodate(startTime, origin, speed, velocity, i)
local previous_i = i - 1 > 0 and i - 1 or 0
local t = (LAST_UPDATE + segmentLength / speed * i) - startTime
local t_compare = (LAST_UPDATE + segmentLength / speed * (previous_i)) - startTime
local newPosition = origin + velocity * t - GRAVITY * t * t
local newPosition_compare = origin + velocity * t_compare - GRAVITY * t_compare * t_compare
--workspace.tracker.visualizer.CFrame = CFrame.new(newPosition)
return workspace:FindPartOnRayWithIgnoreList(
Ray.new(
newPosition_compare,
newPosition - newPosition_compare
), IGNORE_LIST
)
end
local function raycastDownIgnoreCancollideFalse(ray, ignoreList)
local hitPart, hitPosition = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
while hitPart and not hitPart.CanCollide do
ignoreList[#ignoreList + 1] = hitPart
hitPart, hitPosition = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
end
return hitPart, hitPosition
end
local function int__updateProjectiles(step)
for i, projectileData in pairs(projectileQueue) do
local distanceMoved = projectileData.velocity * step - GRAVITY * step * step
if utilities.magnitude(distanceMoved) >= segmentLength then
for i = 1, math.floor(utilities.magnitude(distanceMoved) / segmentLength) do
local hitPart, hitPosition = raycastBetween_accomodate(projectileData.startTime, projectileData.origin, projectileData.speed, projectileData.velocity, i)
if projectileData.trackerPart then
projectileData.trackerPart.CFrame = CFrame.new(hitPosition)
end
if hitPart then
projectileData.collisionFunction(hitPart)
projectileData.markForRemove = true
end
end
end
if not projectileData.markForRemove then
local i = (utilities.magnitude(distanceMoved) / segmentLength) % 1
if i > 0 then
local hitPart, hitPosition = raycastBetween_accomodate(projectileData.startTime, projectileData.origin, projectileData.speed, projectileData.velocity, i)
if projectileData.trackerPart then
projectileData.trackerPart.CFrame = CFrame.new(hitPosition)
end
if hitPart then
projectileData.collisionFunction(hitPart)
projectileData.markForRemove = true
elseif tick() - projectileData.startTime > 3 then
projectileData.collisionFunction(nil)
projectileData.markForRemove = true
end
end
end
if projectileData.markForRemove then
table.remove(projectileQueue, i)
end
end
if #projectileQueue == 0 then
projectileUpdateConnection:disconnect()
projectileUpdateConnection = nil
end
LAST_UPDATE = tick()
end
function module.createProjectile(origin, direction, speed, trackerPart, collisionFunction)
table.insert(projectileQueue, {
origin = origin;
direction = direction;
speed = speed;
velocity = direction * speed;
collisionFunction = collisionFunction;
trackerPart = trackerPart;
startTime = tick();
})
if not projectileUpdateConnection then
projectileUpdateConnection = runService.Heartbeat:connect(int__updateProjectiles)
end
end
local function onCharacterAdded(character)
myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
IGNORE_LIST = {character; myClientCharacterContainer; entitiesPlaceFolder}
end
local function main()
while not game.Players.LocalPlayer do wait() end
player = game.Players.LocalPlayer
if player.Character then
onCharacterAdded(player.Character)
end
player.CharacterAdded:connect(onCharacterAdded)
end
spawn(main)
return module
================================================
FILE: src/ReplicatedStorage/modules/tableUtil.lua
================================================
-- Table Util
-- Stephen Leitnick
-- September 13, 2017
--[[
TableUtil.Copy(Table tbl)
TableUtil.CopyShallow(Table tbl)
TableUtil.Sync(Table tbl, Table templateTbl)
TableUtil.Print(Table tbl, String label, Boolean deepPrint)
TableUtil.FastRemove(Table tbl, Number index)
TableUtil.FastRemoveFirstValue(Table tbl, Variant value)
TableUtil.Map(Table tbl, Function callback)
TableUtil.Filter(Table tbl, Function callback)
TableUtil.Reduce(Table tbl, Function callback [, Number initialValue])
TableUtil.Assign(Table target, ...Table sources)
TableUtil.IndexOf(Table tbl, Variant item)
TableUtil.Reverse(Table tbl)
TableUtil.Shuffle(Table tbl)
TableUtil.IsEmpty(Table tbl)
TableUtil.EncodeJSON(Table tbl)
TableUtil.DecodeJSON(String json)
EXAMPLES:
Copy:
Performs a deep copy of the given table. In other words,
all nested tables will also get copied.
local tbl = {"a", "b", "c"}
local tblCopy = TableUtil.Copy(tbl)
CopyShallow:
Performs a shallow copy of the given table. In other words,
all nested tables will not be copied, but only moved by
reference. Thus, a nested table in both the original and
the copy will be the same.
local tbl = {"a", "b", "c"}
local tblCopy = TableUtil.CopyShallow(tbl)
Sync:
Synchronizes a table to a template table. If the table does not have an
item that exists within the template, it gets added. If the table has
something that the template does not have, it gets removed.
local tbl1 = {kills = 0; deaths = 0; points = 0}
local tbl2 = {points = 0}
TableUtil.Sync(tbl2, tbl1) -- In words: "Synchronize table2 to table1"
print(tbl2.deaths)
Print:
Prints out the table to the output in an easy-to-read format. Good for
debugging tables. If deep printing, avoid cyclical references.
local tbl = {a = 32; b = 64; c = 128; d = {x = 0; y = 1; z = 2}}
TableUtil.Print(tbl, "My Table", true)
FastRemove:
Removes an item from an array at a given index. Only use this if you do
NOT care about the order of your array. This works by simply popping the
last item in the array and overwriting the given index with the last
item. This is O(1), compared to table.remove's O(n) speed.
local tbl = {"hello", "there", "this", "is", "a", "test"}
TableUtil.FastRemove(tbl, 2) -- Remove "there" in the array
print(table.concat(tbl, " ")) -- > hello test is a
FastRemoveFirstValue:
Calls FastRemove on the first index that holds the given value.
local tbl = {"abc", "hello", "hi", "goodbye", "hello", "hey"}
local removed, atIndex = TableUtil.FastRemoveFirstValue(tbl, "hello")
if (removed) then
print("Removed at index " .. atIndex)
print(table.concat(tbl, " ")) -- > abc hi goodbye hello hey
else
print("Did not find value")
end
Map:
This allows you to construct a new table by calling the given function
on each item in the table.
local peopleData = {
{firstName = "Bob"; lastName = "Smith"};
{firstName = "John"; lastName = "Doe"};
{firstName = "Jane"; lastName = "Doe"};
}
local people = TableUtil.Map(peopleData, function(item)
return {Name = item.firstName .. " " .. item.lastName}
end)
-- 'people' is now an array that looks like: { {Name = "Bob Smith"}; ... }
Filter:
This allows you to create a table based on the given table and a filter
function. If the function returns 'true', the item remains in the new
table; if the function returns 'false', the item is discluded from the
new table.
local people = {
{Name = "Bob Smith"; Age = 42};
{Name = "John Doe"; Age = 34};
{Name = "Jane Doe"; Age = 37};
}
local peopleUnderForty = TableUtil.Filter(people, function(item)
return item.Age < 40
end)
Reduce:
This allows you to reduce an array to a single value. Useful for quickly
summing up an array.
local tbl = {40, 32, 9, 5, 44}
local tblSum = TableUtil.Reduce(tbl, function(accumulator, value)
return accumulator + value
end)
print(tblSum) -- > 130
Assign:
This allows you to assign values from multiple tables into one. The
Assign function is very similar to JavaScript's Object.Assign() and
is useful for things such as composition-designed systems.
local function Driver()
return {
Drive = function(self) self.Speed = 10 end;
}
end
local function Teleporter()
return {
Teleport = function(self, pos) self.Position = pos end;
}
end
local function CreateCar()
local state = {
Speed = 0;
Position = Vector3.new();
}
-- Assign the Driver and Teleporter components to the car:
return TableUtil.Assign({}, Driver(), Teleporter())
end
local car = CreateCar()
car:Drive()
car:Teleport(Vector3.new(0, 10, 0))
IndexOf:
Returns the index of the given item in the table. If not found, this
will return nil.
This is the same as table.find, which Roblox added after this method
was written. To keep backwards compatibility, this method will continue
to exist, but will point directly to table.find.
local tbl = {"Hello", 32, true, "abc"}
local abcIndex = TableUtil.IndexOf(tbl, "abc") -- > 4
local helloIndex = TableUtil.IndexOf(tbl, "Hello") -- > 1
local numberIndex = TableUtil.IndexOf(tbl, 64) -- > nil
Reverse:
Creates a reversed version of the array. Note: This is a shallow
copy, so existing references will remain within the new table.
local tbl = {2, 4, 6, 8}
local rblReversed = TableUtil.Reverse(tbl) -- > {8, 6, 4, 2}
Shuffle:
Shuffles (i.e. randomizes) an array. This uses the Fisher-Yates algorithm.
local tbl = {1, 2, 3, 4, 5, 6, 7, 8, 9}
TableUtil.Shuffle(tbl)
print(table.concat(tbl, ", ")) -- e.g. > 3, 6, 9, 2, 8, 4, 1, 7, 5
--]]
local TableUtil = {}
local http = game:GetService("HttpService")
local IndexOf = table.find
local function CopyTable(t)
assert(type(t) == "table", "First argument must be a table")
local tCopy = table.create(#t)
for k,v in pairs(t) do
if (type(v) == "table") then
tCopy[k] = CopyTable(v)
else
tCopy[k] = v
end
end
return tCopy
end
local function CopyTableShallow(t)
local tCopy = table.create(#t)
for k,v in pairs(t) do tCopy[k] = v end
return tCopy
end
local function Sync(tbl, templateTbl)
assert(type(tbl) == "table", "First argument must be a table")
assert(type(templateTbl) == "table", "Second argument must be a table")
-- If 'tbl' has something 'templateTbl' doesn't, then remove it from 'tbl'
-- If 'tbl' has something of a different type than 'templateTbl', copy from 'templateTbl'
-- If 'templateTbl' has something 'tbl' doesn't, then add it to 'tbl'
for k,v in pairs(tbl) do
local vTemplate = templateTbl[k]
-- Remove keys not within template:
if (vTemplate == nil) then
tbl[k] = nil
-- Synchronize data types:
elseif (type(v) ~= type(vTemplate)) then
if (type(vTemplate) == "table") then
tbl[k] = CopyTable(vTemplate)
else
tbl[k] = vTemplate
end
-- Synchronize sub-tables:
elseif (type(v) == "table") then
Sync(v, vTemplate)
end
end
-- Add any missing keys:
for k,vTemplate in pairs(templateTbl) do
local v = tbl[k]
if (v == nil) then
if (type(vTemplate) == "table") then
tbl[k] = CopyTable(vTemplate)
else
tbl[k] = vTemplate
end
end
end
end
local function FastRemove(t, i)
local n = #t
t[i] = t[n]
t[n] = nil
end
local function Map(t, f)
assert(type(t) == "table", "First argument must be a table")
assert(type(f) == "function", "Second argument must be an array")
local newT = table.create(#t)
for k,v in pairs(t) do
newT[k] = f(v, k, t)
end
return newT
end
local function Filter(t, f)
assert(type(t) == "table", "First argument must be a table")
assert(type(f) == "function", "Second argument must be an array")
local newT = table.create(#t)
if (#t > 0) then
local n = 0
for i = 1,#t do
local v = t[i]
if (f(v, i, t)) then
n = (n + 1)
newT[n] = v
end
end
else
for k,v in pairs(t) do
if (f(v, k, t)) then
newT[k] = v
end
end
end
return newT
end
local function Reduce(t, f, init)
assert(type(t) == "table", "First argument must be a table")
assert(type(f) == "function", "Second argument must be an array")
assert(init == nil or type(init) == "number", "Third argument must be a number or nil")
local result = (init or 0)
for k,v in pairs(t) do
result = f(result, v, k, t)
end
return result
end
-- tableUtil.Assign(Table target, ...Table sources)
local function Assign(target, ...)
for _,src in ipairs({...}) do
for k,v in pairs(src) do
target[k] = v
end
end
return target
end
local function Print(tbl, label, deepPrint)
assert(type(tbl) == "table", "First argument must be a table")
assert(label == nil or type(label) == "string", "Second argument must be a string or nil")
label = (label or "TABLE")
local strTbl = {}
local indent = " - "
-- Insert(string, indentLevel)
local function Insert(s, l)
strTbl[#strTbl + 1] = (indent:rep(l) .. s .. "\n")
end
local function AlphaKeySort(a, b)
return (tostring(a.k) < tostring(b.k))
end
local function PrintTable(t, lvl, lbl)
Insert(lbl .. ":", lvl - 1)
local nonTbls = {}
local tbls = {}
local keySpaces = 0
for k,v in pairs(t) do
if (type(v) == "table") then
table.insert(tbls, {k = k, v = v})
else
table.insert(nonTbls, {k = k, v = "[" .. typeof(v) .. "] " .. tostring(v)})
end
local spaces = #tostring(k) + 1
if (spaces > keySpaces) then
keySpaces = spaces
end
end
table.sort(nonTbls, AlphaKeySort)
table.sort(tbls, AlphaKeySort)
for _,v in ipairs(nonTbls) do
Insert(tostring(v.k) .. ":" .. (" "):rep(keySpaces - #tostring(v.k)) .. v.v, lvl)
end
if (deepPrint) then
for _,v in ipairs(tbls) do
PrintTable(v.v, lvl + 1, tostring(v.k) .. (" "):rep(keySpaces - #tostring(v.k)) .. " [Table]")
end
else
for _,v in ipairs(tbls) do
Insert(tostring(v.k) .. ":" .. (" "):rep(keySpaces - #tostring(v.k)) .. "[Table]", lvl)
end
end
end
PrintTable(tbl, 1, label)
print(table.concat(strTbl, ""))
end
local function Reverse(tbl)
local n = #tbl
local tblRev = table.create(n)
for i = 1,n do
tblRev[i] = tbl[n - i + 1]
end
return tblRev
end
local function Shuffle(tbl)
assert(type(tbl) == "table", "First argument must be a table")
local rng = Random.new()
for i = #tbl, 2, -1 do
local j = rng:NextInteger(1, i)
tbl[i], tbl[j] = tbl[j], tbl[i]
end
end
local function IsEmpty(tbl)
return (next(tbl) == nil)
end
local function EncodeJSON(tbl)
return http:JSONEncode(tbl)
end
local function DecodeJSON(str)
return http:JSONDecode(str)
end
local function FastRemoveFirstValue(t, v)
local index = IndexOf(t, v)
if (index) then
FastRemove(t, index)
return true, index
end
return false, nil
end
TableUtil.Copy = CopyTable
TableUtil.CopyShallow = CopyTableShallow
TableUtil.Sync = Sync
TableUtil.FastRemove = FastRemove
TableUtil.FastRemoveFirstValue = FastRemoveFirstValue
TableUtil.Print = Print
TableUtil.Map = Map
TableUtil.Filter = Filter
TableUtil.Reduce = Reduce
TableUtil.Assign = Assign
TableUtil.IndexOf = IndexOf
TableUtil.Reverse = Reverse
TableUtil.Shuffle = Shuffle
TableUtil.IsEmpty = IsEmpty
TableUtil.EncodeJSON = EncodeJSON
TableUtil.DecodeJSON = DecodeJSON
return TableUtil
================================================
FILE: src/ReplicatedStorage/modules/tempData.lua
================================================
local module = {
data = {}
}
function module:set(player, key, val)
if not self.data[player] then
self.data[player] = {}
end
self.data[player][key] = val
end
function module:get(player, key)
if self.data[player] then
return self.data[player][key]
end
end
function module:delete(player, key)
if not self.data[player] then return end
self.data[player][key] = nil
end
return module
================================================
FILE: src/ReplicatedStorage/modules/terrainUtil.lua
================================================
-- kyle emmerich
local terrainUtil = {}
local VOXEL_SIZE = Vector3.new(4, 4, 4)
local TERRAIN = workspace.Terrain
function terrainUtil.getTerrainAt(pos)
local cellPos = TERRAIN:WorldToCell(pos)
local regionCorner = TERRAIN:CellCornerToWorld(cellPos)
local region = Region3.new(regionCorner, regionCorner + VOXEL_SIZE)
local material, occupancy = TERRAIN:ReadVoxels(region, 4)
return material[1][1][1], occupancy[1][1][1]
end
local WATER = Enum.Material.Water
local AIR = Enum.Material.Air
function terrainUtil.isPointUnderwater(point)
local cellPos = TERRAIN:WorldToCell(point)
local regionCorner = TERRAIN:CellCornerToWorld(cellPos.X, cellPos.Y, cellPos.Z)
local region = Region3.new(regionCorner, regionCorner + Vector3.new(4, 8, 4))
local material, occupancy = TERRAIN:ReadVoxels(region, 4)
local material0, occupancy0 = material[1][1][1], occupancy[1][1][1] -- cell containing point
local material1, occupancy1 = material[1][2][1], occupancy[1][2][1] -- cell above
if material0 == WATER then
-- point is in water cell and cell above is not air => we're either completely in water or at the water-solid boundary
-- so we can safely assume underwater
if material1 ~= AIR then
-- Can overestimate water level to be at top of cell
return true, cellPos.Y * VOXEL_SIZE.Y, false
end
-- cell above is air => have to estimate the plane based on occupancy
local waterHeightCell = occupancy0
-- cell clamping from mesher
local waterLevelCell = cellPos.Y + 0.5 + math.max(0, waterHeightCell - 0.5)
return point.Y / 4 < waterLevelCell, waterLevelCell * VOXEL_SIZE.Y, true
elseif material1 == WATER then
-- point is in solid cell and cell above is water => we're at the water-solid boundary
-- so we can safely assume underwater
if material0 ~= AIR then
-- Can overestimate water level to be at top of cell
-- Adding plus 2 because cell0 is already + 1 above posY
return true, (cellPos.Y + 1) * VOXEL_SIZE.Y, false
end
-- point is in air => have to estimate the plane based on occupancy
local waterHeightCell = occupancy1
-- cell clamping from mesher
local waterLevelCell = (cellPos.Y + 1) - math.min(waterHeightCell, 0.5)
return point.Y / 4 > waterLevelCell, waterLevelCell * VOXEL_SIZE.Y, true
end
return false, 0, false
end
return terrainUtil
================================================
FILE: src/ReplicatedStorage/modules/thread.lua
================================================
-- Thread
-- Stephen Leitnick
-- January 5, 2020
--[[
Thread.SpawnNow(func, ...)
Thread.Spawn(func, ...)
Thread.Delay(waitTime, func, ...)
SpawnNow(Function func, Arguments...)
> Uses a BindableEvent to spawn a new thread
immediately. More performance-intensive than
using Thread.Spawn, but will guarantee a
thread is started immediately.
> Use this only if the thread must be executed
right away, otherwise use Thread.Spawn for
the sake of performance.
Spawn(Function func, Arguments...)
> Uses RunService's Heartbeat to spawn a new
thread on the next heartbeat and then
call the given function.
> Better performance than Thread.SpawnNow, but
will have a short delay of 1 frame before
calling the function.
Delay(Number waitTime, Function func, Arguments...)
> The same as Thread.Spawn, but waits to call
the function until the in-game time as elapsed
by 'waitTime' amount.
> Returns the connection to the Heartbeat event,
so the delay can be cancelled by disconnecting
the returned connection.
DelayRepeat(Number intervalTime, Function func, Arguments...)
> The same as Thread.Delay, except it repeats
indefinitely.
> Returns the Heartbeat connection, thus the
repeated delay can be stopped by disconnecting
the returned connection.
> Properly bound to the time interval, thus will
not experience drift.
Examples:
Thread.Spawn(function()
print("Hello from Spawn")
end)
Thread.Delay(1, function()
print("Hello from Delay")
end)
Thread.SpawnNow(function()
print("Hello from SpawnNow")
end)
local delayConnection = Thread.Delay(5, function()
print("Hello?")
end)
delayConnection:Disconnect()
local repeatConnection = Thread.DelayRepeat(1, function()
print("Hello again", tick())
end)
wait(5)
repeatConnection:Disconnect()
Why:
The built-in 'spawn' and 'delay' functions have the
potential to be throttled unknowingly. This can cause
all sorts of problems. Developers need to be certain
when their code is going to run. This small library
helps give the same functionality as 'spawn' and 'delay'
but with the expected behavior.
Why not coroutines:
Coroutines are powerful, but can be extremely difficult
to debug due to the ways that coroutines obscure the
stack trace.
Credit:
evaera & buildthomas: https://devforum.roblox.com/t/coroutines-v-s-spawn-which-one-should-i-use/368966
Quenty: FastSpawn (AKA SpawnNow) method using BindableEvent
--]]
local Thread = {}
local heartbeat = game:GetService("RunService").Heartbeat
function Thread.SpawnNow(func, ...)
--[[
This method was originally written by Quenty and is slightly
modified for this module. The original source can be found in
the link below, as well as the MIT license:
https://github.com/Quenty/NevermoreEngine/blob/version2/Modules/Shared/Utility/fastSpawn.lua
https://github.com/Quenty/NevermoreEngine/blob/version2/LICENSE.md
--]]
local args = table.pack(...)
local bindable = Instance.new("BindableEvent")
bindable.Event:Connect(function() func(table.unpack(args, 1, args.n)) end)
bindable:Fire()
bindable:Destroy()
end
function Thread.Spawn(func, ...)
local args = table.pack(...)
local hb
hb = heartbeat:Connect(function()
hb:Disconnect()
func(table.unpack(args, 1, args.n))
end)
end
function Thread.Delay(waitTime, func, ...)
local args = table.pack(...)
local executeTime = (tick() + waitTime)
local hb
hb = heartbeat:Connect(function()
if (tick() >= executeTime) then
hb:Disconnect()
func(table.unpack(args, 1, args.n))
end
end)
return hb
end
function Thread.DelayRepeat(intervalTime, func, ...)
local args = table.pack(...)
local nextExecuteTime = (tick() + intervalTime)
local hb
hb = heartbeat:Connect(function()
if (tick() >= nextExecuteTime) then
nextExecuteTime = (tick() + intervalTime)
func(table.unpack(args, 1, args.n))
end
end)
return hb
end
return Thread
================================================
FILE: src/ReplicatedStorage/modules/tween.lua
================================================
return function (Object, Properties, Value, Time, Style, Direction)
Style = Style or Enum.EasingStyle.Quad
Direction = Direction or Enum.EasingDirection.Out
Time = Time or 0.5
local propertyGoals = {}
local Table = (type(Value) == "table" and true) or false
for i,Property in pairs(Properties) do
propertyGoals[Property] = Table and Value[i] or Value
end
local tweenInfo = TweenInfo.new(
Time,
Style,
Direction
)
local tween = game:GetService("TweenService"):Create(Object,tweenInfo,propertyGoals)
tween:Play()
return tween
end
================================================
FILE: src/ReplicatedStorage/modules/utilities.lua
================================================
local module = {}
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local sounds = require(game:GetService("ReplicatedStorage"):WaitForChild("sounds"))
local network = modules.load("network")
local configuration = modules.load("configuration")
-- x = x0 + v0 * t + 0.5 * g * t ^ 2
-- todo
function module.simulateProjectileMotion()
end
module.romanNumerals = {"I","II","III","IV","V","VI","VII","VIII","IX","X","XI","XII","XIII","XIV","XV","XVI","XVII","XVIII","XIX","XX"}
function module.copyTable(tableToCopy)
local tableCopy = {}
for i, v in pairs(tableToCopy) do
if type(v) == "table" then
tableCopy[i] = module.copyTable(v)
else
tableCopy[i] = v
end
end
return tableCopy
end
local mapNameCache = {}
function module.getPlaceName(destination)
if mapNameCache[tostring(destination)] then
return mapNameCache[tostring(destination)]
else
local placeInfo = game.MarketplaceService:GetProductInfo(destination,Enum.InfoType.Asset)
local placeName = placeInfo.Name
if placeName then
placeName = string.gsub(placeName, " %(Demo%)", "")
mapNameCache[tostring(destination)] = placeName
return placeName
end
end
return "???"
end
local function addComas(str)
return #str % 3 == 0 and str:reverse():gsub("(%d%d%d)", "%1,"):reverse():sub(2) or str:reverse():gsub("(%d%d%d)", "%1,"):reverse()
end
function module.formatNumber(number)
number = math.floor(number)
return addComas(tostring(number))
end
function module.isEntityManifestValid(entityManifest, deadIncluded)
return
entityManifest:FindFirstChild("entityType")
and entityManifest:FindFirstChild("entityId")
and entityManifest:FindFirstChild("state") and (deadIncluded or entityManifest.state.Value ~= "dead")
and entityManifest:FindFirstChild("health")
and entityManifest:FindFirstChild("maxHealth")
end
function module.getEntities(_entityType, deadIncluded)
local entities = {}
for i, entityObject in pairs(workspace.placeFolders.entityManifestCollection:GetChildren()) do
local entityManifest do
if entityObject:IsA("Model") and entityObject.PrimaryPart then
entityManifest = entityObject.PrimaryPart
elseif entityObject:IsA("BasePart") then
entityManifest = entityObject
end
end
if entityManifest then
if module.isEntityManifestValid(entityManifest, deadIncluded) then
if not _entityType or entityManifest.entityType.Value == _entityType then
table.insert(entities, entityManifest)
end
end
end
end
return entities
end
function module.timeToString(seconds)
local days = math.floor(seconds / 86400)
seconds = seconds - days * 86400
local hours = math.floor(seconds / 3600)
seconds = seconds - hours * 3600
local minutes = math.floor(seconds / 60)
seconds = seconds - minutes * 60
local timestring = ""
if days > 0 then
timestring = timestring .. days .. "d "
end
if hours > 0 then
timestring = timestring .. hours .. "h "
end
if minutes > 0 then
timestring = timestring .. minutes .. "m "
end
if seconds > 0 then
timestring = timestring .. seconds .. "s"
end
return timestring
end
-- LOCATION: Part, Vector3 or CFrame
-- location and duration are optional
function module.playSound(soundName, location, duration, additionalInfo)
-- soundName can be an instance (deprecated)
local sound
if typeof(soundName) == "string" then
local soundData = sounds[soundName]
assert(soundData, "Sound " .. soundName .. " missing from assets.")
sound = Instance.new("Sound")
for property, value in pairs(soundData) do
sound[property] = value
end
else
sound = soundName
end
if sound then
if location then
if typeof(location) == "Instance" and location:IsA("BasePart") then
-- existing part
sound.Volume = additionalInfo and additionalInfo.volume or sound.Volume
sound.EmitterSize = additionalInfo and additionalInfo.emitterSize or sound.EmitterSize
sound.MaxDistance = additionalInfo and additionalInfo.maxDistance or sound.MaxDistance
sound.Parent = location
sound:Play()
if not sound.Looped then
game.Debris:AddItem(sound, duration or sound.TimeLength + 1)
elseif duration then
game.Debris:AddItem(sound, duration)
end
return sound
else
-- create a new part
local targetCF
if typeof(location) == "CFrame" then
targetCF = location
elseif typeof(location) == "Vector3" then
targetCF = CFrame.new(location)
end
if targetCF then
local soundPart = Instance.new("Part")
soundPart.Name = "soundPart"
soundPart.Size = Vector3.new(1,1,1)
soundPart.Anchored = true
soundPart.CanCollide = false
soundPart.Transparency = 1
soundPart.CFrame = targetCF
soundPart.Parent = workspace.CurrentCamera
sound = sound:Clone()
sound.Volume = additionalInfo and additionalInfo.volume or sound.Volume
sound.EmitterSize = additionalInfo and additionalInfo.emitterSize or sound.EmitterSize
sound.MaxDistance = additionalInfo and additionalInfo.maxDistance or sound.MaxDistance
sound.Parent = soundPart
sound:Play()
if not sound.Looped then
game.Debris:AddItem(soundPart, duration or sound.TimeLength + 1)
elseif duration then
game.Debris:AddItem(soundPart, duration)
end
return soundPart
end
end
else
-- no location provided, just play the root sound
if sound:IsA("Sound") and sound.Looped then
sound:Play()
else
sound = sound:Clone()
sound.Parent = workspace.CurrentCamera
sound:Play()
game.Debris:AddItem(sound, 1 + sound.TimeLength)
end
return sound
end
end
end
-- welds two parts together
function module.weld(part0, part1)
local motor6d = Instance.new("Motor6D")
motor6d.Part0 = part0
motor6d.Part1 = part1
motor6d.C0 = CFrame.new()
motor6d.C1 = part1.CFrame:toObjectSpace(part0.CFrame)
motor6d.Name = part1.Name
motor6d.Parent = part0
end
local httpService = game:GetService("HttpService")
function module.safeJSONEncode(t)
local t2 = module.copyTable(t)
for i, v in pairs(t2) do
if typeof(v) == "Vector3" then
t2[i] = {x = v.X; y = v.Y; z = v.Z}
elseif typeof(v) == "Vector2" then
t2[i] = {x = v.X; y = v.Y}
end
end
return pcall(function() return httpService:JSONEncode(t2) end)
end
function module.safeJSONDecode(t)
local success, response = pcall(function() return httpService:JSONDecode(t) end)
if success then
local t2 = module.copyTable(response)
for i, v in pairs(t2) do
if typeof(v) == "table" and v.x and v.y and v.z then
t2[i] = Vector3.new(v.x, v.y, v.z)
elseif typeof(v) == "table" and v.x and v.y and not v.z then
t2[i] = Vector2.new(v.x, v.y)
end
end
return true, t2
end
return false, nil
end
-- i put this here just so the client and the server can access the same function
local itemLookup
function module.playerCanPickUpItem(player, item, isPet)
if not itemLookup then
itemLookup = require(game.ReplicatedStorage.itemData)
end
if item:FindFirstChild("pickupBlacklist") and item.pickupBlacklist:FindFirstChild(tostring(player.userId)) then
return false
end
if isPet and item:FindFirstChild("petsIgnore") then
return false
end
if item:FindFirstChild("created") then
local timeSinceCreated = os.time() - item.created.Value
if timeSinceCreated >= configuration.getConfigurationValue("timeForAnyonePickupItem") then
return true
elseif itemLookup[item.Name] then
if itemLookup[item.Name].everyoneAvailabilityTime and timeSinceCreated >= itemLookup[item.Name].everyoneAvailabilityTime then
return true
end
end
end
if item:FindFirstChild("owners") then
for _, owner in pairs(item.owners:GetChildren()) do
if owner.Value == player or tonumber(owner.Name) == player.userId then
return true
end
end
return false
end
return true
end
function module.magnitude(vec)
local x = vec.x;
local y = vec.y;
local z = vec.z;
local m = (x*x + y*y + z*z)
return m ^ .5;
end
function module.wipeReferences(tableToLookInto)
for i, v in pairs(tableToLookInto) do
if typeof(v) == "Instance" then
tableToLookInto[i] = nil
elseif type(v) == "table" then
module.wipeReferences(v)
end
-- do this after iterating through v to make sure we get deepest references
if typeof(i) == "Instance" then
tableToLookInto[i] = nil
end
end
end
function module.isSafeToProcess(part, doWaitForChild, ...)
for _, v in pairs({...}) do
if doWaitForChild then
part:WaitForChild(v)
else
if not part:FindFirstChild(v) then
return false
end
end
end
return true
end
function module.isInTable(t, valueToLookFor)
for _, v in pairs(t) do
if v == valueToLookFor then
return true
end
end
return false
end
local itemWeightSelectionGenerator = Random.new()
function module.selectFromWeightTable(weightTable)
local sum = 0
for _, item in pairs(weightTable) do
sum = sum + item.selectionWeight
end
while true do
local rIndex = itemWeightSelectionGenerator:NextInteger(1, #weightTable)
local item = weightTable[rIndex]
if item then
if sum - item.selectionWeight > 0 then
sum = sum - item.selectionWeight
else
return item, rIndex
end
else
return weightTable[1] or {}, weightTable[1] and 1 or nil
end
end
end
local function findObjectHelper(model, objectName, className, listOfFoundObjects)
if not model then return end
local findStart, findEnd = string.find(model.Name, objectName)
if findStart == 1 and findEnd == #(model.Name) then -- must match entire name
if not className or model.className == className or (pcall(model.IsA, model, className) and model:IsA(className)) then
table.insert(listOfFoundObjects, model)
end
end
if pcall(model.GetChildren, model) then
local modelChildren = model:GetChildren()
for i = 1, #modelChildren do
findObjectHelper(modelChildren[i], objectName, className, listOfFoundObjects)
end
end
end
local function resizeModelInternal(model, resizeFactor)
local modelCFrame = model:GetModelCFrame()
local modelSize = model:GetModelSize()
local baseParts = {}
local basePartCFrames = {}
local joints = {}
local jointParents = {}
local meshes = {}
findObjectHelper(model, ".*", "BasePart", baseParts)
findObjectHelper(model, ".*", "JointInstance", joints)
-- meshes don't inherit from anything accessible?
findObjectHelper(model, ".*", "FileMesh", meshes) -- base class for SpecialMesh and FileMesh
findObjectHelper(model, ".*", "CylinderMesh", meshes)
findObjectHelper(model, ".*", "BlockMesh", meshes)
-- store the CFrames, so our other changes don't rearrange stuff
for _, basePart in pairs(baseParts) do
basePartCFrames[basePart] = basePart.CFrame
end
-- scale joints
for _, joint in pairs(joints) do
joint.C0 = joint.C0 + (joint.C0.p) * (resizeFactor - 1)
joint.C1 = joint.C1 + (joint.C1.p) * (resizeFactor - 1)
jointParents[joint] = joint.Parent
end
-- scale parts and reposition them within the model
for _, basePart in pairs(baseParts) do
-- if pcall(function() basePart.FormFactor = "Custom" end) then basePart.FormFactor = "Custom" end
basePart.Size = basePart.Size * resizeFactor
local oldCFrame = basePartCFrames[basePart]
local oldPositionInModel = modelCFrame:pointToObjectSpace(oldCFrame.p)
local distanceFromCorner = oldPositionInModel + modelSize/2
distanceFromCorner = distanceFromCorner * resizeFactor
local newPositionInSpace = modelCFrame:pointToWorldSpace(distanceFromCorner - modelSize/2)
basePart.CFrame = oldCFrame - oldCFrame.p + newPositionInSpace
end
-- scale meshes
for _,mesh in pairs(meshes) do
-- mesh.Scale = mesh.Scale * resizeFactor
end
-- pop the joints back, because they prolly got borked
for _, joint in pairs(joints) do
joint.Parent = jointParents[joint]
end
return model
end
function module.getEntityGUIDByEntityManifest(entityManifest)
local player
if entityManifest.Parent and entityManifest.Parent:IsA("Model") then
player = game.Players:GetPlayerFromCharacter(entityManifest.Parent)
end
-- this is important because 1 player will always have the same GUID, but the character
-- might change!
if player then
return player.entityGUID.Value
elseif entityManifest:FindFirstChild("entityGUID") then
return entityManifest.entityGUID.Value
end
return nil
end
-- returns
-- * BasePart entityManifest
-- * Boolean isEntityInWorld [even if first argument is nil, treat it like it isn't... ie if player is respawning]
function module.getEntityManifestByEntityGUID(entityGUID)
local entityManifests = module.getEntities(nil, true)
for i, entityManifest in pairs(entityManifests) do
local _entityGUID = module.getEntityGUIDByEntityManifest(entityManifest)
if _entityGUID == entityGUID then
return entityManifest, true
end
end
for i, player in pairs(game.Players:GetPlayers()) do
if player:FindFirstChild("entityGUID") and player.entityGUID.Value == entityGUID then
return nil, true
end
end
return nil, false
end
function module.connectEventHelper(event, func)
local connection
connection = event:connect(function(...)
local doDisconnect = func(...)
if doDisconnect then
connection:disconnect()
end
end)
return connection
end
module.placeIdMapping = {
["2376885433"] = 2015602902;
["2064647391"] = 2015602902;--4041449372;
["2035250551"] = 4041616995;
["2060360203"] = 4041642879;
["2060556572"] = 4784798551;
["2093766642"] = 4784800626;
["2471035818"] = 4042327457;
["2546689567"] = 4042356215;
["3852057184"] = 4041618739;
["2376890690"] = 4042533453;
["2470481225"] = 4042553675;
["2260598172"] = 4042431927;
["3112029149"] = 4042493740;
["3460262616"] = 4042399045;
["2496503573"] = 4042381342;
["2119298605"] = 4042577479;
["2878620739"] = 4042595899;
["2677014001"] = 4786263828;
["3232913902"] = 4787417227;
["2544075708"] = 4787415375;
}
function module.placeIdForGame(placeId)
if game.GameId == 712031239 then
return module.placeIdMapping[tostring(placeId)] or placeId
else
return placeId
end
end
function module.originPlaceId(placeId)
if game.GameId == 712031239 then
for originPlaceIdString, mappedId in pairs(module.placeIdMapping) do
if mappedId == placeId then
return tonumber(originPlaceIdString)
end
end
end
return placeId
end
module.scale = resizeModelInternal
function module.doesPlayerHaveEquipmentPerk(player, perkName)
local char = player.Character
if not char then return false end
local entityManifest = char.PrimaryPart
if not entityManifest then return false end
local renderCharacterContainer = network:invoke("getRenderCharacterContainerByEntityManifest", entityManifest)
if not renderCharacterContainer then return false end
local entityRender = renderCharacterContainer:FindFirstChild("entity")
if not entityRender then return false end
local equipment = network:invoke("getCurrentlyEquippedForRenderCharacter", entityRender)
if not equipment then return false end
for position, info in pairs(equipment) do
if info.baseData.perks then
for perk, active in pairs(info.baseData.perks) do
if perk == perkName then
return active
end
end
end
end
return false
end
function module.calculateNumArrowsFromDex(dex)
local numArrows = 1
local dexAccumulator = 0
local currentDexStep = 20
repeat
dexAccumulator = dexAccumulator + currentDexStep
currentDexStep = currentDexStep + 10
if dexAccumulator <= dex then
numArrows = numArrows + 1
end
until dexAccumulator >= dex
return numArrows
end
function module.calculatePierceFromStr(str)
local pierce = 0
local accumulator = 0
local currentStep = 20
local stepIncrease = 10
repeat
accumulator = accumulator + currentStep
currentStep = currentStep + stepIncrease
if accumulator <= str then
pierce = pierce + 1
end
until accumulator >= str
return pierce
end
function module.doesEntityHaveStatusEffect(entity, statusEffectName)
local statusEffectsV2 = entity:FindFirstChild("statusEffectsV2")
if not statusEffectsV2 then return false end
local success, statusEffects = module.safeJSONDecode(statusEffectsV2.Value)
if not success then return false end
for _, statusEffect in pairs(statusEffects) do
if statusEffect.statusEffectType == statusEffectName then
return true
end
end
return false
end
function module.healEntity(sourceEntity, targetEntity, amount)
local health = targetEntity:FindFirstChild("health")
if not health then return end
local maxHealth = targetEntity:FindFirstChild("maxHealth")
if not maxHealth then return end
local healthBefore = health.Value
health.Value = math.min(health.Value + amount, maxHealth.Value)
local trueHealing = health.Value - healthBefore
if trueHealing > 1 then
network:fireAllClients("signal_damage", targetEntity, {
damage = -trueHealing,
})
end
end
local function rankCheck(player)
wait(0.1)
local playerRank = 0 do
local success, returnValue = pcall(function()
return player:GetRankInGroup(4238824)
end)
if success and returnValue > playerRank then
playerRank = returnValue
end
end
if game:GetService("RunService"):IsStudio() or player.Name == "berezaa" or player.Name == "Polymorphic" or player.Name == "sk3let0n" or playerRank >= 250 then
local devTag = Instance.new("BoolValue")
devTag.Name = "developer"
devTag.Parent = player
end
if playerRank > 1 then
local devTag = Instance.new("BoolValue")
devTag.Name = "QA"
devTag.Parent = player
-- elseif game.PlaceId == 2061558182 and not (runService:IsRunMode() or runService:IsStudio()) then
-- player:Kick("Not allowed to be here.")
end
end
game.Players.PlayerAdded:connect(rankCheck)
for _, player in pairs(game.Players:GetPlayers()) do
rankCheck(player)
end
return module
================================================
FILE: src/ReplicatedStorage/monsterLookup/Baby Shroom.lua
================================================
local monsterData = {
level = 1;
--> combat stats <--
attackRange = 2.5;
baseSpeed = 12;
attackSpeed = 4;
maxHealth = 6;
damage = 2;
defense = 0;
--> death reward stuff <--
EXP = 3;
gold = 5;
--> monster damage <--
damageHitboxCollection = {
{partName = "Cap"; castType = "box"; originOffset = CFrame.new(0, 0.5, 0)};
};
--> loot drop stuff <--
lootDrops = {
{id = 1; spawnChance = 3/4};
{itemName = "mushroom spore"; spawnChance = 1};
{itemName = "mushroom spore"; spawnChance = 1/7};
{itemName = "health potion"; spawnChance = 1/12};
{itemName = "wooden club"; spawnChance = 1/40};
};
--> ease of access <--
module = script;
id = "monster";
}
return monsterData
================================================
FILE: src/ReplicatedStorage/monsterLookup/Chicken.lua
================================================
return {
--> combat stats <--
attackRange = 2;
baseSpeed = 6;
baseHealth = 10;
-- healthMulti = 10000;
doNotLure = true;
attackSpeed = 2;
baseDamage = 10;
--> death reward stuff <--
baseEXP = 10;
level = 2;
baseMoney = 10;
--> monster damage <--
damageHitboxCollection = {};
--> loot drop stuff <--
lootDrops = {
{id = 270; spawnChance = 0.5};
{id = 271; spawnChance = 1};
{id = 277; spawnChance = 0.7};
};
--> ease of access <--
module = script;
id = "chicken";
}
================================================
FILE: src/ReplicatedStorage/monsterLookup/Dummy.lua
================================================
local monsterData = {
--> combat stats <--
attackRange = 0;
baseSpeed = 0;
attackSpeed = 0;
baseHealth = 1e9;
baseDamage = 0;
maxHealth = 1e9;
damage = 0;
--> gameplay flags <--
resilient = true;
--> death reward stuff <--
baseEXP = 1;
level = 1;
baseMoney = 0;
--> spawn region stuff <--
monsterSpawnRegions = {
[script.Name] = 1;
[script.Name.."2"] = 1;
[script.Name.."3"] = 1;
};
animationDamageEnd = 1;
dontScale = true;
--> monster damage <--
damageHitboxCollection = {
};
--> loot drop stuff <--
lootDrops = {
};
--> ease of access <--
module = script;
id = "monster";
}
return monsterData
================================================
FILE: src/ReplicatedStorage/monsterLookup/Elder Shroom.lua
================================================
local monsterData = {
level = 5;
--> combat stats <--
attackRange = 6;
baseSpeed = 10;
attackSpeed = 3.2;
maxHealth = 30;
damage = 8;
defense = 1;
--> death reward stuff <--
EXP = 20;
gold = 25;
--> monster damage <--
damageHitboxCollection = {
{partName = "RightFoot"; castType = "sphere"; radius = 3; originOffset = CFrame.new(0, 0.5, 1.5)};
};
-- only for effect rn
damageHitboxCollection2 = {
{partName = "RightFoot"; castType = "sphere"; radius = 6; originOffset = CFrame.new(0, -2.5, 0)};
};
--> loot drop stuff <--
lootDrops = {
{id = 1; spawnChance = 3/4};
{itemName = "mushroom beard"; spawnChance = 1};
{itemName = "mushroom mini"; spawnChance = 1/7};
{itemName = "mushroom spore"; spawnChance = 1/7};
{itemName = "health potion"; spawnChance = 1/8};
{itemName = "mana potion"; spawnChance = 1/12};
{itemName = "100% weapon attack scroll"; spawnChance = 1/40};
{itemName = "rune mushtown"; spawnChance = 1/120};
{itemName = "shoulder pads 2"; spawnChance = 1/150};
{itemName = "worn boots"; spawnChance = 1/200};
};
--> ease of access <--
module = script;
animationDamageStart = .3;
animationDamageEnd = 1;
}
return monsterData
================================================
FILE: src/ReplicatedStorage/monsterLookup/Shroom.lua
================================================
local monsterData = {
level = 3;
--> combat stats <--
attackRange = 3.4;
baseSpeed = 17;
attackSpeed = 1.5;
maxHealth = 14;
damage = 4;
defense = 0;
--> death reward stuff <--
EXP = 7;
gold = 10;
variations = {
poisoned = {
specialName = "Poisoned Shroom";
dye = {r=160;g=255;b=160};
level = 6;
bonusXPMulti = 2.5;
bonusLootMulti = 4;
healthMulti = 2;
scale = 1.25;
specialAttackHealth = 1;
specialAttackCap = 10;
};
};
--> monster damage <--
damageHitboxCollection = {
{partName = "LeftLeg"; castType = "sphere"; radius = 3; originOffset = CFrame.new(0, -0.3, 0.4)};
};
--> loot drop stuff <--
lootDrops = {
{id = 1; spawnChance = 3/4};
{itemName = "mushroom mini"; spawnChance = 1};
{itemName = "mushroom mini"; spawnChance = 1/7};
{itemName = "health potion"; spawnChance = 1/10};
{itemName = "mana potion"; spawnChance = 1/15};
{itemName = "wooden sword"; spawnChance = 1/80;};
{itemName = "leather tunic"; spawnChance = 1/100;};
};
--> ease of access <--
module = script;
monsterEvents = {};
}
return monsterData
================================================
FILE: src/ReplicatedStorage/monsterLookup/init.lua
================================================
--[[
monsterData {}
--]]
local replicatedStorage = game:GetService("ReplicatedStorage")
local models = replicatedStorage:WaitForChild("assets"):WaitForChild("monsters")
local lookupTable = {} do
for _, monsterDataModule in pairs(script:GetChildren()) do
local monsterData = require(monsterDataModule)
local entry = models:FindFirstChild(monsterDataModule.Name)
if entry then
-- internal stuff
monsterData.module = monsterDataModule
monsterData.entity = entry:WaitForChild("entity")
local defaultStatesData = require(replicatedStorage.defaultMonsterState)
local statesData
local statesModule = monsterDataModule:FindFirstChild("states")
if statesModule then
statesData = require(statesModule)
end
if statesData then
setmetatable(statesData, {__index = defaultStatesData})
setmetatable(statesData.states, {__index = defaultStatesData.states})
else
statesData = defaultStatesData
end
monsterData.statesData = statesData
-- hook ups
lookupTable[monsterDataModule.Name] = monsterData
end
end
end
return lookupTable
================================================
FILE: src/ReplicatedStorage/perkLookup/colosseum equipment.lua
================================================
local modules = require(game.ReplicatedStorage.modules)
local network = modules.load("network")
local tempData = modules.load("tempData")
local events = modules.load("events")
local utilities = modules.load("utilities")
local rand = Random.new()
local perkData = {
sharedValues = {
layoutOrder = 0;
subtitle = "Equipment Perk";
color = Color3.fromRGB(234, 102, 84);
},
perks = {
["inoculation"] = {
title = "Inoculation",
description = "10% chance to heal 200 HP when damaged.",
onDamageTaken = function(sourceManifest, sourceType, sourceId, targetManifest, damageData)
if damageData.damage > 1 and rand:NextNumber() <= 0.1 then
-- wait required to seperate damage from healing
-- healing should probably get its own signal like damage has now
wait(0.1)
pcall(function()
utilities.healEntity(targetManifest, targetManifest, 200)
local heal = script.heal:Clone()
heal.Parent = targetManifest
heal:Emit(30)
game.Debris:AddItem(heal,3)
utilities.playSound("item_heal", targetManifest)
end)
end
end;
},
["resonance"] = {
title = "Resonance Charms",
description = "Restore 35% of damage taken as MP.",
onDamageTaken = function(sourceManifest, sourceType, sourceId, targetManifest, damageData)
if damageData.damage > 1 then
-- wait required to seperate damage from healing
-- healing should probably get its own signal like damage has now
wait(0.1)
pcall(function()
local restored = math.floor(damageData.damage * 0.35)
local actuallyRestored
if targetManifest.mana.Value + restored > targetManifest.maxMana.Value then
actuallyRestored = targetManifest.maxMana.Value - targetManifest.mana.Value
else
actuallyRestored = restored
end
targetManifest.mana.Value = math.min(targetManifest.mana.Value + restored, targetManifest.maxMana.Value)
if actuallyRestored >= 10 then
local heal = script.manaEmitter:Clone()
heal.Parent = targetManifest
heal:Emit(math.floor(math.clamp( 3 * actuallyRestored^(1/2), 1, 50)))
game.Debris:AddItem(heal,3)
utilities.playSound("item_mana", targetManifest, nil, {volume = 0.3; maxDistance = 100; emitterSize = 7})
end
end)
end
end;
},
["reckless"] = {
title = "Reckless",
description = "Deal and recieve 120% damage.",
onDamageTaken = function(sourceManifest, sourceType, sourceId, targetManifest, ref_damageData)
ref_damageData.damage = ref_damageData.damage * 1.2
end;
onDamageGiven = function(sourceManifest, sourceType, sourceId, targetManifest, ref_damageData)
ref_damageData.damage = ref_damageData.damage * 1.2
end
},
}
}
return perkData
================================================
FILE: src/ReplicatedStorage/perkLookup/dunes equipment.lua
================================================
local modules = require(game.ReplicatedStorage.modules)
local network = modules.load("network")
local targeting = modules.load("damage")
local utilities = modules.load("utilities")
local itemLookup = require(game.ReplicatedStorage.itemData)
local function isDay(clockTime)
return (clockTime > 6) and (clockTime < 18)
end
local function isNight(clockTime)
return not isDay(clockTime)
end
local perkData = {
sharedValues = {
layoutOrder = 0,
subtitle = "Equipment Perk",
color = BrickColor.new("Cool yellow").Color,
},
perks = {
["overdraw"] = {
title = "Overdraw",
description = "Fires larger arrows further.",
-- implementation is with arrow basic attacks
},
["you look great"] = {
title = "You Look Great",
description = "Not even the Sun can stand in your way.",
-- implementation is somewhere
},
["solar wind"] = {
title = "Solar Wind",
description = "Increases the range of Blink.",
-- implementation is in the Blink ability
},
["one good hit"] = {
title = "One Good Hit",
description = "Execute instantly kills low health non-resilient foes.",
-- implementation is in Execute ability
},
["aftershock"] = {
title = "Aftershock",
description = "Adds shockwaves to Ground Slam.",
-- implementation is in Ground Slam ability
},
["dunes wisdom"] = {
title = "Dunes Wisdom",
description = "Regenerate extra mana during the day.",
onTimeOfDayUpdated = function(player, clockTime, dt)
if not isDay(clockTime) then return end
local char = player.Character
if not char then return end
local entity = char.PrimaryPart
if not entity then return end
local mana = entity:FindFirstChild("mana")
local maxMana = entity:FindFirstChild("maxMana")
if not (mana and maxMana) then return end
local regenerationPerSecond = 6
mana.Value = math.min(maxMana.Value, mana.Value + regenerationPerSecond * dt)
end,
},
["apogee"] = {
title = "Apogee",
description = "During the day, deal more damage and regenerate health.",
onDamageGiven = function(sourceManifest, sourceType, sourceId, targetManifest, damageData)
if isDay(game.Lighting.ClockTime) then
damageData.damage = damageData.damage * 1.2
end
end,
onTimeOfDayUpdated = function(player, clockTime, dt)
if not isDay(clockTime) then return end
local char = player.Character
if not char then return end
local entity = char.PrimaryPart
if not entity then return end
local health = entity:FindFirstChild("health")
local maxHealth = entity:FindFirstChild("maxHealth")
local state = entity:FindFirstChild("state")
if not (health and maxHealth and state) then return end
if state.Value == "dead" then return end
local regenerationPerSecond = 20
health.Value = math.min(maxHealth.Value, health.Value + regenerationPerSecond * dt)
end,
},
["midnight"] = {
title = "Midnight",
description = "During the night, deal more damage and move faster.",
onDamageGiven = function(sourceManifest, sourceType, sourceId, targetManifest, damageData)
if isNight(game.Lighting.ClockTime) then
damageData.damage = damageData.damage * 1.2
end
end,
onTimeOfDayUpdated = function(player, clockTime, dt)
if not isNight(clockTime) then return end
local char = player.Character
if not char then return end
local entity = char.PrimaryPart
if not entity then return end
network:invoke(
"applyStatusEffectToEntityManifest",
entity,
"empower",
{
hideInStatusBar = true,
duration = 5,
modifierData = {walkspeed = 4},
},
entity,
"perk"
)
end,
},
["twilight"] = {
title = "Twilight",
description = "Empower Magic Missile to launch Mana Stars.",
-- implementation is in Magic Missile
},
["acidic arrows"] = {
title = "Acidic Arrows",
description = "Critical strikes on enemies create a splash of acid.",
onCritGiven = function(sourceManifest, sourceType, sourceId, targetManifest, damageData)
local player = game:GetService("Players"):GetPlayerFromCharacter(sourceManifest.Parent)
if not player then return end
local isArrow = (damageData.sourceType == "equipment") and (damageData.category == "projectile")
if not isArrow then return end
local radius = 12
network:fireAllClients("effects_requestEffect", "acidSplash", {
position = damageData.position,
radius = radius,
duration = 0.25,
})
local function damageEntity(entity)
local entityType = entity:FindFirstChild("entityType")
if not entityType then return end
entityType = entityType.Value
local damageData = {
damage = damageData.damage / 2,
sourceType = "perk",
sourceId = 0,
damageType = "magical",
sourcePlayerId = player.UserId,
}
if entityType == "monster" then
network:invoke("monsterDamageRequest_server", player, entity, damageData)
else
network:invoke("playerDamageRequest_server", player, entity, damageData)
end
end
local radiusSq = radius * radius
for _, target in pairs(targeting.getDamagableTargets(game.Players.LocalPlayer)) do
local delta = target.Position - damageData.position
local distanceSq = delta.X * delta.X + delta.Y * delta.Y + delta.Z * delta.Z
if distanceSq < radiusSq then
damageEntity(target)
end
end
end,
},
["dunes courage"] = {
title = "Dunes Courage",
description = "Deal more damage to non-human Dunes enemies.",
onDamageGiven = function(sourceManifest, sourceType, sourceId, targetManifest, damageData)
local n = targetManifest.Name
if
n == "Stingtail" or
n == "Deathsting" or
n == "Sandwurm" or
n == "Scarab"
then
damageData.damage = damageData.damage * 1.2
end
end,
},
["bloodcraze"] = {
title = "Bloodcraze",
description = "Critical hits grant a stacking attack speed bonus.",
onCritGiven = function(sourceManifest, sourceType, sourceId, targetManifest, damageData)
local guid = utilities.getEntityGUIDByEntityManifest(sourceManifest)
if not guid then return false end
local statuses = network:invoke("getStatusEffectsOnEntityManifestByEntityGUID", guid)
local stacks = 0
for _, status in pairs(statuses) do
if status.statusEffectType == "bloodcrazed" then
stacks = status.statusEffectModifier.stacks
end
end
stacks = math.min(stacks + 1, 6)
network:invoke(
"applyStatusEffectToEntityManifest",
sourceManifest,
"empower",
{
stacks = stacks,
duration = 8,
modifierData = {attackSpeed = 0.05 * stacks},
},
sourceManifest,
"item",
229
)
end
},
["weakening venom"] = {
title = "Weakening Venom",
description = "Melee attacks mark a target to take more damage.",
onDamageGiven = function(sourceManifest, sourceType, sourceId, targetManifest, damageData)
if damageData.sourceType == "equipment" then
network:invoke(
"applyStatusEffectToEntityManifest",
targetManifest,
"weakened by venom",
{
duration = 5,
},
sourceManifest,
"item",
230
)
elseif damageData.sourceType == "ability" then
local hasStatus, status = network:invoke("doesEntityHaveStatusEffect", targetManifest, "weakened by venom")
if hasStatus then
damageData.damage = damageData.damage * 1.5
network:invoke("revokeStatusEffectByStatusEffectGUID", status.statusEffectGUID)
end
end
end,
},
["just in case"] = {
title = "Just in Case",
description = "Increase damage when above 80% health. Does not work in off-hand.",
onDamageGiven = function(sourceManifest, sourceType, sourceId, targetManifest, damageData)
local bowId = 249
local player = game.Players:GetPlayerFromCharacter(sourceManifest.Parent)
if not player then return end
local equipmentInfo = network:invoke("getPlayerEquipmentDataByEquipmentPosition", player, 1)
if equipmentInfo.id ~= bowId then return end
local health = sourceManifest:FindFirstChild("health")
local maxHealth = sourceManifest:FindFirstChild("maxHealth")
if not (health and maxHealth) then return end
local ratio = health.Value / maxHealth.Value
if ratio < 0.8 then return end
damageData.damage = damageData.damage * 1.1
end
},
["not like this"] = {
title = "Not Like This",
description = "Dramatically increase damage when below 20% health. Works in off-hand.",
onDamageGiven = function(sourceManifest, sourceType, sourceId, targetManifest, damageData)
local health = sourceManifest:FindFirstChild("health")
local maxHealth = sourceManifest:FindFirstChild("maxHealth")
if not (health and maxHealth) then return end
local ratio = health.Value / maxHealth.Value
if ratio > 0.2 then return end
damageData.damage = damageData.damage * 1.4
end
},
["living blade"] = {
title = "Living Blade",
description = "Basic attacks deal magic damage and scale accordingly.",
onDamageGiven = function(sourceManifest, sourceType, sourceId, targetManifest, damageData)
if sourceType == "equipment" then
damageData.damageType = "magical"
end
end,
}
},
}
return perkData
================================================
FILE: src/ReplicatedStorage/perkLookup/init.lua
================================================
--[[
perkFolder
table perks
table sharedValues
perkData
string title
string description
function apply[ref statistics_final]
table modifierData
-- "ref damageData" can be modified to modify the damage dealt
function onAbilityHit[sourceEntity, sourceType, sourceId, targetManifest, ref damageData]
function onBasicAttackHit[sourceEntity, sourceType, sourceId, targetManifest, ref damageData]
function onDamageDealt[sourceEntity, sourceType, sourceId, targetManifest, ref damageData]
function onDamageReceived[sourceEntity, sourceType, sourceId, targetManifest, ref damageData]
--]]
local perkLookup = {}
for i,folder in pairs(script:GetChildren()) do
local perkFolderData = require(folder)
for perkName, perk in pairs(perkFolderData.perks) do
if perkFolderData.sharedValues then
for key, data in pairs(perkFolderData.sharedValues) do
perk[key] = data
end
end
perk.folder = folder.Name
perkLookup[perkName] = perk
end
end
return perkLookup
================================================
FILE: src/ReplicatedStorage/perkLookup/misc.lua
================================================
local perkData = {
perks = {
--[[
["warrior"] = {
layoutOrder = 1;
title = "Steadfast";
subtitle = "Warrior Perk";
color = Color3.fromRGB(130, 59, 60);
description = "Take 20% less damage.";
-- auto-assign based on class
class = "warrior";
apply = function(statistics_final)
statistics_final.damageTakenMulti = statistics_final.damageTakenMulti * 0.8
end;
};
]]
["holymagic"] = {
layoutOrder = 1;
title = "Holy Magic";
subtitle = "Cleric Perk";
color = Color3.fromRGB(235, 198, 48);
description = "Mage Ability upgrade.";
-- auto-assign based on class
class = "cleric";
};
["colosseum"] = {
layoutOrder = 2;
title = "Colosseum Blessing";
subtitle = "Location Perk";
color = Color3.fromRGB(226, 166, 61);
description = "Take 50% less damage.";
-- auto-assign based on class
condition = function(statistics_final)
return game.PlaceId == 2496503573 or game.PlaceId == 4042381342
end;
apply = function(statistics_final)
statistics_final.damageTakenMulti = statistics_final.damageTakenMulti * 0.5
end;
};
["battydagger"] = {
layoutOrder = 1,
title = "Nocturnal Flight",
color = Color3.fromRGB(77, 34, 89),
description = "Shunpo has no cooldown.",
}
}
}
return perkData
================================================
FILE: src/ReplicatedStorage/perkLookup/mokotuaa equipment.lua
================================================
local modules = require(game.ReplicatedStorage.modules)
local network = modules.load("network")
local tempData = modules.load("tempData")
local events = modules.load("events")
local function keyDamage(slot)
return "perk_rhythmDamage_"..slot
end
local function keyStun(slot)
return "perk_rhythmStun_"..slot
end
local function keyDefense(slot)
return "perk_rhythmDefense_"..slot
end
local function keyMana(slot)
return "perk_rhythmMana_"..slot
end
local function keySplinter(slot)
return "perk_rhythmSplinter_"..slot
end
local function keyAttackSpeed(slot)
return "perk_rhythmAttackSpeed_"..slot
end
local perkData = {
sharedValues = {
layoutOrder = 0;
subtitle = "Equipment Perk";
color = Color3.fromRGB(235, 131, 82);
},
perks = {
["rhythmDamage"] = {
title = "Rhythmic Ferocity",
description = "Every 4th basic attack deals extra damage.",
onEquipped = function(player, itemData, slot)
if slot ~= "1" then return end
local data = {
hits = 0,
}
data.guid = events:registerForEvent("playerWillDealDamage", function(playerDamaging, damageData)
if playerDamaging ~= player then return end
if damageData.sourceType ~= "equipment" then return end
data.hits = data.hits + 1
if data.hits == 4 then
data.hits = 0
damageData.damage = damageData.damage * 2
end
end)
tempData:set(player, keyDamage(slot), data)
end,
onUnequipped = function(player, itemData, slot)
if slot ~= "1" then return end
local data = tempData:get(player, keyDamage(slot))
events:deregisterFromEvent(data.guid)
tempData:delete(player, keyDamage(slot))
end
},
["rhythmStun"] = {
title = "Rhythmic Concussion",
description = "Every 4th basic attack hit stuns the victim.",
onEquipped = function(player, itemData, slot)
local data = {
hits = 0,
}
data.guid = events:registerForEvent("playerWillDealDamage", function(playerDamaging, damageData)
if playerDamaging ~= player then return end
if damageData.sourceType ~= "equipment" then return end
local char = player.Character
if not char then return end
local manifest = char.PrimaryPart
if not manifest then return end
data.hits = data.hits + 1
if data.hits == 4 then
data.hits = 0
network:invoke(
"applyStatusEffectToEntityManifest",
damageData.target,
"stunned",
{
duration = 1,
modifierData = {
walkspeed_totalMultiplicative = -1,
},
},
manifest,
"equipment",
213
)
end
end)
tempData:set(player, keyStun(slot), data)
end,
onUnequipped = function(player, itemData, slot)
local data = tempData:get(player, keyStun(slot))
events:deregisterFromEvent(data.guid)
tempData:delete(player, keyStun(slot))
end
},
["rhythmDefense"] = {
title = "Rhythmic Tenacity",
description = "Every 4th hit suffered deals half damage.",
onEquipped = function(player, itemData, slot)
local data = {
hits = 0,
}
data.guid = events:registerForEvent("playerWillTakeDamage", function(playerDamaged, damageData)
if playerDamaged ~= player then return end
data.hits = data.hits + 1
if data.hits == 4 then
data.hits = 0
damageData.damage = damageData.damage / 2
end
end)
tempData:set(player, keyDefense(slot), data)
end,
onUnequipped = function(player, itemData, slot)
local data = tempData:get(player, keyDefense(slot))
events:deregisterFromEvent(data.guid)
tempData:delete(player, keyDefense(slot))
end
},
["rhythmMana"] = {
title = "Rhythmic Invocation",
description = "Every 4th ability used costs no mana.",
onEquipped = function(player, itemData, slot)
local data = {
uses = 0,
}
data.guid = events:registerForEvent("playerWillUseAbility", function(playerUsing, abilityData)
if playerUsing ~= player then return end
data.uses = data.uses + 1
if data.uses == 4 then
data.uses = 0
abilityData.manaCost = 0
end
end)
tempData:set(player, keyMana(slot), data)
end,
onUnequipped = function(player, itemData, slot)
local data = tempData:get(player, keyMana(slot))
events:deregisterFromEvent(data.guid)
tempData:delete(player, keyMana(slot))
end
},
["rhythmSplinter"] = {
title = "Rhythmic Splintering",
description = "Only every 4th shot uses an arrow.",
onEquipped = function(player, itemData, slot)
if slot ~= "1" then return end
local data = {
uses = 0,
}
data.guid = events:registerForEvent("playerWillUseArrow", function(playerUsing, arrowData)
if playerUsing ~= player then return end
data.uses = data.uses + 1
if data.uses == 4 then
data.uses = 0
arrowData.needsArrow = true
else
arrowData.needsArrow = false
end
end)
tempData:set(player, keySplinter(slot), data)
end,
onUnequipped = function(player, itemData, slot)
if slot ~= "1" then return end
local data = tempData:get(player, keySplinter(slot))
events:deregisterFromEvent(data.guid)
tempData:delete(player, keySplinter(slot))
end
},
["rhythmAttackSpeed"] = {
title = "Rhythmic Acceleration",
description = "Every 4th hit temporarily boosts attack speed.",
onEquipped = function(player, itemData, slot)
if slot ~= "1" then return end
local data = {
hits = 0,
}
data.guid = events:registerForEvent("playerWillDealDamage", function(playerDamaging, damageData)
if playerDamaging ~= player then return end
if damageData.sourceType ~= "equipment" then return end
local char = player.Character
if not char then return end
local manifest = char.PrimaryPart
if not manifest then return end
data.hits = data.hits + 1
if data.hits == 4 then
data.hits = 0
network:invoke(
"applyStatusEffectToEntityManifest",
manifest,
"empower",
{
duration = 2,
modifierData = {
attackSpeed = 0.25,
},
},
manifest,
"item",
210
)
end
end)
tempData:set(player, keyAttackSpeed(slot), data)
end,
onUnequipped = function(player, itemData, slot)
if slot ~= "1" then return end
local data = tempData:get(player, keyAttackSpeed(slot))
events:deregisterFromEvent(data.guid)
tempData:delete(player, keyAttackSpeed(slot))
end
},
}
}
return perkData
================================================
FILE: src/ReplicatedStorage/perkLookup/mushroom equipment.lua
================================================
local perkData = {
sharedValues = {
layoutOrder = 0;
subtitle = "Equipment Perk";
color = Color3.fromRGB(171, 47, 86);
};
perks = {
["airborne"] = {
title = "Airborne";
description = "1.5x Basic Attack damage while jumping.";
onDamageGiven = function(sourceManifest, sourceType, sourceId, targetManifest, ref_damageData)
if sourceManifest:FindFirstChild("state") and sourceManifest.state.Value == "jumping" then
if ref_damageData.sourceType == "equipment" then
ref_damageData.damage = ref_damageData.damage * 1.5
end
end
end;
};
["poisonblink"] = {
title = "Spore Cloud";
description = "Blink releases a cloud of poisonous spores.";
};
["bounceback"] = {
title = "Bounceback",
description = "15x Ability Knockback",
},
}
}
return perkData
================================================
FILE: src/ReplicatedStorage/perkLookup/spider equipment.lua
================================================
local modules = require(game.ReplicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local SPIDER_BOW_ID = 92
local SPIDER_DAGGER_ID = 52
local perkData = {
sharedValues = {
layoutOrder = 0;
subtitle = "Equipment Perk";
color = Color3.fromRGB(147, 40, 234);
};
perks = {
["causticwounds"] = {
title = "Caustic Wounds";
description = "Basic attacks inflict poison damage.";
onDamageGiven = function(sourceManifest, sourceType, sourceId, targetManifest, ref_damageData)
-- only perform this bonus if we're using the dagger in main hand
local player = game.Players:GetPlayerFromCharacter(sourceManifest.Parent)
if not player then return end
local data = network:invoke("getPlayerEquipmentDataByEquipmentPosition", player, 1)
if data.id ~= SPIDER_DAGGER_ID then return end
if targetManifest:FindFirstChild("poisoned") == nil and ref_damageData.sourceType == "equipment" then
if targetManifest and targetManifest:FindFirstChild("health") and targetManifest:FindFirstChild("maxHealth") then
if targetManifest.health.Value > 0 then
local wasApplied, reason = require(game.ReplicatedStorage.modules.network):invoke(
"applyStatusEffectToEntityManifest",
targetManifest,
"poison",
{duration = 3; healthLost = 1 * ref_damageData.damage},
sourceManifest,
"perk",
52
)
if wasApplied then
local tag = Instance.new("BoolValue")
tag.Name = "poisoned"
tag.Parent = targetManifest
game.Debris:AddItem(tag, 3)
utilities.playSound("bubbles", targetManifest)
local poison = script.poison:Clone()
poison.Parent = targetManifest
poison:Emit(50)
poison.Enabled = true
spawn(function()
wait(3)
if poison then
poison.Enabled = false
end
end)
game.Debris:AddItem(poison,5)
end
return wasApplied
end
end
end
end;
};
["webbedshots"] = {
title = "Webbed Shots";
description = "Ranged attacks slow enemies.";
onDamageGiven = function(sourceManifest, sourceType, sourceId, targetManifest, ref_damageData)
-- only perform this perk when we're in the main hand
local player = game.Players:GetPlayerFromCharacter(sourceManifest.Parent)
if not player then return end
local data = network:invoke("getPlayerEquipmentDataByEquipmentPosition", player, 1)
if data.id ~= SPIDER_BOW_ID then return end
if targetManifest and targetManifest:FindFirstChild("health") and targetManifest:FindFirstChild("maxHealth") then
if targetManifest.health.Value > 0 then
local wasApplied, reason = require(game.ReplicatedStorage.modules.network):invoke(
"applyStatusEffectToEntityManifest",
targetManifest,
"empower",
{
duration = 3;
modifierData = {
walkspeed_totalMultiplicative = -0.35;
};
},
targetManifest,
"perk",
92
)
if wasApplied then
local slow = script.slow:Clone()
slow.Parent = targetManifest
slow:Emit(30)
game.Debris:AddItem(slow, 3)
end
return true
end
end
end;
};
["venombomb"] = {
title = "Venom Bomb";
description = "Magic bombs leave behind damaging venom.";
apply = function(ref_statistics_final)
ref_statistics_final.VENOM_BOMB = true
end;
};
["manathief"] = {
title = "Mana Thief";
description = "Basic attacks steal Mana.";
onDamageGiven = function(sourceManifest, sourceType, sourceId, targetManifest, ref_damageData)
if ref_damageData.sourceType == "equipment" then
if targetManifest and sourceManifest:FindFirstChild("mana") and sourceManifest:FindFirstChild("maxMana") then
if targetManifest:FindFirstChild("mana") and targetManifest:FindFirstChild("maxMana") then
sourceManifest.mana.Value = math.clamp(sourceManifest.mana.Value + 5, 0, sourceManifest.maxMana.Value)
targetManifest.mana.Value = math.clamp(targetManifest.mana.Value - 5, 0, targetManifest.maxMana.Value)
return true
else
sourceManifest.mana.Value = math.clamp(sourceManifest.mana.Value + 2, 0, sourceManifest.maxMana.Value)
end
end
end
end;
};
}
}
return perkData
================================================
FILE: src/ReplicatedStorage/professionLookup/fishing.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local projectile = modules.load("projectile")
local placeSetup = modules.load("placeSetup")
local client_utilities = modules.load("client_utilities")
local network = modules.load("network")
local abilityData = {
--> identifying information <--
id = 1;
--> generic information <--
name = "Fishing";
image = "rbxassetid://2585107511";
color = Color3.fromRGB(9, 79, 113);
description = "Catch some fish";
}
return abilityData
================================================
FILE: src/ReplicatedStorage/professionLookup/init.lua
================================================
local lookupTable = {} do
for i, professionDataModule in pairs(script:GetChildren()) do
local professionData = require(professionDataModule)
-- internal stuff
professionData.module = professionDataModule
-- hook ups
lookupTable[professionData.id] = professionData
lookupTable[professionDataModule.Name] = professionData
end
end
return lookupTable
================================================
FILE: src/ReplicatedStorage/questLookupNew/init.lua
================================================
local lookupTable = {}
for i, questModule in pairs(script:GetChildren()) do
local questData = require(questModule)
if questData then
if lookupTable[questData.id] then
warn("QuestId: " .. questData.id .. " already taken.")
else
lookupTable[questData.id] = questData
end
end
end
return lookupTable
================================================
FILE: src/ReplicatedStorage/questLookupNew/testQuest.lua
================================================
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Modules = require(ReplicatedStorage.modules)
local Network = Modules.load("network")
local questData = {
id = 1;
name = "Test Quest";
questType = "findAndReturn";
steps = {
[1] = {
stepData = {
stepType = "findAndReturn";
objective = "Apple"; -- Item name.
timeLimit = 0; -- Time in seconds, 0 = Infinite
};
};
[2] = {
stepData = {
stepType = "killMonster";
objective = "Baby Shroom"; -- Monster name.
timeLimit = 0; -- Time in seconds, 0 = Infinite
};
};
};
startData = {
triggerType = "npc";
trigger = "Test NPC";
};
finishData = {
triggerType = "npcReturn";
trigger = "Test NPC";
};
}
function questData:beginQuest(player)
end
function questData:beginQuestServer(player)
end
function questData:endQuest(player)
end
function questData:endQuestServer(player)
end
return questData
================================================
FILE: src/ReplicatedStorage/sounds.lua
================================================
-- All Vesteria sound effects
-- made with tools/serializer.lua
return {
["potion"] = {
Volume = 1,
SoundId = "rbxassetid://2095021574",
},
["pickup"] = {
PlaybackSpeed = 1.7,
Volume = 0.1,
SoundId = "rbxassetid://2094665072",
},
["error"] = {
Volume = 0.2,
SoundId = "rbxassetid://3209996586",
},
["coins"] = {
SoundId = "rbxassetid://607665037",
},
["scrollSuccess"] = {
MaxDistance = 150,
Volume = 1,
EmitterSize = 5,
SoundId = "rbxassetid://2396595172",
},
["bowFireStance"] = {
MaxDistance = 270,
Volume = 4,
EmitterSize = 2,
SoundId = "rbxassetid://4731966529",
},
["rareItemDrop"] = {
Volume = 1,
SoundId = "rbxassetid://2396499850",
},
["rareItemPickup"] = {
Volume = 1,
SoundId = "rbxassetid://2396558969",
},
["giantEnemySpawned"] = {
Volume = 1,
SoundId = "rbxassetid://4834146449",
},
["giantEnemyBoom"] = {
MaxDistance = 400,
Volume = 2,
EmitterSize = 20,
SoundId = "rbxassetid://2401259303",
},
["door"] = {
SoundId = "rbxassetid://2414883467",
},
["water_out"] = {
PlaybackSpeed = 1.1,
MaxDistance = 65,
Volume = 1,
EmitterSize = 1,
SoundId = "rbxassetid://2480953100",
},
["water_in"] = {
PlaybackSpeed = 1.1,
MaxDistance = 65,
Volume = 1,
EmitterSize = 1,
SoundId = "rbxassetid://2480952670",
},
["scrollFail"] = {
MaxDistance = 150,
Volume = 1,
EmitterSize = 5,
SoundId = "rbxassetid://2396595731",
},
["footstep_dirt"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491169979",
},
["footstep_dirt2"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491170122",
},
["footstep_dirt3"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491170245",
},
["footstep_grass"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491170395",
},
["footstep_sand2"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491170805",
},
["footstep_grass2"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491170501",
},
["footstep_sand"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491171031",
},
["footstep_grass3"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491170625",
},
["footstep_sand3"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491170886",
},
["footstep_snow"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491172015",
},
["footstep_snow2"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491172469",
},
["footstep_snow3"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491172586",
},
["footstep_stone"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491172765",
},
["footstep_stone2"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491172866",
},
["footstep_stone3"] = {
MaxDistance = 150,
Volume = 0.1,
EmitterSize = 4,
SoundId = "rbxassetid://2491172965",
},
["chest_unlock"] = {
Volume = 1,
SoundId = "rbxassetid://2564067410",
},
["fireIgnite"] = {
MaxDistance = 200,
Volume = 1,
EmitterSize = 5,
SoundId = "rbxassetid://2564068060",
},
["fireLoop"] = {
MaxDistance = 150,
Looped = true,
SoundId = "rbxassetid://2564068359",
},
["chest_reward"] = {
Volume = 1,
SoundId = "rbxassetid://2564067500",
},
["fishing_BaitSplash"] = {
MaxDistance = 400,
Volume = 0.6,
SoundId = "rbxassetid://2577401402",
},
["fishing_FishBite"] = {
MaxDistance = 250,
Volume = 0.2,
SoundId = "rbxassetid://2577401252",
},
["fishingPoleCast_Short"] = {
MaxDistance = 250,
Volume = 0.2,
SoundId = "rbxassetid://2577400844",
},
["bowArrowImpact"] = {
MaxDistance = 250,
Volume = 1,
EmitterSize = 2,
SoundId = "rbxassetid://2765142756",
},
["eat_food"] = {
SoundId = "rbxassetid://2452054634",
},
["spiderQueenScreech"] = {
Volume = 1.2,
EmitterSize = 30,
SoundId = "rbxassetid://2686159346",
},
["spiderQueenRotating"] = {
SoundId = "rbxassetid://2686159185",
},
["spiderQueenAbilityUse"] = {
SoundId = "rbxassetid://2686159040",
},
["spiderQueenSpawn"] = {
Volume = 1.5,
EmitterSize = 30,
SoundId = "rbxassetid://2686159459",
},
["bowFire"] = {
MaxDistance = 270,
Volume = 0.2,
EmitterSize = 2,
SoundId = "rbxassetid://2765142550",
},
["bowDraw"] = {
MaxDistance = 120,
Volume = 0.2,
EmitterSize = 2,
SoundId = "rbxassetid://2765142358",
},
["fishing_FishSplashOutOfWater"] = {
MaxDistance = 400,
SoundId = "rbxassetid://2577401081",
},
["npc_male_old_mhm"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820828299",
},
["npc_male_old_uh"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820828833",
},
["npc_male_old_ouh"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820828598",
},
["npc_female_hmm4"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820823423",
},
["npc_female_huh"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820823573",
},
["npc_female_laugh"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820823688",
},
["npc_female_uhuh"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820823809",
},
["npc_female_ah"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820823938",
},
["npc_female_aha"] = {
MaxDistance = 100,
Volume = 0.4,
EmitterSize = 3,
SoundId = "rbxassetid://2820824109",
},
["npc_female_aha2"] = {
MaxDistance = 100,
Volume = 0.6,
EmitterSize = 3,
SoundId = "rbxassetid://2820824290",
},
["npc_male_grunt"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820824651",
},
["npc_male_ahaa"] = {
MaxDistance = 100,
EmitterSize = 3,
SoundId = "rbxassetid://2820824774",
},
["npc_male_hm"] = {
MaxDistance = 100,
Volume = 0.6,
EmitterSize = 3,
SoundId = "rbxassetid://2820824979",
},
["npc_male_hmph"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820825124",
},
["npc_male_aah"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820825274",
},
["npc_tiny_laugh"] = {
PlaybackSpeed = 1.7,
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820825450",
},
["npc_male_ah"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820825661",
},
["npc_male_aha"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820825947",
},
["npc_male_aha2"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820826277",
},
["npc_male_eh"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820826734",
},
["npc_male_grunt2"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820826986",
},
["npc_male_huh"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820827159",
},
["npc_male_huh2"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820827354",
},
["npc_male_laugh2"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820828025",
},
["npc_female_hmm"] = {
MaxDistance = 100,
Volume = 0.6,
EmitterSize = 3,
SoundId = "rbxassetid://2820822988",
},
["npc_female_hmm2"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820823153",
},
["npc_female_hmm3"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820823291",
},
["stranger_greeting"] = {
MaxDistance = 200,
Volume = 1,
EmitterSize = 5,
SoundId = "rbxassetid://2863573429",
},
["npc_male_hobo"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2885930060",
},
["ladder"] = {
Volume = 0.6,
SoundId = "rbxassetid://2886528596",
},
["npc_male_burp"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2886886266",
},
["itemEnchanted"] = {
Volume = 1,
SoundId = "rbxassetid://3049829444",
},
["kill"] = {
MaxDistance = 400,
Volume = 1,
SoundId = "rbxassetid://5273351117",
},
["blink"] = {
MaxDistance = 150,
Volume = 0.6,
EmitterSize = 5,
SoundId = "rbxassetid://2621111307",
},
["theYetiSpawn"] = {
MaxDistance = 1000002,
Volume = 0.8,
EmitterSize = 500,
SoundId = "rbxassetid://3179288791",
},
["ethyr4"] = {
Volume = 1,
SoundId = "rbxassetid://3188891018",
},
["ethyr3"] = {
Volume = 1,
SoundId = "rbxassetid://3188890637",
},
["ethyr2"] = {
Volume = 1,
SoundId = "rbxassetid://3188890819",
},
["ethyr1"] = {
Volume = 1,
SoundId = "rbxassetid://3188890464",
},
["bounce"] = {
MaxDistance = 150,
SoundId = "rbxassetid://2103090015",
},
["npc_male_owh"] = {
PlaybackSpeed = 1.5,
MaxDistance = 100,
Volume = 1,
EmitterSize = 3,
SoundId = "rbxassetid://3233161636",
},
["legendaryItemDrop"] = {
Volume = 1,
SoundId = "rbxassetid://3293384571",
},
["legendaryItemPickup"] = {
Volume = 1,
SoundId = "rbxassetid://3293384131",
},
["questComplete"] = {
Volume = 0.1,
SoundId = "rbxassetid://3293432741",
},
["idolDrop"] = {
Volume = 1.2,
SoundId = "rbxassetid://3373168511",
},
["idolPickup"] = {
Volume = 1,
SoundId = "rbxassetid://3373168139",
},
["pvp"] = {
Volume = 0.3,
EmitterSize = 40,
SoundId = "rbxassetid://3454331891",
},
["npc_male_laugh"] = {
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820825450",
},
["npc_female_burp"] = {
PlaybackSpeed = 1.2,
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2886886266",
},
["npc_tiny_hmph"] = {
PlaybackSpeed = 1.2,
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820825124",
},
["npc_tiny_grunt"] = {
PlaybackSpeed = 1.5,
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820824651",
},
["npc_tiny_eh"] = {
PlaybackSpeed = 1.3,
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820826734",
},
["npc_female_laugh_tiny"] = {
PlaybackSpeed = 1.5,
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820823688",
},
["npc_female_huh_tiny"] = {
PlaybackSpeed = 1.5,
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820823573",
},
["npc_male_hm_tiny"] = {
PlaybackSpeed = 1.7,
MaxDistance = 100,
Volume = 0.6,
EmitterSize = 3,
SoundId = "rbxassetid://2820824979",
},
["npc_female_hmm_tiny"] = {
PlaybackSpeed = 1.5,
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820822988",
},
["npc_male_old_uh_tiny"] = {
PlaybackSpeed = 1.5,
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820828833",
},
["npc_male_grunt_kid"] = {
PlaybackSpeed = 1.3,
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820824651",
},
["npc_female_laugh_kid"] = {
PlaybackSpeed = 1.1,
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820823688",
},
["magicAttack"] = {
MaxDistance = 100,
Volume = 0.6,
EmitterSize = 5,
SoundId = "rbxassetid://3663291385",
},
["item_mana"] = {
SoundId = "rbxassetid://3663291828",
},
["item_heal"] = {
Volume = 0.3,
SoundId = "rbxassetid://3663293574",
},
["item_buff"] = {
SoundId = "rbxassetid://3663292263",
},
["stat_point"] = {
Volume = 0.6,
SoundId = "rbxassetid://3663293127",
},
["stat_perk"] = {
SoundId = "rbxassetid://3663292694",
},
["lightning"] = {
MaxDistance = 120,
Volume = 0.2,
EmitterSize = 3,
SoundId = "rbxassetid://2620389369",
},
["splash"] = {
PlaybackSpeed = 2,
MaxDistance = 65,
Volume = 1.7,
EmitterSize = 1,
SoundId = "rbxassetid://2480952670",
},
["npc_female_huh_gross"] = {
PlaybackSpeed = 0.8,
MaxDistance = 100,
Volume = 0.8,
EmitterSize = 3,
SoundId = "rbxassetid://2820823573",
},
["questTurnedIn"] = {
PlaybackSpeed = 0.8,
Volume = 0.6,
SoundId = "rbxassetid://3663292694",
},
["npc_asnelar_hiss"] = {
MaxDistance = 100,
Volume = 0.4,
EmitterSize = 3,
SoundId = "rbxassetid://3236705213",
},
["npc_asnelar_laugh"] = {
MaxDistance = 120,
EmitterSize = 3,
SoundId = "rbxassetid://3236705652",
},
["npc_moglo_grunt"] = {
PlaybackSpeed = 1.1,
MaxDistance = 100,
Volume = 1.5,
EmitterSize = 5,
SoundId = "rbxassetid://2551318823",
},
["bubbles"] = {
MaxDistance = 300,
EmitterSize = 6,
SoundId = "rbxassetid://2669521458",
},
["DEATH"] = {
Volume = 1,
SoundId = "rbxassetid://2820640568",
},
["bush3"] = {
MaxDistance = 150,
Volume = 1,
SoundId = "rbxassetid://4831368756",
},
["shunpoCast"] = {
MaxDistance = 150,
Volume = 1,
EmitterSize = 5,
SoundId = "rbxassetid://4621088159",
},
["shunpoImpact"] = {
MaxDistance = 150,
Volume = 1,
EmitterSize = 5,
SoundId = "rbxassetid://4621088322",
},
["orbLightning1"] = {
SoundId = "rbxassetid://821439273",
},
["orbLightning2"] = {
SoundId = "rbxassetid://4818807812",
},
["bush2"] = {
MaxDistance = 150,
Volume = 1,
SoundId = "rbxassetid://4831368446",
},
["bush1"] = {
MaxDistance = 150,
Volume = 1,
SoundId = "rbxassetid://4831368165",
},
["swoosh_soft"] = {
PlaybackSpeed = 1.5,
Volume = 0.2,
SoundId = "rbxassetid://997701840",
},
["itemDrop"] = {
MaxDistance = 250,
Volume = 1,
EmitterSize = 5,
SoundId = "rbxassetid://5273348701",
},
["restore"] = {
PlaybackSpeed = 1.2,
SoundId = "rbxassetid://3763842006",
},
["swoosh"] = {
PlaybackSpeed = 2,
Volume = 0.3,
SoundId = "rbxassetid://997701840",
},
["boom"] = {
MaxDistance = 400,
Volume = 0.6,
SoundId = "rbxassetid://3066286483",
},
}
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/ablaze.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
local utilities = modules.load("utilities")
local debris = game:GetService("Debris")
local statusEffectData = {
--> identifying information <--
id = 10;
--> generic information <--
name = "Ablaze";
activeEffectName = "Ablaze";
styleText = "On fire (and not in a good way).";
description = "";
image = "rbxassetid://2528902271";
notSavedToPlayerData = true,
}
local MAX_DAMAGE_PER_SECOND = 256
function statusEffectData.onStarted_server(activeStatusEffectData, entityManifest)
local emitterAttachment = Instance.new("Attachment")
emitterAttachment.Position = Vector3.new(0, 0, 0)
emitterAttachment.Parent = entityManifest
local emitter = script.emitter:Clone()
emitter.Parent = emitterAttachment
activeStatusEffectData.__emitterAttachment = emitterAttachment
end
function statusEffectData.onEnded_server(activeStatusEffectData, entityManifest)
local emitterAttachment = activeStatusEffectData.__emitterAttachment
if not emitterAttachment then return end
local emitter = emitterAttachment:FindFirstChild("emitter")
if not emitter then return end
emitter.Enabled = false
debris:AddItem(emitterAttachment, emitter.Lifetime.Max)
end
function statusEffectData.execute(activeStatusEffectData, entityManifest, ticksPerSecond)
local maxHealth = entityManifest:FindFirstChild("maxHealth")
if not maxHealth then return end
local damage = maxHealth.Value * activeStatusEffectData.statusEffectModifier.percent
local duration = activeStatusEffectData.statusEffectModifier.duration
local dps = math.min(damage / duration, MAX_DAMAGE_PER_SECOND)
local damage = dps / ticksPerSecond
if damage <= 0 then return end
local entityType = entityManifest:FindFirstChild("entityType")
if not entityType then return end
entityType = entityType.Value
local sourceGuid = activeStatusEffectData.sourceEntityGUID
if not sourceGuid then return end
local source = utilities.getEntityManifestByEntityGUID(sourceGuid)
if not source then return end
local sourceType = source:FindFirstChild("entityType")
if not sourceType then return end
sourceType = sourceType.Value
if sourceType ~= "character" then
return
end
local char = source.Parent
local player = game.Players:GetPlayerFromCharacter(char)
if not player then return end
local damageData = {
damage = damage,
sourceType = "status",
sourceId = statusEffectData.id,
damageType = "magical",
sourcePlayerId = player.UserId,
sourceEntityGUID = activeStatusEffectData.sourceEntityGUID,
}
if entityType == "monster" then
network:invoke("monsterDamageRequest_server", player, entityManifest, damageData)
else
network:invoke("playerDamageRequest_server", player, entityManifest, damageData)
end
-- remove this if my boy is dead
local state = entityManifest:FindFirstChild("state")
if not state then return end
state = state.Value
if state == "dead" then
statusEffectData.onEnded_server(activeStatusEffectData, entityManifest)
end
end
return statusEffectData
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/empower.lua
================================================
local statusEffectData = {
--> identifying information <--
id = 4;
--> generic information <--
name = "Empowered";
activeEffectName = "Empowered";
styleText = "This unit is empowered.";
description = "";
image = "rbxassetid://2528902271";
}
-- (renderCharacterContainer, targetPosition, isAbilitySource, hitNormal, nil, guid)
function statusEffectData:execute()
end
return statusEffectData
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/explode.lua
================================================
local statusEffectData = {
--> identifying information <--
id = 5;
--> generic information <--
name = "Explode";
activeEffectName = "Exploding";
styleText = "This unit is exploding.";
description = "";
image = "rbxassetid://2528902271";
--
statusEffectApplicationData = {
duration = 8;
}
}
-- explode status effect works like this:
-- duration refers to the wind-up time BEFORE THE EXPLOSION!
-- when the effect is ADDED, the user will see the windup particle effects
-- when the effect is REMOVED, the explosion effect will actually occur and deal whatever damage
function statusEffectData.__clientApplyStatusEffectOnCharacter(renderCharacterContainer)
if not renderCharacterContainer or not renderCharacterContainer:FindFirstChild("entity") then return false end
end
function statusEffectData.__clientApplyTransitionEffectOnCharacter(renderCharacterContainer)
-- do nothing here
end
function statusEffectData.__clientRemoveStatusEffectOnCharacter(renderCharacterContainer)
if not renderCharacterContainer or not renderCharacterContainer:FindFirstChild("entity") then return false end
end
function statusEffectData._serverExecutionFunction(activeStatusEffectData, entityManifest)
end
function statusEffectData._serverCleanupFunction(activeStatusEffectData, entityManifest)
end
function statusEffectData.onStarted_server(activeStatusEffectData, entityManifest)
if entityManifest then
end
end
function statusEffectData.onEnded_server(activeStatusEffectData, entityManifest)
if entityManifest then
end
end
return statusEffectData
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/heat exhausted.lua
================================================
local statusEffectData = {
--> identifying information <--
id = 18;
--> generic information <--
name = "Heat Exhausted";
activeEffectName = "Heat Exhausted";
styleText = "Suffering from heat exhaustion.";
description = "";
image = "rbxassetid://334869557";
hideInStatusBar = true,
notSavedToPlayerData = true,
}
function statusEffectData.execute(activeStatusEffectData, entityManifest, ticksPerSecond)
-- purely handled by the client
end
return statusEffectData
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/init.lua
================================================
--[[
abilityData {}
--> identifying information <--
int abilityId
--> generic information <--
string name
string image
string description
--> execution information <--
function execute
number maxRank
number cooldown
number cost
table prerequisite
prerequisiteData {}
number abilityId
number points
--]]
local lookupTable = {} do
for _, statusEffectDataModule in pairs(script:GetChildren()) do
local statusEffectData = require(statusEffectDataModule)
-- internal stuff
statusEffectData.module = statusEffectDataModule
-- hook ups
lookupTable[statusEffectData.id] = statusEffectData
lookupTable[statusEffectDataModule.Name] = statusEffectData
end
end
return lookupTable
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/mystically bound.lua
================================================
local statusEffectData = {
--> identifying information <--
id = 6;
--> generic information <--
name = "Mystically Bound";
activeEffectName = "Mystically Bound";
styleText = "Mystically bound in place.";
description = "";
image = "rbxassetid://2528902271";
}
-- (renderCharacterContainer, targetPosition, isAbilitySource, hitNormal, nil, guid)
function statusEffectData.execute(activeStatusEffectData, entityManifest, activeStatusEffectTickTimePerSecond)
end
function statusEffectData.onStarted_server(activeStatusEffectData, entityManifest)
local binding = Instance.new("BodyPosition")
binding.Name = "MysticallyBoundBindingForce"
binding.Position = entityManifest.Position + Vector3.new(0, 8, 0)
binding.MaxForce = Vector3.new(1e9, 1e9, 1e9)
binding.Parent = entityManifest
activeStatusEffectData.__binding = binding
end
function statusEffectData.onEnded_server(activeStatusEffectData, entityManifest)
if not activeStatusEffectData.__binding then
warn("CRITICAL ERROR WITH MYSTICALLY BOUND\nCouldn't find binding force!")
return
end
activeStatusEffectData.__binding:Destroy()
end
return statusEffectData
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/poison.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
local utilities = modules.load("utilities")
local statusEffectData = {
--> identifying information <--
id = 3;
--> generic information <--
name = "Poisoned";
activeEffectName = "Poisoned";
styleText = "Poisoned and taking damage over time.";
description = "";
image = "rbxassetid://2528902271";
}
--function statusEffectData.execute_old(playerStatusEffectData, targetEntity, ticksPerSecond)
-- local healthLost = playerStatusEffectData.statusEffectModifier.healthLost or 10
-- local duration = playerStatusEffectData.statusEffectModifier.duration or 5
--
-- if targetEntity:FindFirstChild("health") and targetEntity:FindFirstChild("maxHealth") then
-- targetEntity.health.Value = math.clamp(targetEntity.health.Value - healthLost / duration / ticksPerSecond, 0, targetEntity.maxHealth.Value)
-- end
--end
function statusEffectData.execute(activeStatusEffectData, entityManifest, ticksPerSecond)
local totalDamage = activeStatusEffectData.statusEffectModifier.healthLost
local duration = activeStatusEffectData.statusEffectModifier.duration
local dps = totalDamage / duration
local damage = dps / ticksPerSecond
local entityType = entityManifest:FindFirstChild("entityType")
if not entityType then return end
entityType = entityType.Value
local sourceGuid = activeStatusEffectData.sourceEntityGUID
if not sourceGuid then return end
local source = utilities.getEntityManifestByEntityGUID(sourceGuid)
if not source then return end
local sourceType = source:FindFirstChild("entityType")
if not sourceType then return end
sourceType = sourceType.Value
if sourceType ~= "character" then
return
end
local char = source.Parent
local player = game.Players:GetPlayerFromCharacter(char)
if not player then return end
local damageData = {
damage = damage,
sourceType = "status",
sourceId = statusEffectData.id,
damageType = "magical",
sourcePlayerId = player.UserId,
}
if entityType == "monster" then
network:invoke("monsterDamageRequest_server", player, entityManifest, damageData)
else
network:invoke("playerDamageRequest_server", player, entityManifest, damageData)
end
end
function statusEffectData.__clientApplyStatusEffectOnCharacter(renderCharacterContainer)
if not renderCharacterContainer or not renderCharacterContainer:FindFirstChild("entity") then return false end
end
function statusEffectData.__clientRemoveStatusEffectOnCharacter(renderCharacterContainer)
end
return statusEffectData
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/potion.lua
================================================
local statusEffectData = {
--> identifying information <--
id = 1;
--> generic information <--
name = "Potion";
activeEffectName = "Potioning";
styleText = "Activated a potion.";
description = "";
image = "rbxassetid://2528902271";
}
-- (renderCharacterContainer, targetPosition, isAbilitySource, hitNormal, nil, guid)
function statusEffectData.execute(activeStatusEffectData, entityManifest, activeStatusEffectTickTimePerSecond)
print('WE ARE RUNNING THE POT!')
local healthHealed = activeStatusEffectData.statusEffectModifier.healthToHeal or 0
local manaRestored = activeStatusEffectData.statusEffectModifier.manaToRestore or 0
local duration = activeStatusEffectData.statusEffectModifier.duration or 5
if entityManifest:FindFirstChild("health") and entityManifest:FindFirstChild("maxHealth") and healthHealed > 0 then
entityManifest.health.Value = math.clamp(
entityManifest.health.Value + healthHealed / duration / activeStatusEffectTickTimePerSecond,
0,
entityManifest.maxHealth.Value
)
end
if entityManifest:FindFirstChild("mana") and entityManifest:FindFirstChild("maxMana") and manaRestored > 0 then
entityManifest.mana.Value = math.clamp(
entityManifest.mana.Value + manaRestored / duration / activeStatusEffectTickTimePerSecond,
0,
entityManifest.maxMana.Value
)
end
end
function statusEffectData.onStatusEffectApplied_server(player)
end
function statusEffectData.onStatusEffectRemoved_server(player)
end
function statusEffectData.onStatusEffectApplied_client(player)
end
function statusEffectData.onStatusEffectRemoved_client(player)
end
return statusEffectData
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/ranger stance.lua
================================================
local abilityAnimations = game:GetService("ReplicatedStorage").assets:WaitForChild("abilityAnimations")
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
local events = modules.load("events")
local debris = game:GetService("Debris")
local itemLookup = require(game.ReplicatedStorage.itemData)
local statusEffectData = {
--> identifying information <--
id = 7;
--> generic information <--
name = "Ranger's Stance";
activeEffectName = "Ranger's Stance";
styleText = "In ranger's stance.";
description = "";
image = "rbxassetid://2528902271";
notSavedToPlayerData = true,
}
local function findTrackByAnimation(animator, animation)
for _, track in pairs(animator:GetPlayingAnimationTracks()) do
if track.Animation == animation then
return track
end
end
return nil
end
function statusEffectData.__clientApplyTransitionEffectOnCharacter(renderCharacterContainer)
local entity = renderCharacterContainer:FindFirstChild("entity")
if not entity then return end
local animator = entity:FindFirstChild("AnimationController")
if not animator then return end
local startTrack = animator:LoadAnimation(abilityAnimations.rangerStanceStarting)
startTrack.Looped = true
startTrack:Play()
delay(startTrack.Length - 0.05, function()
if not startTrack.IsPlaying then return end
local idleTrack = animator:LoadAnimation(abilityAnimations.rangerStanceIdling)
idleTrack:Play(0)
startTrack:Stop(0)
end)
end
function statusEffectData.__clientRemoveStatusEffectOnCharacter(renderCharacterContainer)
local entity = renderCharacterContainer:FindFirstChild("entity")
if not entity then return end
local animator = entity:FindFirstChild("AnimationController")
if not animator then return end
local leaveTrack = animator:LoadAnimation(abilityAnimations.rangerStanceExiting)
leaveTrack:Play(0)
local startTrack = findTrackByAnimation(animator, abilityAnimations.rangerStanceStarting)
if startTrack then
startTrack:Stop(0)
end
local idleTrack = findTrackByAnimation(animator, abilityAnimations.rangerStanceIdling)
if idleTrack then
idleTrack:Stop(0)
end
end
function statusEffectData.onStarted_server(activeStatusEffectData, entityManifest)
local emitterAttachment = Instance.new("Attachment")
emitterAttachment.Position = Vector3.new(0, -2, 0)
emitterAttachment.Parent = entityManifest
local emitter = script.emitter:Clone()
emitter.Parent = emitterAttachment
activeStatusEffectData.__emitterAttachment = emitterAttachment
activeStatusEffectData.__eventGuid = events:registerForEvent("playerEquipmentChanged", function(player)
local char = player.Character
if not char then return end
local manifest = char.PrimaryPart
if not manifest then return end
if manifest == entityManifest then
network:invoke("revokeStatusEffectByStatusEffectGUID", activeStatusEffectData.statusEffectGUID)
end
end)
end
function statusEffectData.onEnded_server(activeStatusEffectData, entityManifest)
local emitterAttachment = activeStatusEffectData.__emitterAttachment
if not emitterAttachment then return end
local emitter = emitterAttachment:FindFirstChild("emitter")
if not emitter then return end
emitter.Enabled = false
debris:AddItem(emitterAttachment, emitter.Lifetime.Max)
events:deregisterEventByGuid(activeStatusEffectData.__eventGuid)
end
function statusEffectData.execute(activeStatusEffectData, entityManifest, ticksPerSecond)
local manaCost = activeStatusEffectData.statusEffectModifier.manaCost / ticksPerSecond
local mana = entityManifest:FindFirstChild("mana")
if not mana then return end
if mana.Value >= manaCost then
mana.Value = mana.Value - manaCost
else
network:invoke("revokeStatusEffectByStatusEffectGUID", activeStatusEffectData.statusEffectGUID)
end
-- revoke instantly if we're not holding a bow, ok?
local player = game.Players:GetPlayerFromCharacter(entityManifest.Parent)
if player then
local playerData = network:invoke("getPlayerData", player)
local equipment = playerData.equipment
for _, equipmentInfo in pairs(equipment) do
if equipmentInfo.position == 1 then
local itemData = itemLookup[equipmentInfo.id]
if itemData then
if itemData.equipmentType ~= "bow" then
network:invoke("revokeStatusEffectByStatusEffectGUID", activeStatusEffectData.statusEffectGUID)
end
end
end
end
end
end
return statusEffectData
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/refreshed.lua
================================================
local statusEffectData = {
--> identifying information <--
id = 19;
--> generic information <--
name = "Refreshed";
activeEffectName = "Refreshed";
styleText = "Hydrated and refreshed.";
description = "";
image = "rbxassetid://3298789929";
notSavedToPlayerData = true,
}
function statusEffectData.execute(activeStatusEffectData, entityManifest, ticksPerSecond)
end
return statusEffectData
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/regenerate.lua
================================================
local statusEffectData = {
--> identifying information <--
id = 1;
--> generic information <--
name = "Regeneration";
activeEffectName = "Regenerating";
styleText = "Regenerating health.";
description = "";
image = "rbxassetid://2528902271";
}
function statusEffectData.execute(activeStatusEffectData, entityManifest, activeStatusEffectTickTimePerSecond)
local healthHealed = activeStatusEffectData.statusEffectModifier.healthToHeal or 10
local duration = activeStatusEffectData.statusEffectModifier.duration or 5
local health = entityManifest:FindFirstChild("health")
local maxHealth = entityManifest:FindFirstChild("maxHealth")
if health and maxHealth then
health.Value = math.clamp(health.Value + healthHealed / duration / activeStatusEffectTickTimePerSecond, 0, maxHealth.Value)
end
end
return statusEffectData
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/stealth.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
local events = modules.load("events")
local statusEffectData = {
--> identifying information <--
id = 5;
--> generic information <--
name = "Stealth";
activeEffectName = "Stealthed";
styleText = "This unit is stealthed.";
description = "";
image = "rbxassetid://2528902271";
--
statusEffectApplicationData = {
duration = 8;
}
}
local effectTagName = "stealthStatusEffectVisualEffectTag"
function statusEffectData.__clientApplyStatusEffectOnCharacter(renderCharacterContainer)
if not renderCharacterContainer or not renderCharacterContainer:FindFirstChild("entity") then return false end
-- hide stealthed units
local transparencyToSetTo = 1 do
if game.Players.LocalPlayer.Character and game.Players.LocalPlayer.Character.PrimaryPart then
local isClient = renderCharacterContainer.clientHitboxToServerHitboxReference.Value == game.Players.LocalPlayer.Character.PrimaryPart
if isClient then
-- what user sees
transparencyToSetTo = 0.8
else
transparencyToSetTo = 1
end
end
end
-- local tag = renderCharacterContainer.entity.PrimaryPart:FindFirstChild(effectTagName)
-- if not tag then return end
for i, obj in pairs(renderCharacterContainer.entity:GetChildren()) do
if obj:IsA("BasePart") and obj ~= renderCharacterContainer.entity.PrimaryPart then
obj.Transparency = transparencyToSetTo
obj.Material = Enum.Material.Glass
end
end
end
function statusEffectData.__clientApplyTransitionEffectOnCharacter(renderCharacterContainer)
if not renderCharacterContainer or not renderCharacterContainer:FindFirstChild("entity") then return false end
-- hide stealthed units
local transparencyToSetTo = 1 do
if game.Players.LocalPlayer.Character and game.Players.LocalPlayer.Character.PrimaryPart then
local isClient = renderCharacterContainer.clientHitboxToServerHitboxReference.Value == game.Players.LocalPlayer.Character.PrimaryPart
if isClient then
-- what user sees
transparencyToSetTo = 0.8
else
transparencyToSetTo = 1
end
end
end
local tag = Instance.new("BoolValue")
tag.Name = effectTagName
tag.Parent = renderCharacterContainer.entity.PrimaryPart
local iters = 10
local models = {}
spawn(function()
for i = 1, iters do
if not tag.Parent then return end
local transparency = transparencyToSetTo * i / iters
for _, obj in pairs(renderCharacterContainer.entity:GetDescendants()) do
if obj:IsA("BasePart") and obj ~= renderCharacterContainer.entity.PrimaryPart then
obj.Transparency = transparency
obj.Material = Enum.Material.Glass
elseif obj:IsA("RopeConstraint") then
obj.Visible = false
end
end
wait(1 / 30)
end
statusEffectData.__clientApplyStatusEffectOnCharacter(renderCharacterContainer)
end)
end
function statusEffectData.__clientRemoveStatusEffectOnCharacter(renderCharacterContainer)
local tag = renderCharacterContainer.entity.PrimaryPart:FindFirstChild(effectTagName)
if tag then
tag:Destroy()
end
for _, obj in pairs(renderCharacterContainer.entity:GetDescendants()) do
if obj:IsA("BasePart") and obj ~= renderCharacterContainer.entity.PrimaryPart then
obj.Transparency = 0
obj.Material = Enum.Material.SmoothPlastic
elseif obj:IsA("RopeConstraint") then
obj.Visible = true
end
end
end
function statusEffectData.onStarted_server(activeStatusEffectData, entityManifest)
local stealthTag = Instance.new("BoolValue", entityManifest)
stealthTag.Name = "isStealthed"
stealthTag.Value = true
if entityManifest and entityManifest:FindFirstChild("entityType") and entityManifest.entityType.Value == "character" then
local stealthedPlayer = game.Players:GetPlayerFromCharacter(entityManifest.Parent)
local function onStealthBroken(player)
if player ~= stealthedPlayer then return end
local char = player.Character
if not char then return end
local manifest = char.PrimaryPart
if not manifest then return end
if manifest == entityManifest then
network:invoke("revokeStatusEffectByStatusEffectGUID", activeStatusEffectData.statusEffectGUID)
end
end
activeStatusEffectData.__eventGuids = {
events:registerForEvent("playerUsedAbility", function(player, abilityId)
local abilitiesById = require(game.ReplicatedStorage.abilityLookup)
local abilityData = abilitiesById[abilityId]
if not abilityData.doesNotBreakStealth then
onStealthBroken(player)
end
end),
events:registerForEvent("playerWillUseBasicAttack", onStealthBroken),
events:registerForEvent("playerWillTakeDamage", onStealthBroken)
}
activeStatusEffectData.__damageEventGuid = events:registerForEvent("playerWillDealDamage", function(player, damageData)
if player ~= stealthedPlayer then return end
if damageData.sourceType == "equipment" then
damageData.damage = damageData.damage * activeStatusEffectData.statusEffectModifier.damageMultiplier
events:deregisterEventByGuid(activeStatusEffectData.__damageEventGuid)
end
end)
end
end
function statusEffectData.onEnded_server(activeStatusEffectData, entityManifest)
if entityManifest:FindFirstChild("isStealthed") then
entityManifest.isStealthed:Destroy()
end
if entityManifest and entityManifest:FindFirstChild("entityType") and entityManifest.entityType.Value == "character" then
for _, guid in pairs(activeStatusEffectData.__eventGuids) do
events:deregisterEventByGuid(guid)
end
delay(1, function()
events:deregisterEventByGuid(activeStatusEffectData.__damageEventGuid)
end)
local player = game.Players:GetPlayerFromCharacter(entityManifest.Parent)
if not player then return end
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart:FindFirstChild("isStealthed") then
player.Character.PrimaryPart.isStealthed:Destroy()
end
end
end
return statusEffectData
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/stunned.lua
================================================
local statusEffectData = {
--> identifying information <--
id = 13;
--> generic information <--
name = "Stunned";
activeEffectName = "Stunned";
styleText = "Stunned.";
description = "";
image = "rbxassetid://2528902271";
}
function statusEffectData:execute()
end
return statusEffectData
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/taunted.lua
================================================
local modules = require(game.ReplicatedStorage.modules)
local network = modules.load("network")
local statusEffectData = {
--> identifying information <--
id = 16;
--> generic information <--
name = "Taunted";
activeEffectName = "Taunted";
styleText = "Taunted and less resilient.";
description = "";
image = "rbxassetid://2528902271";
}
function statusEffectData.execute(activeStatusEffectData, entityManifest, ticksPerSecond)
local target = activeStatusEffectData.statusEffectModifier.target
network:invoke("setMonsterTargetEntity", entityManifest, target, "status", 3)
end
return statusEffectData
================================================
FILE: src/ReplicatedStorage/statusEffectLookup/weakened by venom.lua
================================================
local debris = game:GetService("Debris")
local statusEffectData = {
--> identifying information <--
id = 22;
--> generic information <--
name = "Weakened by Venom";
activeEffectName = "Weakened by Venom";
styleText = "Weakened by the venom of a Stingtail Staff.";
}
function statusEffectData.onStarted_server(activeStatusEffectData, entityManifest)
local emitter = script.emitter:Clone()
emitter.Parent = entityManifest
activeStatusEffectData.__emitter = emitter
end
function statusEffectData.onEnded_server(activeStatusEffectData, entityManifest)
local emitter = activeStatusEffectData.__emitter
if not emitter then return end
emitter.Enabled = false
debris:AddItem(emitter, emitter.Lifetime.Max)
end
return statusEffectData
================================================
FILE: src/ReplicatedStorage/statusEffectsV3/ablaze.lua
================================================
--[[
data {}
int damage
string sourceEntityGUID
]]
local module = {
description = "Lose health over time! Jump into water to put out.";
doesStack = true;
}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local entityUtilities = modules.load("entityUtilities")
local events = modules.load("events")
local activeEffects = {}
function module.__onStatusEffectBegan(statusEffect)
local targetEntity = entityUtilities.getEntityManifestByEntityGUID(statusEffect.targetEntityGUID)
if not targetEntity then return end
local ablazeEmitter = script.emitter:Clone()
ablazeEmitter.Name = "ablazeEmitter"
ablazeEmitter.Parent = targetEntity
activeEffects[statusEffect.guid] = statusEffect
coroutine.wrap(function()
while activeEffects[statusEffect] and statusEffect.isActive do
local targetEntity = entityUtilities.getEntityManifestByEntityGUID(statusEffect.targetEntityGUID)
if targetEntity then
local hitPart, hitPosition, hitNormal, hitMaterial = workspace:FindPartOnRayWithWhitelist(
Ray.new(
targetEntity.Position,
Vector3.new(0, -targetEntity.Size.Y / 2 - 3, 0)
),
{workspace.Terrain}
)
if hitMaterial == Enum.Material.Water then
statusEffect:stop()
break
end
-- how often we checkin?
wait(1.5)
else
break
end
end
end)()
end
function module.__onStatusEffectEnded(statusEffect)
activeEffects[statusEffect.guid] = nil
local targetEntity = entityUtilities.getEntityManifestByEntityGUID(statusEffect.targetEntityGUID)
if not targetEntity then return end
if targetEntity:FindFirstChild("ablazeEmitter") then
targetEntity.ablazeEmitter:Destroy()
end
end
-- note: the statusEffect here is NON-FUNCTIONAL
-- it lost all its functionality, it was JSONified. ONLY READ FROM THE OBJECT.
function module.__onClientStatusEffectBegan(statusEffect)
end
-- note: the statusEffect here is NON-FUNCTIONAL
-- it lost all its functionality, it was JSONified. ONLY READ FROM THE OBJECT.
function module.__onClientStatusEffectEnded(statusEffect)
end
function module.__onStatusEffectTick(statusEffect, t)
local rate = statusEffect.data.damage / statusEffect.data.duration
local targetEntity = entityUtilities.getEntityManifestByEntityGUID(statusEffect.targetEntityGUID)
local sourceEntity = entityUtilities.getEntityManifestByEntityGUID(statusEffect.sourceEntityGUID)
if targetEntity then
-- turn into a table and triggerEvent so that the amount can be modified
-- if applicable
local burnData = {
damage = rate * t * statusEffect.stacks;
sourceType = "status";
sourceId = statusEffect.id;
damageType = "physical";
sourceEntityGUID = statusEffect.sourceEntityGUID;
}
-- apply damage
local entityType = targetEntity:FindFirstChild("entityType")
if not entityType then return end
entityType = entityType.Value
if entityType == "monster" then
network:invoke("monsterDamageRequest_server", nil, targetEntity, burnData)
else
network:invoke("playerDamageRequest_server", nil, targetEntity, burnData)
end
end
end
return module
================================================
FILE: src/ReplicatedStorage/statusEffectsV3/bleed.lua
================================================
local module = {
description = "Heal over time."
}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local events = modules.load("events")
function module.__onStatusEffectBegan(statusEffect, t)
end
function module.__onStatusEffectEnded(statusEffect, t)
end
function module.__onStatusEffectTick(statusEffect, t)
local rate = statusEffect.data.amount / statusEffect.data.duration
local character = statusEffect.entity
if character then
-- turn into a table and triggerEvent so that the amount can be modified
local healingData = {amount = rate * t; isImmuneToReduction = false}
events.triggerEvent("beforeEntityHealed", character, healingData)
-- heal the target
character.Humanoid.Health = character.Humanoid.Health + healingData.amount
print('heal in tick', t, healingData.amount)
events.triggerEvent("afterEntityHealed", character, healingData)
end
end
return module
================================================
FILE: src/ReplicatedStorage/statusEffectsV3/init.lua
================================================
--[[
statusEffect {}
string icon;
]]
================================================
FILE: src/ReplicatedStorage/statusEffectsV3/regenerate.lua
================================================
local module = {
description = "Heal over time."
}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local events = modules.load("events")
function module.__onStatusEffectBegan(statusEffect, t)
end
function module.__onStatusEffectEnded(statusEffect, t)
end
function module.__onStatusEffectTick(statusEffect, t)
local rate = statusEffect.data.amount / statusEffect.data.duration
local character = statusEffect.entity
if character then
-- turn into a table and triggerEvent so that the amount can be modified
local healingData = {amount = rate * t; isImmuneToReduction = false}
events.fireEventLocal("beforeEntityHealed", character, healingData)
-- heal the target
character.Humanoid.Health = character.Humanoid.Health + healingData.amount
print('heal in tick', t, healingData.amount)
events.fireEventLocal("afterEntityHealed", character, healingData)
end
end
return module
================================================
FILE: src/ReplicatedStorage/temporaryEquipment/init.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/ReplicatedStorage/temporaryEquipment/lantern/application.lua
================================================
return function(renderCharacter)
if not renderCharacter:FindFirstChild("LowerTorso") then return false end
if renderCharacter:FindFirstChild("LANTERN_TEMP_EQUIP") then return false end
local lanternModel = script.Parent:Clone()
lanternModel.application:Destroy()
lanternModel.Name = "LANTERN_TEMP_EQUIP"
for i, obj in pairs(lanternModel:GetChildren()) do
if obj:IsA("BasePart") then
obj.CanCollide = false
obj.Anchored = false
end
end
local attachmentMotor = Instance.new("Motor6D")
attachmentMotor.Part0 = renderCharacter.LowerTorso
attachmentMotor.Part1 = lanternModel.Main
attachmentMotor.C1 = CFrame.new(renderCharacter.LowerTorso.Size.X / 2 - 0.1, lanternModel.Main.Size.Y / 2, -0.15) * CFrame.Angles(0, 0, 0) * CFrame.Angles(0, 0, math.pi / 6) + Vector3.new(0, 0.5, 0)
attachmentMotor.Parent = lanternModel.Main
-- lanternModel.Main.BallSocketConstraint.Attachment1 = renderCharacter.LowerTorso.RightHipRigAttachment
-- lanternModel.Main.RopeConstraint.Attachment1 = renderCharacter.LowerTorso.RightHipRigAttachment
lanternModel.Parent = renderCharacter
warn("applying temporary equipment :angrycry:")
end
================================================
FILE: src/ReplicatedStorage/temporaryEquipment/lantern/init.meta.json
================================================
{
"className": "Model",
"ignoreUnknownInstances": true
}
================================================
FILE: src/ServerScriptService/ChatServiceRunner/ChatChannel.lua
================================================
-- // FileName: ChatChannel.lua
-- // Written by: Xsitsu
-- // Description: A representation of one channel that speakers can chat in.
local forceNewFilterAPI = false
local IN_GAME_CHAT_USE_NEW_FILTER_API
do
local textServiceExists = (game:GetService("TextService") ~= nil)
local success, enabled = pcall(function() return UserSettings():IsUserFeatureEnabled("UserInGameChatUseNewFilterAPIV2") end)
local flagEnabled = (success and enabled)
IN_GAME_CHAT_USE_NEW_FILTER_API = (forceNewFilterAPI or flagEnabled) and textServiceExists
end
local module = {}
local modulesFolder = script.Parent
local Chat = game:GetService("Chat")
local RunService = game:GetService("RunService")
local replicatedModules = Chat:WaitForChild("ClientChatModules")
--////////////////////////////// Include
--//////////////////////////////////////
local ChatConstants = require(replicatedModules:WaitForChild("ChatConstants"))
local Util = require(modulesFolder:WaitForChild("Util"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = { Get = function(key,default) return default end } end
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:SendSystemMessage(message, extraData)
local messageObj = self:InternalCreateMessageObject(message, nil, true, extraData)
local success, err = pcall(function() self.eMessagePosted:Fire(messageObj) end)
if not success and err then
print("Error posting message: " ..err)
end
self:InternalAddMessageToHistoryLog(messageObj)
for i, speaker in pairs(self.Speakers) do
speaker:InternalSendSystemMessage(messageObj, self.Name)
end
return messageObj
end
function methods:SendSystemMessageToSpeaker(message, speakerName, extraData)
local speaker = self.Speakers[speakerName]
if (speaker) then
local messageObj = self:InternalCreateMessageObject(message, nil, true, extraData)
speaker:InternalSendSystemMessage(messageObj, self.Name)
else
warn(string.format("Speaker '%s' is not in channel '%s' and cannot be sent a system message", speakerName, self.Name))
end
end
function methods:SendMessageObjToFilters(message, messageObj, fromSpeaker)
local oldMessage = messageObj.Message
messageObj.Message = message
self:InternalDoMessageFilter(fromSpeaker.Name, messageObj, self.Name)
self.ChatService:InternalDoMessageFilter(fromSpeaker.Name, messageObj, self.Name)
local newMessage = messageObj.Message
messageObj.Message = oldMessage
return newMessage
end
function methods:CanCommunicateByUserId(userId1, userId2)
if RunService:IsStudio() then
return true
end
-- UserId is set as 0 for non player speakers.
if userId1 == 0 or userId2 == 0 then
return true
end
local success, canCommunicate = pcall(function()
return Chat:CanUsersChatAsync(userId1, userId2)
end)
return success and canCommunicate
end
function methods:CanCommunicate(speakerObj1, speakerObj2)
local player1 = speakerObj1:GetPlayer()
local player2 = speakerObj2:GetPlayer()
if player1 and player2 then
return self:CanCommunicateByUserId(player1.UserId, player2.UserId)
end
return true
end
function methods:SendMessageToSpeaker(message, speakerName, fromSpeakerName, extraData)
local speakerTo = self.Speakers[speakerName]
local speakerFrom = self.ChatService:GetSpeaker(fromSpeakerName)
if speakerTo and speakerFrom then
local isMuted = speakerTo:IsSpeakerMuted(fromSpeakerName)
if isMuted then
return
end
if not self:CanCommunicate(speakerTo, speakerFrom) then
return
end
-- We need to claim the message is filtered even if it not in this case for compatibility with legacy client side code.
local isFiltered = speakerName == fromSpeakerName
local messageObj = self:InternalCreateMessageObject(message, fromSpeakerName, isFiltered, extraData)
message = self:SendMessageObjToFilters(message, messageObj, fromSpeakerName)
speakerTo:InternalSendMessage(messageObj, self.Name)
--// START FFLAG
if (not IN_GAME_CHAT_USE_NEW_FILTER_API) then --// USES FFLAG
--// OLD BEHAVIOR
local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message, speakerName)
if filteredMessage then
messageObj.Message = filteredMessage
messageObj.IsFiltered = true
speakerTo:InternalSendFilteredMessage(messageObj, self.Name)
end
--// OLD BEHAVIOR
else
--// NEW BEHAVIOR
local textContext = self.Private and Enum.TextFilterContext.PrivateChat or Enum.TextFilterContext.PublicChat
local filterSuccess, isFilterResult, filteredMessage = self.ChatService:InternalApplyRobloxFilterNewAPI(
messageObj.FromSpeaker,
message,
textContext
)
if (filterSuccess) then
messageObj.FilterResult = filteredMessage
messageObj.IsFilterResult = isFilterResult
messageObj.IsFiltered = true
speakerTo:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
end
--// NEW BEHAVIOR
end
--// END FFLAG
else
warn(string.format("Speaker '%s' is not in channel '%s' and cannot be sent a message", speakerName, self.Name))
end
end
function methods:KickSpeaker(speakerName, reason)
local speaker = self.ChatService:GetSpeaker(speakerName)
if (not speaker) then
error("Speaker \"" .. speakerName .. "\" does not exist!")
end
local messageToSpeaker = ""
local messageToChannel = ""
if (reason) then
messageToSpeaker = string.format("You were kicked from '%s' for the following reason(s): %s", self.Name, reason)
messageToChannel = string.format("%s was kicked for the following reason(s): %s", speakerName, reason)
else
messageToSpeaker = string.format("You were kicked from '%s'", self.Name)
messageToChannel = string.format("%s was kicked", speakerName)
end
self:SendSystemMessageToSpeaker(messageToSpeaker, speakerName)
speaker:LeaveChannel(self.Name)
self:SendSystemMessage(messageToChannel)
end
function methods:MuteSpeaker(speakerName, reason, length)
local speaker = self.ChatService:GetSpeaker(speakerName)
if (not speaker) then
error("Speaker \"" .. speakerName .. "\" does not exist!")
end
self.Mutes[speakerName:lower()] = (length == 0 or length == nil) and 0 or (os.time() + length)
if (reason) then
self:SendSystemMessage(string.format("%s was muted for the following reason(s): %s", speakerName, reason))
end
local success, err = pcall(function() self.eSpeakerMuted:Fire(speakerName, reason, length) end)
if not success and err then
print("Error mutting speaker: " ..err)
end
local spkr = self.ChatService:GetSpeaker(speakerName)
if (spkr) then
local success, err = pcall(function() spkr.eMuted:Fire(self.Name, reason, length) end)
if not success and err then
print("Error mutting speaker: " ..err)
end
end
end
function methods:UnmuteSpeaker(speakerName)
local speaker = self.ChatService:GetSpeaker(speakerName)
if (not speaker) then
error("Speaker \"" .. speakerName .. "\" does not exist!")
end
self.Mutes[speakerName:lower()] = nil
local success, err = pcall(function() self.eSpeakerUnmuted:Fire(speakerName) end)
if not success and err then
print("Error unmuting speaker: " ..err)
end
local spkr = self.ChatService:GetSpeaker(speakerName)
if (spkr) then
local success, err = pcall(function() spkr.eUnmuted:Fire(self.Name) end)
if not success and err then
print("Error unmuting speaker: " ..err)
end
end
end
function methods:IsSpeakerMuted(speakerName)
return (self.Mutes[speakerName:lower()] ~= nil)
end
function methods:GetSpeakerList()
local list = {}
for i, speaker in pairs(self.Speakers) do
table.insert(list, speaker.Name)
end
return list
end
function methods:RegisterFilterMessageFunction(funcId, func, priority)
if self.FilterMessageFunctions:HasFunction(funcId) then
error(string.format("FilterMessageFunction '%s' already exists", funcId))
end
self.FilterMessageFunctions:AddFunction(funcId, func, priority)
end
function methods:FilterMessageFunctionExists(funcId)
return self.FilterMessageFunctions:HasFunction(funcId)
end
function methods:UnregisterFilterMessageFunction(funcId)
if not self.FilterMessageFunctions:HasFunction(funcId) then
error(string.format("FilterMessageFunction '%s' does not exists", funcId))
end
self.FilterMessageFunctions:RemoveFunction(funcId)
end
function methods:RegisterProcessCommandsFunction(funcId, func, priority)
if self.ProcessCommandsFunctions:HasFunction(funcId) then
error(string.format("ProcessCommandsFunction '%s' already exists", funcId))
end
self.ProcessCommandsFunctions:AddFunction(funcId, func, priority)
end
function methods:ProcessCommandsFunctionExists(funcId)
return self.ProcessCommandsFunctions:HasFunction(funcId)
end
function methods:UnregisterProcessCommandsFunction(funcId)
if not self.ProcessCommandsFunctions:HasFunction(funcId) then
error(string.format("ProcessCommandsFunction '%s' does not exist", funcId))
end
self.ProcessCommandsFunctions:RemoveFunction(funcId)
end
local function ShallowCopy(table)
local copy = {}
for i, v in pairs(table) do
copy[i] = v
end
return copy
end
function methods:GetHistoryLog()
return ShallowCopy(self.ChatHistory)
end
function methods:GetHistoryLogForSpeaker(speaker)
local userId = -1
local player = speaker:GetPlayer()
if player then
userId = player.UserId
end
local chatlog = {}
--// START FFLAG
if (not IN_GAME_CHAT_USE_NEW_FILTER_API) then --// USES FFLAG
--// OLD BEHAVIOR
for i = 1, #self.ChatHistory do
local logUserId = self.ChatHistory[i].SpeakerUserId
if self:CanCommunicateByUserId(userId, logUserId) then
table.insert(chatlog, ShallowCopy(self.ChatHistory[i]))
end
end
--// OLD BEHAVIOR
else
--// NEW BEHAVIOR
for i = 1, #self.ChatHistory do
local logUserId = self.ChatHistory[i].SpeakerUserId
if self:CanCommunicateByUserId(userId, logUserId) then
local messageObj = ShallowCopy(self.ChatHistory[i])
--// Since we're using the new filter API, we need to convert the stored filter result
--// into an actual string message to send to players for their chat history.
--// System messages aren't filtered the same way, so they just have a regular
--// text value in the Message field.
if (messageObj.MessageType == ChatConstants.MessageTypeDefault or messageObj.MessageType == ChatConstants.MessageTypeMeCommand) then
local filterResult = messageObj.FilterResult
if (messageObj.IsFilterResult) then
if (player) then
messageObj.Message = filterResult:GetChatForUserAsync(player.UserId)
else
messageObj.Message = filterResult:GetNonChatStringForBroadcastAsync()
end
else
messageObj.Message = filterResult
end
end
table.insert(chatlog, messageObj)
end
end
--// NEW BEHAVIOR
end
--// END FFLAG
return chatlog
end
--///////////////// Internal-Use Methods
--//////////////////////////////////////
function methods:InternalDestroy()
for i, speaker in pairs(self.Speakers) do
speaker:LeaveChannel(self.Name)
end
self.eDestroyed:Fire()
self.eDestroyed:Destroy()
self.eMessagePosted:Destroy()
self.eSpeakerJoined:Destroy()
self.eSpeakerLeft:Destroy()
self.eSpeakerMuted:Destroy()
self.eSpeakerUnmuted:Destroy()
end
function methods:InternalDoMessageFilter(speakerName, messageObj, channel)
local filtersIterator = self.FilterMessageFunctions:GetIterator()
for funcId, func, priority in filtersIterator do
local success, errorMessage = pcall(function()
func(speakerName, messageObj, channel)
end)
if not success then
warn(string.format("DoMessageFilter Function '%s' failed for reason: %s", funcId, errorMessage))
end
end
end
function methods:InternalDoProcessCommands(speakerName, message, channel)
local commandsIterator = self.ProcessCommandsFunctions:GetIterator()
for funcId, func, priority in commandsIterator do
local success, returnValue = pcall(function()
local ret = func(speakerName, message, channel)
if type(ret) ~= "boolean" then
error("Process command functions must return a bool")
end
return ret
end)
if not success then
warn(string.format("DoProcessCommands Function '%s' failed for reason: %s", funcId, returnValue))
elseif returnValue then
return true
end
end
return false
end
function methods:InternalPostMessage(fromSpeaker, message, extraData)
if (self:InternalDoProcessCommands(fromSpeaker.Name, message, self.Name)) then return false end
if (self.Mutes[fromSpeaker.Name:lower()] ~= nil) then
local t = self.Mutes[fromSpeaker.Name:lower()]
if (t > 0 and os.time() > t) then
self:UnmuteSpeaker(fromSpeaker.Name)
else
self:SendSystemMessageToSpeaker(ChatLocalization:Get("GameChat_ChatChannel_MutedInChannel","You are muted and cannot talk in this channel"), fromSpeaker.Name)
return false
end
end
local messageObj = self:InternalCreateMessageObject(message, fromSpeaker.Name, false, extraData)
-- allow server to process the unfiltered message string
messageObj.Message = message
local processedMessage
pcall(function()
processedMessage = Chat:InvokeChatCallback(Enum.ChatCallbackType.OnServerReceivingMessage, messageObj)
end)
messageObj.Message = nil
if processedMessage then
-- developer server code's choice to mute the message
if processedMessage.ShouldDeliver == false then
return false
end
messageObj = processedMessage
end
message = self:SendMessageObjToFilters(message, messageObj, fromSpeaker)
local sentToList = {}
for i, speaker in pairs(self.Speakers) do
local isMuted = speaker:IsSpeakerMuted(fromSpeaker.Name)
if not isMuted and self:CanCommunicate(fromSpeaker, speaker) then
table.insert(sentToList, speaker.Name)
if speaker.Name == fromSpeaker.Name then
-- Send unfiltered message to speaker who sent the message.
local cMessageObj = ShallowCopy(messageObj)
cMessageObj.Message = message
cMessageObj.IsFiltered = true
-- We need to claim the message is filtered even if it not in this case for compatibility with legacy client side code.
speaker:InternalSendMessage(cMessageObj, self.Name)
else
speaker:InternalSendMessage(messageObj, self.Name)
end
end
end
local success, err = pcall(function() self.eMessagePosted:Fire(messageObj) end)
if not success and err then
print("Error posting message: " ..err)
end
--// START FFLAG
if (not IN_GAME_CHAT_USE_NEW_FILTER_API) then --// USES FFLAG
--// OLD BEHAVIOR
local filteredMessages = {}
for i, speakerName in pairs(sentToList) do
local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message, speakerName)
if filteredMessage then
filteredMessages[speakerName] = filteredMessage
else
return false
end
end
for i, speakerName in pairs(sentToList) do
local speaker = self.Speakers[speakerName]
if (speaker) then
local cMessageObj = ShallowCopy(messageObj)
cMessageObj.Message = filteredMessages[speakerName]
cMessageObj.IsFiltered = true
speaker:InternalSendFilteredMessage(cMessageObj, self.Name)
end
end
local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message)
if filteredMessage then
messageObj.Message = filteredMessage
else
return false
end
messageObj.IsFiltered = true
self:InternalAddMessageToHistoryLog(messageObj)
--// OLD BEHAVIOR
else
--// NEW BEHAVIOR
local textFilterContext = self.Private and Enum.TextFilterContext.PrivateChat or Enum.TextFilterContext.PublicChat
local filterSuccess, isFilterResult, filteredMessage = self.ChatService:InternalApplyRobloxFilterNewAPI(
messageObj.FromSpeaker,
message,
textFilterContext
)
if (filterSuccess) then
messageObj.FilterResult = filteredMessage
messageObj.IsFilterResult = isFilterResult
else
return false
end
messageObj.IsFiltered = true
self:InternalAddMessageToHistoryLog(messageObj)
for _, speakerName in pairs(sentToList) do
local speaker = self.Speakers[speakerName]
if (speaker) then
speaker:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
end
end
--// NEW BEHAVIOR
end
--// END FFLAG
-- One more pass is needed to ensure that no speakers do not recieve the message.
-- Otherwise a user could join while the message is being filtered who had not originally been sent the message.
local speakersMissingMessage = {}
for _, speaker in pairs(self.Speakers) do
local isMuted = speaker:IsSpeakerMuted(fromSpeaker.Name)
if not isMuted and self:CanCommunicate(fromSpeaker, speaker) then
local wasSentMessage = false
for _, sentSpeakerName in pairs(sentToList) do
if speaker.Name == sentSpeakerName then
wasSentMessage = true
break
end
end
if not wasSentMessage then
table.insert(speakersMissingMessage, speaker.Name)
end
end
end
--// START FFLAG
if (not IN_GAME_CHAT_USE_NEW_FILTER_API) then --// USES FFLAG
--// OLD BEHAVIOR
for _, speakerName in pairs(speakersMissingMessage) do
local speaker = self.Speakers[speakerName]
if speaker then
local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message, speakerName)
if filteredMessage == nil then
return false
end
local cMessageObj = ShallowCopy(messageObj)
cMessageObj.Message = filteredMessage
cMessageObj.IsFiltered = true
speaker:InternalSendFilteredMessage(cMessageObj, self.Name)
end
end
--// OLD BEHAVIOR
else
--// NEW BEHAVIOR
for _, speakerName in pairs(speakersMissingMessage) do
local speaker = self.Speakers[speakerName]
if speaker then
speaker:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
end
end
--// NEW BEHAVIOR
end
--// END FFLAG
return messageObj
end
function methods:InternalAddSpeaker(speaker)
if (self.Speakers[speaker.Name]) then
warn("Speaker \"" .. speaker.name .. "\" is already in the channel!")
return
end
self.Speakers[speaker.Name] = speaker
local success, err = pcall(function() self.eSpeakerJoined:Fire(speaker.Name) end)
if not success and err then
print("Error removing channel: " ..err)
end
end
function methods:InternalRemoveSpeaker(speaker)
if (not self.Speakers[speaker.Name]) then
warn("Speaker \"" .. speaker.name .. "\" is not in the channel!")
return
end
self.Speakers[speaker.Name] = nil
local success, err = pcall(function() self.eSpeakerLeft:Fire(speaker.Name) end)
if not success and err then
print("Error removing speaker: " ..err)
end
end
function methods:InternalRemoveExcessMessagesFromLog()
local remove = table.remove
while (#self.ChatHistory > self.MaxHistory) do
remove(self.ChatHistory, 1)
end
end
function methods:InternalAddMessageToHistoryLog(messageObj)
table.insert(self.ChatHistory, messageObj)
self:InternalRemoveExcessMessagesFromLog()
end
function methods:GetMessageType(message, fromSpeaker)
if fromSpeaker == nil then
return ChatConstants.MessageTypeSystem
end
return ChatConstants.MessageTypeDefault
end
function methods:InternalCreateMessageObject(message, fromSpeaker, isFiltered, extraData)
local messageType = self:GetMessageType(message, fromSpeaker)
local speakerUserId = -1
local speaker = nil
if fromSpeaker then
speaker = self.Speakers[fromSpeaker]
if speaker then
local player = speaker:GetPlayer()
if player then
speakerUserId = player.UserId
else
speakerUserId = 0
end
end
end
local messageObj =
{
ID = self.ChatService:InternalGetUniqueMessageId(),
FromSpeaker = fromSpeaker,
SpeakerUserId = speakerUserId,
OriginalChannel = self.Name,
MessageLength = string.len(message),
MessageType = messageType,
IsFiltered = isFiltered,
Message = isFiltered and message or nil,
--// These two get set by the new API. The comments are just here
--// to remind readers that they will exist so it's not super
--// confusing if they find them in the code but cannot find them
--// here.
--FilterResult = nil,
--IsFilterResult = false,
Time = os.time(),
ExtraData = {},
}
if speaker then
for k, v in pairs(speaker.ExtraData) do
messageObj.ExtraData[k] = v
end
end
if (extraData) then
for k, v in pairs(extraData) do
messageObj.ExtraData[k] = v
end
end
return messageObj
end
function methods:SetChannelNameColor(color)
self.ChannelNameColor = color
for i, speaker in pairs(self.Speakers) do
speaker:UpdateChannelNameColor(self.Name, color)
end
end
function methods:GetWelcomeMessageForSpeaker(speaker)
if self.GetWelcomeMessageFunction then
local welcomeMessage = self.GetWelcomeMessageFunction(speaker)
if welcomeMessage then
return welcomeMessage
end
end
return self.WelcomeMessage
end
function methods:RegisterGetWelcomeMessageFunction(func)
if type(func) ~= "function" then
error("RegisterGetWelcomeMessageFunction must be called with a function.")
end
self.GetWelcomeMessageFunction = func
end
function methods:UnRegisterGetWelcomeMessageFunction()
self.GetWelcomeMessageFunction = nil
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new(vChatService, name, welcomeMessage, channelNameColor)
local obj = setmetatable({}, methods)
obj.ChatService = vChatService
obj.Name = name
obj.WelcomeMessage = welcomeMessage or ""
obj.GetWelcomeMessageFunction = nil
obj.ChannelNameColor = channelNameColor
obj.Joinable = true
obj.Leavable = true
obj.AutoJoin = false
obj.Private = false
obj.Speakers = {}
obj.Mutes = {}
obj.MaxHistory = 200
obj.HistoryIndex = 0
obj.ChatHistory = {}
obj.FilterMessageFunctions = Util:NewSortedFunctionContainer()
obj.ProcessCommandsFunctions = Util:NewSortedFunctionContainer()
-- Make sure to destroy added binadable events in the InternalDestroy method.
obj.eDestroyed = Instance.new("BindableEvent")
obj.eMessagePosted = Instance.new("BindableEvent")
obj.eSpeakerJoined = Instance.new("BindableEvent")
obj.eSpeakerLeft = Instance.new("BindableEvent")
obj.eSpeakerMuted = Instance.new("BindableEvent")
obj.eSpeakerUnmuted = Instance.new("BindableEvent")
obj.MessagePosted = obj.eMessagePosted.Event
obj.SpeakerJoined = obj.eSpeakerJoined.Event
obj.SpeakerLeft = obj.eSpeakerLeft.Event
obj.SpeakerMuted = obj.eSpeakerMuted.Event
obj.SpeakerUnmuted = obj.eSpeakerUnmuted.Event
obj.Destroyed = obj.eDestroyed.Event
return obj
end
return module
================================================
FILE: src/ServerScriptService/ChatServiceRunner/ChatService.lua
================================================
-- // FileName: ChatService.lua
-- // Written by: Xsitsu
-- // Description: Manages creating and destroying ChatChannels and Speakers.
local MAX_FILTER_RETRIES = 3
local FILTER_BACKOFF_INTERVALS = {50/1000, 100/1000, 200/1000}
local MAX_FILTER_DURATION = 60
--- Constants used to decide when to notify that the chat filter is having issues filtering messages.
local FILTER_NOTIFCATION_THRESHOLD = 3 --Number of notifcation failures before an error message is output.
local FILTER_NOTIFCATION_INTERVAL = 60 --Time between error messages.
local FILTER_THRESHOLD_TIME = 60 --If there has not been an issue in this many seconds, the count of issues resets.
local module = {}
local RunService = game:GetService("RunService")
local Chat = game:GetService("Chat")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local modulesFolder = script.Parent
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
local errorTextColor = ChatSettings.ErrorMessageTextColor or Color3.fromRGB(245, 50, 50)
local errorExtraData = {ChatColor = errorTextColor}
--////////////////////////////// Include
--//////////////////////////////////////
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local ChatChannel = require(modulesFolder:WaitForChild("ChatChannel"))
local Speaker = require(modulesFolder:WaitForChild("Speaker"))
local Util = require(modulesFolder:WaitForChild("Util"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:AddChannel(channelName, autoJoin)
if (self.ChatChannels[channelName:lower()]) then
error(string.format("Channel %q alrady exists.", channelName))
end
local function DefaultChannelCommands(fromSpeaker, message)
if (message:lower() == "/leave") then
local channel = self:GetChannel(channelName)
local speaker = self:GetSpeaker(fromSpeaker)
if (channel and speaker) then
if (channel.Leavable) then
speaker:LeaveChannel(channelName)
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatService_YouHaveLeftChannel",
string.format("You have left channel '%s'", channelName)
),
"{RBX_NAME}",channelName),
"System"
)
else
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatService_CannotLeaveChannel","You cannot leave this channel."), channelName)
end
end
return true
end
return false
end
local channel = ChatChannel.new(self, channelName)
self.ChatChannels[channelName:lower()] = channel
channel:RegisterProcessCommandsFunction("default_commands", DefaultChannelCommands, ChatConstants.HighPriority)
local success, err = pcall(function() self.eChannelAdded:Fire(channelName) end)
if not success and err then
print("Error addding channel: " ..err)
end
if autoJoin ~= nil then
channel.AutoJoin = autoJoin
if autoJoin then
for _, speaker in pairs(self.Speakers) do
speaker:JoinChannel(channelName)
end
end
end
return channel
end
function methods:RemoveChannel(channelName)
if (self.ChatChannels[channelName:lower()]) then
local n = self.ChatChannels[channelName:lower()].Name
self.ChatChannels[channelName:lower()]:InternalDestroy()
self.ChatChannels[channelName:lower()] = nil
local success, err = pcall(function() self.eChannelRemoved:Fire(n) end)
if not success and err then
print("Error removing channel: " ..err)
end
else
warn(string.format("Channel %q does not exist.", channelName))
end
end
function methods:GetChannel(channelName)
return self.ChatChannels[channelName:lower()]
end
function methods:AddSpeaker(speakerName)
if (self.Speakers[speakerName:lower()]) then
error("Speaker \"" .. speakerName .. "\" already exists!")
end
local speaker = Speaker.new(self, speakerName)
self.Speakers[speakerName:lower()] = speaker
local success, err = pcall(function() self.eSpeakerAdded:Fire(speakerName) end)
if not success and err then
print("Error adding speaker: " ..err)
end
return speaker
end
function methods:InternalUnmuteSpeaker(speakerName)
for channelName, channel in pairs(self.ChatChannels) do
if channel:IsSpeakerMuted(speakerName) then
channel:UnmuteSpeaker(speakerName)
end
end
end
function methods:RemoveSpeaker(speakerName)
if (self.Speakers[speakerName:lower()]) then
local n = self.Speakers[speakerName:lower()].Name
self:InternalUnmuteSpeaker(n)
self.Speakers[speakerName:lower()]:InternalDestroy()
self.Speakers[speakerName:lower()] = nil
local success, err = pcall(function() self.eSpeakerRemoved:Fire(n) end)
if not success and err then
print("Error removing speaker: " ..err)
end
else
warn("Speaker \"" .. speakerName .. "\" does not exist!")
end
end
function methods:GetSpeaker(speakerName)
return self.Speakers[speakerName:lower()]
end
function methods:GetChannelList()
local list = {}
for i, channel in pairs(self.ChatChannels) do
if (not channel.Private) then
table.insert(list, channel.Name)
end
end
return list
end
function methods:GetAutoJoinChannelList()
local list = {}
for i, channel in pairs(self.ChatChannels) do
if channel.AutoJoin then
table.insert(list, channel)
end
end
return list
end
function methods:GetSpeakerList()
local list = {}
for i, speaker in pairs(self.Speakers) do
table.insert(list, speaker.Name)
end
return list
end
function methods:SendGlobalSystemMessage(message)
for i, speaker in pairs(self.Speakers) do
speaker:SendSystemMessage(message, nil)
end
end
function methods:RegisterFilterMessageFunction(funcId, func, priority)
if self.FilterMessageFunctions:HasFunction(funcId) then
error(string.format("FilterMessageFunction '%s' already exists", funcId))
end
self.FilterMessageFunctions:AddFunction(funcId, func, priority)
end
function methods:FilterMessageFunctionExists(funcId)
return self.FilterMessageFunctions:HasFunction(funcId)
end
function methods:UnregisterFilterMessageFunction(funcId)
if not self.FilterMessageFunctions:HasFunction(funcId) then
error(string.format("FilterMessageFunction '%s' does not exists", funcId))
end
self.FilterMessageFunctions:RemoveFunction(funcId)
end
function methods:RegisterProcessCommandsFunction(funcId, func, priority)
if self.ProcessCommandsFunctions:HasFunction(funcId) then
error(string.format("ProcessCommandsFunction '%s' already exists", funcId))
end
self.ProcessCommandsFunctions:AddFunction(funcId, func, priority)
end
function methods:ProcessCommandsFunctionExists(funcId)
return self.ProcessCommandsFunctions:HasFunction(funcId)
end
function methods:UnregisterProcessCommandsFunction(funcId)
if not self.ProcessCommandsFunctions:HasFunction(funcId) then
error(string.format("ProcessCommandsFunction '%s' does not exist", funcId))
end
self.ProcessCommandsFunctions:RemoveFunction(funcId)
end
local LastFilterNoficationTime = 0
local LastFilterIssueTime = 0
local FilterIssueCount = 0
function methods:InternalNotifyFilterIssue()
if (tick() - LastFilterIssueTime) > FILTER_THRESHOLD_TIME then
FilterIssueCount = 0
end
FilterIssueCount = FilterIssueCount + 1
LastFilterIssueTime = tick()
if FilterIssueCount >= FILTER_NOTIFCATION_THRESHOLD then
if (tick() - LastFilterNoficationTime) > FILTER_NOTIFCATION_INTERVAL then
LastFilterNoficationTime = tick()
local systemChannel = self:GetChannel("System")
if systemChannel then
systemChannel:SendSystemMessage(
ChatLocalization:Get(
"GameChat_ChatService_ChatFilterIssues",
"The chat filter is currently experiencing issues and messages may be slow to appear."
),
errorExtraData
)
end
end
end
end
local StudioMessageFilteredCache = {}
--///////////////// Internal-Use Methods
--//////////////////////////////////////
--DO NOT REMOVE THIS. Chat must be filtered or your game will face
--moderation.
function methods:InternalApplyRobloxFilter(speakerName, message, toSpeakerName) --// USES FFLAG
if (RunService:IsServer() and not RunService:IsStudio()) then
local fromSpeaker = self:GetSpeaker(speakerName)
local toSpeaker = toSpeakerName and self:GetSpeaker(toSpeakerName)
if fromSpeaker == nil then
return nil
end
local fromPlayerObj = fromSpeaker:GetPlayer()
local toPlayerObj = toSpeaker and toSpeaker:GetPlayer()
if fromPlayerObj == nil then
return message
end
local filterStartTime = tick()
local filterRetries = 0
while true do
local success, message = pcall(function()
if toPlayerObj then
return Chat:FilterStringAsync(message, fromPlayerObj, toPlayerObj)
else
return Chat:FilterStringForBroadcast(message, fromPlayerObj)
end
end)
if success then
return message
else
warn("Error filtering message:", message)
end
filterRetries = filterRetries + 1
if filterRetries > MAX_FILTER_RETRIES or (tick() - filterStartTime) > MAX_FILTER_DURATION then
self:InternalNotifyFilterIssue()
return nil
end
local backoffInterval = FILTER_BACKOFF_INTERVALS[math.min(#FILTER_BACKOFF_INTERVALS, filterRetries)]
-- backoffWait = backoffInterval +/- (0 -> backoffInterval)
local backoffWait = backoffInterval + ((math.random()*2 - 1) * backoffInterval)
wait(backoffWait)
end
else
--// Simulate filtering latency.
--// There is only latency the first time the message is filtered, all following calls will be instant.
if not StudioMessageFilteredCache[message] then
StudioMessageFilteredCache[message] = true
wait()
end
return message
end
return nil
end
--// Return values: bool filterSuccess, bool resultIsFilterObject, variant result
function methods:InternalApplyRobloxFilterNewAPI(speakerName, message, textFilterContext) --// USES FFLAG
local alwaysRunFilter = false
local runFilter = RunService:IsServer() and not RunService:IsStudio()
if (alwaysRunFilter or runFilter) then
local fromSpeaker = self:GetSpeaker(speakerName)
if fromSpeaker == nil then
return false, nil, nil
end
local fromPlayerObj = fromSpeaker:GetPlayer()
if fromPlayerObj == nil then
return true, false, message
end
local success, filterResult = pcall(function()
local ts = game:GetService("TextService")
local result = ts:FilterStringAsync(message, fromPlayerObj.UserId, textFilterContext)
return result
end)
if (success) then
return true, true, filterResult
else
warn("Error filtering message:", message, filterResult)
self:InternalNotifyFilterIssue()
return false, nil, nil
end
end
--// Simulate filtering latency.
wait()
return true, false, message
end
function methods:InternalDoMessageFilter(speakerName, messageObj, channel)
local filtersIterator = self.FilterMessageFunctions:GetIterator()
for funcId, func, priority in filtersIterator do
local success, errorMessage = pcall(function()
func(speakerName, messageObj, channel)
end)
if not success then
warn(string.format("DoMessageFilter Function '%s' failed for reason: %s", funcId, errorMessage))
end
end
end
function methods:InternalDoProcessCommands(speakerName, message, channel)
local commandsIterator = self.ProcessCommandsFunctions:GetIterator()
for funcId, func, priority in commandsIterator do
local success, returnValue = pcall(function()
local ret = func(speakerName, message, channel)
if type(ret) ~= "boolean" then
error("Process command functions must return a bool")
end
return ret
end)
if not success then
warn(string.format("DoProcessCommands Function '%s' failed for reason: %s", funcId, returnValue))
elseif returnValue then
return true
end
end
return false
end
function methods:InternalGetUniqueMessageId()
local id = self.MessageIdCounter
self.MessageIdCounter = id + 1
return id
end
function methods:InternalAddSpeakerWithPlayerObject(speakerName, playerObj, fireSpeakerAdded)
if (self.Speakers[speakerName:lower()]) then
error("Speaker \"" .. speakerName .. "\" already exists!")
end
local speaker = Speaker.new(self, speakerName)
speaker:InternalAssignPlayerObject(playerObj)
self.Speakers[speakerName:lower()] = speaker
if fireSpeakerAdded then
local success, err = pcall(function() self.eSpeakerAdded:Fire(speakerName) end)
if not success and err then
print("Error adding speaker: " ..err)
end
end
return speaker
end
function methods:InternalFireSpeakerAdded(speakerName)
local success, err = pcall(function() self.eSpeakerAdded:Fire(speakerName) end)
if not success and err then
print("Error firing speaker added: " ..err)
end
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new()
local obj = setmetatable({}, methods)
obj.MessageIdCounter = 0
obj.ChatChannels = {}
obj.Speakers = {}
obj.FilterMessageFunctions = Util:NewSortedFunctionContainer()
obj.ProcessCommandsFunctions = Util:NewSortedFunctionContainer()
obj.eChannelAdded = Instance.new("BindableEvent")
obj.eChannelRemoved = Instance.new("BindableEvent")
obj.eSpeakerAdded = Instance.new("BindableEvent")
obj.eSpeakerRemoved = Instance.new("BindableEvent")
obj.ChannelAdded = obj.eChannelAdded.Event
obj.ChannelRemoved = obj.eChannelRemoved.Event
obj.SpeakerAdded = obj.eSpeakerAdded.Event
obj.SpeakerRemoved = obj.eSpeakerRemoved.Event
obj.ChatServiceMajorVersion = 0
obj.ChatServiceMinorVersion = 5
return obj
end
return module.new()
================================================
FILE: src/ServerScriptService/ChatServiceRunner/Speaker.lua
================================================
-- // FileName: Speaker.lua
-- // Written by: Xsitsu
-- // Description: A representation of one entity that can chat in different ChatChannels.
local module = {}
local modulesFolder = script.Parent
--////////////////////////////// Methods
--//////////////////////////////////////
local function ShallowCopy(table)
local copy = {}
for i, v in pairs(table) do
copy[i] = v
end
return copy
end
local methods = {}
local lazyEventNames =
{
eDestroyed = true,
eSaidMessage = true,
eReceivedMessage = true,
eReceivedUnfilteredMessage = true,
eMessageDoneFiltering = true,
eReceivedSystemMessage = true,
eChannelJoined = true,
eChannelLeft = true,
eMuted = true,
eUnmuted = true,
eExtraDataUpdated = true,
eMainChannelSet = true,
eChannelNameColorUpdated = true,
}
local lazySignalNames =
{
Destroyed = "eDestroyed",
SaidMessage = "eSaidMessage",
ReceivedMessage = "eReceivedMessage",
ReceivedUnfilteredMessage = "eReceivedUnfilteredMessage",
RecievedUnfilteredMessage = "eReceivedUnfilteredMessage", -- legacy mispelling
MessageDoneFiltering = "eMessageDoneFiltering",
ReceivedSystemMessage = "eReceivedSystemMessage",
ChannelJoined = "eChannelJoined",
ChannelLeft = "eChannelLeft",
Muted = "eMuted",
Unmuted = "eUnmuted",
ExtraDataUpdated = "eExtraDataUpdated",
MainChannelSet = "eMainChannelSet",
ChannelNameColorUpdated = "eChannelNameColorUpdated"
}
methods.__index = function (self, k)
local fromMethods = rawget(methods, k)
if fromMethods then return fromMethods end
if lazyEventNames[k] and not rawget(self, k) then
rawset(self, k, Instance.new("BindableEvent"))
end
local lazySignalEventName = lazySignalNames[k]
if lazySignalEventName and not rawget(self, k) then
if not rawget(self, lazySignalEventName) then
rawset(self, lazySignalEventName, Instance.new("BindableEvent"))
end
rawset(self, k, rawget(self, lazySignalEventName).Event)
end
return rawget(self, k)
end
function methods:LazyFire(eventName, ...)
local createdEvent = rawget(self, eventName)
if createdEvent then
createdEvent:Fire(...)
end
end
function methods:SayMessage(message, channelName, extraData)
if (self.ChatService:InternalDoProcessCommands(self.Name, message, channelName)) then return end
if (not channelName) then return end
local channel = self.Channels[channelName:lower()]
if (not channel) then
error("Speaker is not in channel \"" .. channelName .. "\"")
end
local messageObj = channel:InternalPostMessage(self, message, extraData)
if (messageObj) then
local success, err = pcall(function() self:LazyFire("eSaidMessage", messageObj, channelName) end)
if not success and err then
print("Error saying message: " ..err)
end
end
return messageObj
end
function methods:JoinChannel(channelName)
if (self.Channels[channelName:lower()]) then
warn("Speaker is already in channel \"" .. channelName .. "\"")
return
end
local channel = self.ChatService:GetChannel(channelName)
if (not channel) then
error("Channel \"" .. channelName .. "\" does not exist!")
end
self.Channels[channelName:lower()] = channel
channel:InternalAddSpeaker(self)
local success, err = pcall(function()
self.eChannelJoined:Fire(channel.Name, channel:GetWelcomeMessageForSpeaker(self))
end)
if not success and err then
print("Error joining channel: " ..err)
end
end
function methods:LeaveChannel(channelName)
if (not self.Channels[channelName:lower()]) then
warn("Speaker is not in channel \"" .. channelName .. "\"")
return
end
local channel = self.Channels[channelName:lower()]
self.Channels[channelName:lower()] = nil
channel:InternalRemoveSpeaker(self)
local success, err = pcall(function()
self:LazyFire("eChannelLeft", channel.Name)
self.EventFolder.OnChannelLeft:FireClient(self.PlayerObj, channel.Name)
end)
if not success and err then
print("Error leaving channel: " ..err)
end
end
function methods:IsInChannel(channelName)
return (self.Channels[channelName:lower()] ~= nil)
end
function methods:GetChannelList()
local list = {}
for i, channel in pairs(self.Channels) do
table.insert(list, channel.Name)
end
return list
end
function methods:SendMessage(message, channelName, fromSpeaker, extraData)
local channel = self.Channels[channelName:lower()]
if (channel) then
channel:SendMessageToSpeaker(message, self.Name, fromSpeaker, extraData)
else
warn(string.format("Speaker '%s' is not in channel '%s' and cannot receive a message in it.", self.Name, channelName))
end
end
function methods:SendSystemMessage(message, channelName, extraData)
local channel = self.Channels[channelName:lower()]
if (channel) then
channel:SendSystemMessageToSpeaker(message, self.Name, extraData)
else
warn(string.format("Speaker '%s' is not in channel '%s' and cannot receive a system message in it.", self.Name, channelName))
end
end
function methods:GetPlayer()
return self.PlayerObj
end
function methods:SetExtraData(key, value)
self.ExtraData[key] = value
self:LazyFire("eExtraDataUpdated", key, value)
end
function methods:GetExtraData(key)
return self.ExtraData[key]
end
function methods:SetMainChannel(channelName)
local success, err = pcall(function()
self:LazyFire("eMainChannelSet", channelName)
self.EventFolder.OnMainChannelSet:FireClient(self.PlayerObj, channelName)
end)
if not success and err then
print("Error setting main channel: " ..err)
end
end
--- Used to mute a speaker so that this speaker does not see their messages.
function methods:AddMutedSpeaker(speakerName)
self.MutedSpeakers[speakerName:lower()] = true
end
function methods:RemoveMutedSpeaker(speakerName)
self.MutedSpeakers[speakerName:lower()] = false
end
function methods:IsSpeakerMuted(speakerName)
return self.MutedSpeakers[speakerName:lower()]
end
--///////////////// Internal-Use Methods
--//////////////////////////////////////
function methods:InternalDestroy()
for i, channel in pairs(self.Channels) do
channel:InternalRemoveSpeaker(self)
end
self.eDestroyed:Fire()
self.EventFolder = nil
self.eDestroyed:Destroy()
self.eSaidMessage:Destroy()
self.eReceivedMessage:Destroy()
self.eReceivedUnfilteredMessage:Destroy()
self.eMessageDoneFiltering:Destroy()
self.eReceivedSystemMessage:Destroy()
self.eChannelJoined:Destroy()
self.eChannelLeft:Destroy()
self.eMuted:Destroy()
self.eUnmuted:Destroy()
self.eExtraDataUpdated:Destroy()
self.eMainChannelSet:Destroy()
self.eChannelNameColorUpdated:Destroy()
end
function methods:InternalAssignPlayerObject(playerObj)
self.PlayerObj = playerObj
end
function methods:InternalAssignEventFolder(eventFolder)
self.EventFolder = eventFolder
end
function methods:InternalSendMessage(messageObj, channelName)
local success, err = pcall(function()
self:LazyFire("eReceivedUnfilteredMessage", messageObj, channelName)
self.EventFolder.OnNewMessage:FireClient(self.PlayerObj, messageObj, channelName)
end)
if not success and err then
print("Error sending internal message: " ..err)
end
end
function methods:InternalSendFilteredMessage(messageObj, channelName)
local success, err = pcall(function()
self:LazyFire("eReceivedMessage", messageObj, channelName)
self:LazyFire("eMessageDoneFiltering", messageObj, channelName)
self.EventFolder.OnMessageDoneFiltering:FireClient(self.PlayerObj, messageObj, channelName)
end)
if not success and err then
print("Error sending internal filtered message: " ..err)
end
end
--// This method is to be used with the new filter API. This method takes the
--// TextFilterResult objects and converts them into the appropriate string
--// messages for each player.
function methods:InternalSendFilteredMessageWithFilterResult(inMessageObj, channelName)
local messageObj = ShallowCopy(inMessageObj)
local oldFilterResult = messageObj.FilterResult
local player = self:GetPlayer()
local msg = ""
pcall(function()
if (messageObj.IsFilterResult) then
if (player) then
msg = oldFilterResult:GetChatForUserAsync(player.UserId)
else
msg = oldFilterResult:GetNonChatStringForBroadcastAsync()
end
else
msg = oldFilterResult
end
end)
--// Messages of 0 length are the result of two users not being allowed
--// to chat, or GetChatForUserAsync() failing. In both of these situations,
--// messages with length of 0 should not be sent.
if (#msg > 0) then
messageObj.Message = msg
messageObj.FilterResult = nil
self:InternalSendFilteredMessage(messageObj, channelName)
end
end
function methods:InternalSendSystemMessage(messageObj, channelName)
local success, err = pcall(function()
self:LazyFire("eReceivedSystemMessage", messageObj, channelName)
self.EventFolder.OnNewSystemMessage:FireClient(self.PlayerObj, messageObj, channelName)
end)
if not success and err then
print("Error sending internal system message: " ..err)
end
end
function methods:UpdateChannelNameColor(channelName, channelNameColor)
self:LazyFire("eChannelNameColorUpdated", channelName, channelNameColor)
self.EventFolder.ChannelNameColorUpdated:FireClient(self.PlayerObj, channelName, channelNameColor)
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new(vChatService, name)
local obj = setmetatable({}, methods)
obj.ChatService = vChatService
obj.PlayerObj = nil
obj.Name = name
obj.ExtraData = {}
obj.Channels = {}
obj.MutedSpeakers = {}
obj.EventFolder = nil
return obj
end
return module
================================================
FILE: src/ServerScriptService/ChatServiceRunner/Util.lua
================================================
-- // FileName: Util.lua
-- // Written by: TheGamer101
-- // Description: Utility code used by the server side chat implementation.
local Chat = game:GetService("Chat")
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
local DEFAULT_PRIORITY = ChatConstants.StandardPriority
if DEFAULT_PRIORITY == nil then
DEFAULT_PRIORITY = 10
end
local Util = {}
Util.__index = Util
local SortedFunctionContainer = {}; do
-- This sorted function container is used to handle the logic around storing filter functions and
-- command processors by priority.
local methods = {}
methods.__index = methods
function methods:RebuildProcessCommandsPriorities()
self.RegisteredPriorites = {}
for priority, functions in pairs(self.FunctionMap) do
local functionsEmpty = true
for funcId, funciton in pairs(functions) do
functionsEmpty = false
break
end
if not functionsEmpty then
table.insert(self.RegisteredPriorites, priority)
end
end
table.sort(self.RegisteredPriorites, function(a, b)
return a > b
end)
end
function methods:HasFunction(funcId)
if self.RegisteredFunctions[funcId] == nil then
return false
end
return true
end
function methods:RemoveFunction(funcId)
local functionPriority = self.RegisteredFunctions[funcId]
self.RegisteredFunctions[funcId] = nil
self.FunctionMap[functionPriority][funcId] = nil
self:RebuildProcessCommandsPriorities()
end
function methods:AddFunction(funcId, func, priority)
if priority == nil then
priority = DEFAULT_PRIORITY
end
if self.RegisteredFunctions[funcId] then
error(funcId .. " is already in use!")
end
self.RegisteredFunctions[funcId] = priority
if self.FunctionMap[priority] == nil then
self.FunctionMap[priority] = {}
end
self.FunctionMap[priority][funcId] = func
self:RebuildProcessCommandsPriorities()
end
function methods:GetIterator()
local priorityIndex = 1
local funcId = nil
local func = nil
return function()
while true do
if priorityIndex > #self.RegisteredPriorites then
return
end
local priority = self.RegisteredPriorites[priorityIndex]
funcId, func = next(self.FunctionMap[priority], funcId)
if funcId == nil then
priorityIndex = priorityIndex + 1
else
return funcId, func, priority
end
end
end
end
function SortedFunctionContainer.new()
local obj = setmetatable({}, methods)
obj.RegisteredFunctions = {}
obj.RegisteredPriorites = {}
obj.FunctionMap = {}
return obj
end
end
function Util:NewSortedFunctionContainer()
return SortedFunctionContainer.new()
end
return Util
================================================
FILE: src/ServerScriptService/ChatServiceRunner/init.server.lua
================================================
-- // FileName: ChatServiceRunner.lua
-- // Written by: Xsitsu
-- // Description: Main script to initialize ChatService and run ChatModules.
local EventFolderName = "DefaultChatSystemChatEvents"
local EventFolderParent = game:GetService("ReplicatedStorage")
local modulesFolder = script
local PlayersService = game:GetService("Players")
local RunService = game:GetService("RunService")
local Chat = game:GetService("Chat")
local ChatService = require(modulesFolder:WaitForChild("ChatService"))
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(Chat.ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = { Get = function(key,default) return default end } end
local useEvents = {}
local EventFolder = EventFolderParent:FindFirstChild(EventFolderName)
if EventFolder then
EventFolder:Destroy()
EventFolder = nil
end
if (not EventFolder) then
EventFolder = Instance.new("Folder")
EventFolder.Name = EventFolderName
EventFolder.Archivable = false
EventFolder.Parent = EventFolderParent
end
--// No-opt connect Server>Client RemoteEvents to ensure they cannot be called
--// to fill the remote event queue.
local function emptyFunction()
--intentially empty
end
local function GetObjectWithNameAndType(parentObject, objectName, objectType)
for _, child in pairs(parentObject:GetChildren()) do
if (child:IsA(objectType) and child.Name == objectName) then
return child
end
end
return nil
end
local function CreateIfDoesntExist(parentObject, objectName, objectType)
local obj = GetObjectWithNameAndType(parentObject, objectName, objectType)
if (not obj) then
obj = Instance.new(objectType)
obj.Name = objectName
obj.Parent = parentObject
end
useEvents[objectName] = obj
return obj
end
--// All remote events will have a no-opt OnServerEvent connecdted on construction
local function CreateEventIfItDoesntExist(parentObject, objectName)
local obj = CreateIfDoesntExist(parentObject, objectName, "RemoteEvent")
obj.OnServerEvent:Connect(emptyFunction)
return obj
end
CreateEventIfItDoesntExist(EventFolder, "OnNewMessage")
CreateEventIfItDoesntExist(EventFolder, "OnMessageDoneFiltering")
CreateEventIfItDoesntExist(EventFolder, "OnNewSystemMessage")
CreateEventIfItDoesntExist(EventFolder, "OnChannelJoined")
CreateEventIfItDoesntExist(EventFolder, "OnChannelLeft")
CreateEventIfItDoesntExist(EventFolder, "OnMuted")
CreateEventIfItDoesntExist(EventFolder, "OnUnmuted")
CreateEventIfItDoesntExist(EventFolder, "OnMainChannelSet")
CreateEventIfItDoesntExist(EventFolder, "ChannelNameColorUpdated")
CreateEventIfItDoesntExist(EventFolder, "SayMessageRequest")
CreateEventIfItDoesntExist(EventFolder, "SetBlockedUserIdsRequest")
CreateIfDoesntExist(EventFolder, "GetInitDataRequest", "RemoteFunction")
CreateIfDoesntExist(EventFolder, "MutePlayerRequest", "RemoteFunction")
CreateIfDoesntExist(EventFolder, "UnMutePlayerRequest", "RemoteFunction")
EventFolder = useEvents
local function CreatePlayerSpeakerObject(playerObj)
--// If a developer already created a speaker object with the
--// name of a player and then a player joins and tries to
--// take that name, we first need to remove the old speaker object
local speaker = ChatService:GetSpeaker(playerObj.Name)
if (speaker) then
ChatService:RemoveSpeaker(playerObj.Name)
end
speaker = ChatService:InternalAddSpeakerWithPlayerObject(playerObj.Name, playerObj, false)
for _, channel in pairs(ChatService:GetAutoJoinChannelList()) do
speaker:JoinChannel(channel.Name)
end
speaker:InternalAssignEventFolder(EventFolder)
speaker.ChannelJoined:connect(function(channel, welcomeMessage)
local log = nil
local channelNameColor = nil
local channelObject = ChatService:GetChannel(channel)
if (channelObject) then
log = channelObject:GetHistoryLogForSpeaker(speaker)
channelNameColor = channelObject.ChannelNameColor
end
EventFolder.OnChannelJoined:FireClient(playerObj, channel, welcomeMessage, log, channelNameColor)
end)
speaker.Muted:connect(function(channel, reason, length)
EventFolder.OnMuted:FireClient(playerObj, channel, reason, length)
end)
speaker.Unmuted:connect(function(channel)
EventFolder.OnUnmuted:FireClient(playerObj, channel)
end)
ChatService:InternalFireSpeakerAdded(speaker.Name)
end
EventFolder.SayMessageRequest.OnServerEvent:connect(function(playerObj, message, channel)
if type(message) ~= "string" then
return
end
if type(channel) ~= "string" then
return
end
local speaker = ChatService:GetSpeaker(playerObj.Name)
if (speaker) then
return speaker:SayMessage(message, channel)
end
return nil
end)
EventFolder.MutePlayerRequest.OnServerInvoke = function(playerObj, muteSpeakerName)
if type(muteSpeakerName) ~= "string" then
return
end
local speaker = ChatService:GetSpeaker(playerObj.Name)
if speaker then
local muteSpeaker = ChatService:GetSpeaker(muteSpeakerName)
if muteSpeaker then
speaker:AddMutedSpeaker(muteSpeaker.Name)
return true
end
end
return false
end
EventFolder.UnMutePlayerRequest.OnServerInvoke = function(playerObj, unmuteSpeakerName)
if type(unmuteSpeakerName) ~= "string" then
return
end
local speaker = ChatService:GetSpeaker(playerObj.Name)
if speaker then
local unmuteSpeaker = ChatService:GetSpeaker(unmuteSpeakerName)
if unmuteSpeaker then
speaker:RemoveMutedSpeaker(unmuteSpeaker.Name)
return true
end
end
return false
end
-- Map storing Player -> Blocked user Ids.
local BlockedUserIdsMap = {}
PlayersService.PlayerAdded:connect(function(newPlayer)
for player, blockedUsers in pairs(BlockedUserIdsMap) do
local speaker = ChatService:GetSpeaker(player.Name)
if speaker then
for i = 1, #blockedUsers do
local blockedUserId = blockedUsers[i]
if blockedUserId == newPlayer.UserId then
speaker:AddMutedSpeaker(newPlayer.Name)
end
end
end
end
end)
PlayersService.PlayerRemoving:connect(function(removingPlayer)
BlockedUserIdsMap[removingPlayer] = nil
end)
EventFolder.SetBlockedUserIdsRequest.OnServerEvent:connect(function(player, blockedUserIdsList)
if type(blockedUserIdsList) ~= "table" then
return
end
BlockedUserIdsMap[player] = blockedUserIdsList
local speaker = ChatService:GetSpeaker(player.Name)
if speaker then
for i = 1, #blockedUserIdsList do
if type(blockedUserIdsList[i]) == "number" then
local blockedPlayer = PlayersService:GetPlayerByUserId(blockedUserIdsList[i])
if blockedPlayer then
speaker:AddMutedSpeaker(blockedPlayer.Name)
end
end
end
end
end)
EventFolder.GetInitDataRequest.OnServerInvoke = (function(playerObj)
local speaker = ChatService:GetSpeaker(playerObj.Name)
if not (speaker and speaker:GetPlayer()) then
CreatePlayerSpeakerObject(playerObj)
speaker = ChatService:GetSpeaker(playerObj.Name)
end
local data = {}
data.Channels = {}
data.SpeakerExtraData = {}
for _, channelName in pairs(speaker:GetChannelList()) do
local channelObj = ChatService:GetChannel(channelName)
if (channelObj) then
local channelData =
{
channelName,
channelObj:GetWelcomeMessageForSpeaker(speaker),
channelObj:GetHistoryLogForSpeaker(speaker),
channelObj.ChannelNameColor,
}
table.insert(data.Channels, channelData)
end
end
for _, oSpeakerName in pairs(ChatService:GetSpeakerList()) do
local oSpeaker = ChatService:GetSpeaker(oSpeakerName)
data.SpeakerExtraData[oSpeakerName] = oSpeaker.ExtraData
end
return data
end)
local function DoJoinCommand(speakerName, channelName, fromChannelName)
local speaker = ChatService:GetSpeaker(speakerName)
local channel = ChatService:GetChannel(channelName)
if (speaker) then
if (channel) then
if (channel.Joinable) then
if (not speaker:IsInChannel(channel.Name)) then
speaker:JoinChannel(channel.Name)
else
speaker:SetMainChannel(channel.Name)
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_SwitchChannel_NowInChannel",
string.format("You are now chatting in channel: '%s'", channel.Name)
),
"{RBX_NAME}",channel.Name),
channel.Name
)
end
else
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatServiceRunner_YouCannotJoinChannel",
("You cannot join channel '" .. channelName .. "'.")
),
"{RBX_NAME}",channelName),
fromChannelName
)
end
else
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatServiceRunner_ChannelDoesNotExist",
("Channel '" .. channelName .. "' does not exist.")
),
"{RBX_NAME}",channelName),
fromChannelName
)
end
end
end
local function DoLeaveCommand(speakerName, channelName, fromChannelName)
local speaker = ChatService:GetSpeaker(speakerName)
local channel = ChatService:GetChannel(channelName)
if (speaker) then
if (speaker:IsInChannel(channelName)) then
if (channel.Leavable) then
speaker:LeaveChannel(channel.Name)
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatService_YouHaveLeftChannel",
string.format("You have left channel '%s'", channelName)
),
"{RBX_NAME}",channel.Name),
"System"
)
else
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatServiceRunner_YouCannotLeaveChannel",
("You cannot leave channel '" .. channelName .. "'.")
),
"{RBX_NAME}",channelName),
fromChannelName
)
end
else
speaker:SendSystemMessage(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatServiceRunner_YouAreNotInChannel",
("You are not in channel '" .. channelName .. "'.")
),
"{RBX_NAME}",channelName),
fromChannelName
)
end
end
end
ChatService:RegisterProcessCommandsFunction("default_commands", function(fromSpeaker, message, channel)
if (string.sub(message, 1, 6):lower() == "/join ") then
DoJoinCommand(fromSpeaker, string.sub(message, 7), channel)
return true
elseif (string.sub(message, 1, 3):lower() == "/j ") then
DoJoinCommand(fromSpeaker, string.sub(message, 4), channel)
return true
elseif (string.sub(message, 1, 7):lower() == "/leave ") then
DoLeaveCommand(fromSpeaker, string.sub(message, 8), channel)
return true
elseif (string.sub(message, 1, 3):lower() == "/l ") then
DoLeaveCommand(fromSpeaker, string.sub(message, 4), channel)
return true
elseif (string.sub(message, 1, 3) == "/e " or string.sub(message, 1, 7) == "/emote ") then
-- Just don't show these in the chatlog. The animation script listens on these.
return true
end
return false
end)
if ChatSettings.GeneralChannelName and ChatSettings.GeneralChannelName ~= "" then
local allChannel = ChatService:AddChannel(ChatSettings.GeneralChannelName)
allChannel.Leavable = false
allChannel.AutoJoin = true
allChannel:RegisterGetWelcomeMessageFunction(function(speaker)
if RunService:IsStudio() then
return nil
end
local player = speaker:GetPlayer()
if player then
local success, canChat = pcall(function()
return Chat:CanUserChatAsync(player.UserId)
end)
if success and not canChat then
return ""
end
end
end)
end
local systemChannel = ChatService:AddChannel("System")
systemChannel.Leavable = false
systemChannel.AutoJoin = true
systemChannel.WelcomeMessage = ChatLocalization:Get(
"GameChat_ChatServiceRunner_SystemChannelWelcomeMessage", "This channel is for system and game notifications."
)
systemChannel.SpeakerJoined:connect(function(speakerName)
systemChannel:MuteSpeaker(speakerName)
end)
local function TryRunModule(module)
if module:IsA("ModuleScript") then
local ret = require(module)
if (type(ret) == "function") then
ret(ChatService)
end
end
end
local modules = Chat:WaitForChild("ChatModules")
modules.ChildAdded:connect(function(child)
local success, returnval = pcall(TryRunModule, child)
if not success and returnval then
print("Error running module " ..child.Name.. ": " ..returnval)
end
end)
for _, module in pairs(modules:GetChildren()) do
local success, returnval = pcall(TryRunModule, module)
if not success and returnval then
print("Error running module " ..module.Name.. ": " ..returnval)
end
end
PlayersService.PlayerRemoving:connect(function(playerObj)
if (ChatService:GetSpeaker(playerObj.Name)) then
ChatService:RemoveSpeaker(playerObj.Name)
end
end)
================================================
FILE: src/ServerScriptService/analytics/GameAnalytics/INSTRUCTIONS.server.lua
================================================
--[[
Thanks for using my GameAnalytics module! PM me (ByDefault) with any suggestions, bugs or feedback in
general!
To start using the module, follow these steps:
1. Create an account on gameanalytics.com and create a game
2. Get your game key and secret key
3. Require this module ( require(GameAnalytics) )
4. Call :Init(GameKey, SecretKey) on the module
5. You're ready!
To send events, follow these steps:
1. Create a table for the event, like so:
local eventTable = {
["category"] = "design",
["event_id"] = "Game:RoundStart:Spleef",
}
2. Call :SendEvent(EventTable) on the module, letting the only parameter be the eventTable you just made
3. That's it! Your event will automatically be sent within 15 seconds!
It may take a few minutes for your event to appear in the gameanalytics realtime page and every
other page gets refreshed with the newest data daily.
Resources:
REST API Docs: http://www.gameanalytics.com/docs/rest-api
Account Management: http://www.gameanalytics.com/docs/account-management
FAQ: http://www.gameanalytics.com/docs/faq
--]]
script:remove()
================================================
FILE: src/ServerScriptService/analytics/GameAnalytics/bit.lua
================================================
local M = {_TYPE='module', _NAME='bit.numberlua', _VERSION='0.3.1.20120131'}
M.bits = 32
local floor = math.floor
local MOD = 2^32
local MODM = MOD-1
local function memoize(f)
local mt = {}
local t = setmetatable({}, mt)
function mt:__index(k)
local v = f(k); t[k] = v
return v
end
return t
end
local function make_bitop_uncached(t, m)
local function bitop(a, b)
local res,p = 0,1
while a ~= 0 and b ~= 0 do
local am, bm = a%m, b%m
res = res + t[am][bm]*p
a = (a - am) / m
b = (b - bm) / m
p = p*m
end
res = res + (a+b)*p
return res
end
return bitop
end
local function make_bitop(t)
local op1 = make_bitop_uncached(t,2^1)
local op2 = memoize(function(a)
return memoize(function(b)
return op1(a, b)
end)
end)
return make_bitop_uncached(op2, 2^(t.n or 1))
end
-- ok? probably not if running on a 32-bit int Lua number type platform
function M.tobit(x)
return x % 2^32
end
M.cast = M.tobit
M.bxor = make_bitop {[0]={[0]=0,[1]=1},[1]={[0]=1,[1]=0}, n=4}
local bxor = M.bxor
function M.bnot(a) return MODM - a end
local bnot = M.bnot
function M.band(a,b) return ((a+b) - bxor(a,b))/2 end
local band = M.band
function M.bor(a,b) return MODM - band(MODM - a, MODM - b) end
local bor = M.bor
local lshift, rshift -- forward declare
function M.rshift(a,disp) -- Lua5.2 insipred
if disp < 0 then return lshift(a,-disp) end
return floor(a % 2^32 / 2^disp)
end
rshift = M.rshift
function M.lshift(a,disp) -- Lua5.2 inspired
if disp < 0 then return rshift(a,-disp) end
return (a * 2^disp) % 2^32
end
lshift = M.lshift
function M.tohex(x, n) -- BitOp style
n = n or 8
local up
if n <= 0 then
if n == 0 then return '' end
up = true
n = - n
end
x = band(x, 16^n-1)
return ('%0'..n..(up and 'X' or 'x')):format(x)
end
local tohex = M.tohex
function M.extract(n, field, width) -- Lua5.2 inspired
width = width or 1
return band(rshift(n, field), 2^width-1)
end
local extract = M.extract
function M.replace(n, v, field, width) -- Lua5.2 inspired
width = width or 1
local mask1 = 2^width-1
v = band(v, mask1) -- required by spec?
local mask = bnot(lshift(mask1, field))
return band(n, mask) + lshift(v, field)
end
local replace = M.replace
function M.bswap(x) -- BitOp style
local a = band(x, 0xff); x = rshift(x, 8)
local b = band(x, 0xff); x = rshift(x, 8)
local c = band(x, 0xff); x = rshift(x, 8)
local d = band(x, 0xff)
return lshift(lshift(lshift(a, 8) + b, 8) + c, 8) + d
end
local bswap = M.bswap
function M.rrotate(x, disp) -- Lua5.2 inspired
disp = disp % 32
local low = band(x, 2^disp-1)
return rshift(x, disp) + lshift(low, 32-disp)
end
local rrotate = M.rrotate
function M.lrotate(x, disp) -- Lua5.2 inspired
return rrotate(x, -disp)
end
local lrotate = M.lrotate
M.rol = M.lrotate -- LuaOp inspired
M.ror = M.rrotate -- LuaOp insipred
function M.arshift(x, disp) -- Lua5.2 inspired
local z = rshift(x, disp)
if x >= 0x80000000 then z = z + lshift(2^disp-1, 32-disp) end
return z
end
local arshift = M.arshift
function M.btest(x, y) -- Lua5.2 inspired
return band(x, y) ~= 0
end
M.bit32 = bit32
--
-- Start Lua 5.2 "bit32" compat section.
--
--[[
M.bit32 = {} -- Lua 5.2 'bit32' compatibility
M.bit32.bits = M.bits
M.bit32.cast = M.cast
local function bit32_bnot(x)
return (-1 - x) % MOD
end
M.bit32.bnot = bit32_bnot
local function bit32_bxor(a, b, c, ...)
local z
if b then
a = a % MOD
b = b % MOD
z = bxor(a, b)
if c then
z = bit32_bxor(z, c, ...)
end
return z
elseif a then
return a % MOD
else
return 0
end
end
M.bit32.bxor = bit32_bxor
local function bit32_band(a, b, c, ...)
local z
if b then
a = a % MOD
b = b % MOD
z = ((a+b) - bxor(a,b)) / 2
if c then
z = bit32_band(z, c, ...)
end
return z
elseif a then
return a % MOD
else
return MODM
end
end
M.bit32.band = bit32_band
local function bit32_bor(a, b, c, ...)
local z
if b then
a = a % MOD
b = b % MOD
z = MODM - band(MODM - a, MODM - b)
if c then
z = bit32_bor(z, c, ...)
end
return z
elseif a then
return a % MOD
else
return 0
end
end
M.bit32.bor = bit32_bor
function M.bit32.btest(...)
return bit32_band(...) ~= 0
end
function M.bit32.lrotate(x, disp)
return lrotate(x % MOD, disp)
end
function M.bit32.rrotate(x, disp)
return rrotate(x % MOD, disp)
end
function M.bit32.lshift(x,disp)
if disp > 31 or disp < -31 then return 0 end
return lshift(x % MOD, disp)
end
function M.bit32.rshift(x,disp)
if disp > 31 or disp < -31 then return 0 end
return rshift(x % MOD, disp)
end
function M.bit32.arshift(x,disp)
x = x % MOD
if disp >= 0 then
if disp > 31 then
return (x >= 0x80000000) and MODM or 0
else
local z = rshift(x, disp)
if x >= 0x80000000 then z = z + lshift(2^disp-1, 32-disp) end
return z
end
else
return lshift(x, -disp)
end
end
function M.bit32.extract(x, field, ...)
local width = ... or 1
if field < 0 or field > 31 or width < 0 or field+width > 32 then error 'out of range' end
x = x % MOD
return extract(x, field, ...)
end
function M.bit32.replace(x, v, field, ...)
local width = ... or 1
if field < 0 or field > 31 or width < 0 or field+width > 32 then error 'out of range' end
x = x % MOD
v = v % MOD
return replace(x, v, field, ...)
end
]]
--
-- Start LuaBitOp "bit" compat section.
--
M.bit = {} -- LuaBitOp "bit" compatibility
M.bit.bits = M.bits
M.bit.cast = M.cast
function M.bit.tobit(x)
x = x % MOD
if x >= 0x80000000 then x = x - MOD end
return x
end
local bit_tobit = M.bit.tobit
function M.bit.tohex(x, ...)
return tohex(x % MOD, ...)
end
function M.bit.bnot(x)
return bit_tobit(bnot(x % MOD))
end
local function bit_bor(a, b, c, ...)
if c then
return bit_bor(bit_bor(a, b), c, ...)
elseif b then
return bit_tobit(bor(a % MOD, b % MOD))
else
return bit_tobit(a)
end
end
M.bit.bor = bit_bor
local function bit_band(a, b, c, ...)
if c then
return bit_band(bit_band(a, b), c, ...)
elseif b then
return bit_tobit(band(a % MOD, b % MOD))
else
return bit_tobit(a)
end
end
M.bit.band = bit_band
local function bit_bxor(a, b, c, ...)
if c then
return bit_bxor(bit_bxor(a, b), c, ...)
elseif b then
return bit_tobit(bxor(a % MOD, b % MOD))
else
return bit_tobit(a)
end
end
M.bit.bxor = bit_bxor
function M.bit.lshift(x, n)
return bit_tobit(lshift(x % MOD, n % 32))
end
function M.bit.rshift(x, n)
return bit_tobit(rshift(x % MOD, n % 32))
end
function M.bit.arshift(x, n)
return bit_tobit(arshift(x % MOD, n % 32))
end
function M.bit.rol(x, n)
return bit_tobit(lrotate(x % MOD, n % 32))
end
function M.bit.ror(x, n)
return bit_tobit(rrotate(x % MOD, n % 32))
end
function M.bit.bswap(x)
return bit_tobit(bswap(x % MOD))
end
return M
================================================
FILE: src/ServerScriptService/analytics/GameAnalytics/init.lua
================================================
--Variables
local baseURL = "http://api.gameanalytics.com/v2/"
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
--Services
local HTTP = game:GetService("HttpService")
--Normalize strings for GameAnalytics processing
local function normify(str)
str = string.gsub(string.gsub(str," ","_"),"[^a-zA-Z0-9 - _ :]","")
if #str > 30 then
str = string.sub(str,1,30)
end
return str
end
local placeName = "unknown"
_G.placeName = placeName
spawn(function()
local placeInfo = game.MarketplaceService:GetProductInfo(game.PlaceId,Enum.InfoType.Asset)
if placeInfo then
placeName = normify(placeInfo.Name)
_G.placeName = placeName
end
end)
GA = {
PostFrequency = 20,
GameKey = nil,
SecretKey = nil,
SessionID = nil,
Queue = {},
EncodingModules = {},
Ready = false,
}
function GA:Init(GameKey, SecretKey)
baseURL = baseURL..GameKey.."/"
GA.GameKey = GameKey
GA.SecretKey = SecretKey
GA.SessionID = HTTP:GenerateGUID(false):lower()
--Encoding Modules
GA.EncodingModules.lockbox = require(script.lockbox)
GA.EncodingModules.lockbox.bit = require(script.bit).bit
GA.EncodingModules.array = require(GA.EncodingModules.lockbox.util.array)
GA.EncodingModules.stream = require(GA.EncodingModules.lockbox.util.stream)
GA.EncodingModules.base64 = require(GA.EncodingModules.lockbox.util.base64)
GA.EncodingModules.hmac = require(GA.EncodingModules.lockbox.mac.hmac)
GA.EncodingModules.sha256 = require(GA.EncodingModules.lockbox.digest.sha2_256)
GA.Base = {
["device"] = "unknown",
["v"] = 2,
["user_id"] = "unknown",
["client_ts"] = os.time(),
-- ["sdk_version"] = "rest api v2",
["sdk_version"] = "roblox 1.0.1",
["os_version"] = "windows 10",
["manufacturer"] = "unknown",
["platform"] = "windows",
["session_id"] = GA.SessionID,
["session_num"] = 1,
}
local Data = HTTP:JSONEncode({
["platform"] = "unknown",
["os_version"] = "unknown",
["sdk_version"] = "rest api v2",
})
local Headers = {
Authorization = GA:Encode(Data)
}
local Response = HTTP:PostAsync(baseURL.."init", Data, Enum.HttpContentType.ApplicationJson, false, Headers)
Response = HTTP:JSONDecode(Response)
if not Response.enabled then
warn("GameAnalytics did not initialize properly!")
return
end
GA.Ready = true
spawn(function()
while true do
wait(GA.PostFrequency)
GA:Post()
end
end)
end
game:BindToClose(function()
if game:GetService("RunService"):IsStudio() then return end
GA:Post()
wait(1)
GA:Post()
end)
function GA:SendEvent(Data, Player)
if not GA.Ready then
warn("GameAnalytics has not been initialized! Call :Init(GameKey, SecretKey) on the module before sending events!")
end
-- normify strings to comply with GameAnalytics
for key,value in pairs(Data) do
if type(value) == "string" and key ~= "message" then
Data[key] = normify(value)
end
end
-- do this BEFORE the base values are assigned
for i,v in pairs(GA.Base) do
Data[i] = Data[i] or v
end
if Player ~= nil and Player.Parent == game.Players then -- Allow game to specify a player (mod by berezaa)
Data["user_id"] = tostring(Player.userId)
if Player:FindFirstChild("AnalyticsSessionId") then
Data["session_id"] = Player.AnalyticsSessionId.Value
else
warn("failed to find analytics session for",Player.Name)
end
end
local playerClass = "unknown"
local playerData = network:invoke("getPlayerData", Player)
if playerData then
if playerData.class then
playerClass = normify(playerData.class)
end
if playerData.sessionCount then
Data["session_num"] = playerData.sessionCount
end
end
Data["client_ts"] = os.time()
-- CLASS
Data["custom_01"] = playerClass
-- LOCATION
Data["custom_02"] = placeName
table.insert(GA.Queue, Data)
return true
end
function GA:Post()
if not GA.Ready then
warn("GameAnalytics has not been initialized! Call :Init(GameKey, SecretKey) on the module before sending events!")
return
end
if #GA.Queue > 0 then
local Data = HTTP:JSONEncode(GA.Queue)
GA.Queue = {}
local Headers = {
["Authorization"] = GA:Encode(Data);
["Content-Type"] = "application/json";
}
local HttpRequest = {
Url = baseURL.."events";
Method = "POST";
Headers = Headers;
Body = Data;
}
local s,ret = pcall(function() return HTTP:RequestAsync(HttpRequest) end)
if not s then
warn("GameAnalytics post failed without result!")
elseif (ret.StatusCode and ret.StatusCode ~= 200) then
warn("GameAnalytics error!", "HTTP", ret.StatusCode, HTTP:JSONEncode(ret))
end
end
end
function GA:Encode(body)
local secretKey = GA.SecretKey
local hmacBuilder = GA.EncodingModules.hmac()
.setBlockSize(64)
.setDigest(GA.EncodingModules.sha256)
.setKey(GA.EncodingModules.array.fromString(secretKey))
.init()
.update(GA.EncodingModules.stream.fromString(body))
.finish()
return GA.EncodingModules.base64.fromArray(hmacBuilder.asBytes())
end
return GA
================================================
FILE: src/ServerScriptService/analytics/GameAnalytics/lockbox/digest/init.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/ServerScriptService/analytics/GameAnalytics/lockbox/digest/sha2_256.lua
================================================
local lockbox = script.Parent.Parent
local Bit = require(lockbox.util.bit);
local String = string;
local Math = math;
local Queue = require(lockbox.util.queue);
local CONSTANTS = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 };
local AND = Bit.band;
local OR = Bit.bor;
local NOT = Bit.bnot;
local XOR = Bit.bxor;
local LROT = Bit.lrotate;
local RROT = Bit.rrotate;
local LSHIFT = Bit.lshift;
local RSHIFT = Bit.rshift;
--SHA2 is big-endian
local bytes2word = function(b0,b1,b2,b3)
local i = b0; i = LSHIFT(i,8);
i = OR(i,b1); i = LSHIFT(i,8);
i = OR(i,b2); i = LSHIFT(i,8);
i = OR(i,b3);
return i;
end
local word2bytes = function(word)
local b0,b1,b2,b3;
b3 = AND(word,0xFF); word = RSHIFT(word,8);
b2 = AND(word,0xFF); word = RSHIFT(word,8);
b1 = AND(word,0xFF); word = RSHIFT(word,8);
b0 = AND(word,0xFF);
return b0,b1,b2,b3;
end
local bytes2dword = function(b0,b1,b2,b3,b4,b5,b6,b7)
local i = bytes2word(b0,b1,b2,b3);
local j = bytes2word(b4,b5,b6,b7);
return (i*0x100000000)+j;
end
local dword2bytes = function(i)
local b4,b5,b6,b7 = word2bytes(i);
local b0,b1,b2,b3 = word2bytes(Math.floor(i/0x100000000));
return b0,b1,b2,b3,b4,b5,b6,b7;
end
local SHA2_256 = function()
local queue = Queue();
local h0 = 0x6a09e667;
local h1 = 0xbb67ae85;
local h2 = 0x3c6ef372;
local h3 = 0xa54ff53a;
local h4 = 0x510e527f;
local h5 = 0x9b05688c;
local h6 = 0x1f83d9ab;
local h7 = 0x5be0cd19;
local public = {};
local processBlock = function()
local a = h0;
local b = h1;
local c = h2;
local d = h3;
local e = h4;
local f = h5;
local g = h6;
local h = h7;
local w = {};
for i=0,15 do
w[i] = bytes2word(queue.pop(),queue.pop(),queue.pop(),queue.pop());
end
game:GetService("RunService").Heartbeat:Wait()
for i=16,63 do
if i%20 == 0 then
game:GetService("RunService").Heartbeat:Wait()
end
local s0 = XOR(RROT(w[i-15],7), XOR(RROT(w[i-15],18), RSHIFT(w[i-15],3)));
local s1 = XOR(RROT(w[i-2],17), XOR(RROT(w[i-2], 19), RSHIFT(w[i-2],10)));
w[i] = AND(w[i-16] + s0 + w[i-7] + s1, 0xFFFFFFFF);
end
for i=0,63 do
if i%12 == 0 then
game:GetService("RunService").Heartbeat:Wait()
end
local s1 = XOR(RROT(e,6), XOR(RROT(e,11),RROT(e,25)));
local ch = XOR(AND(e,f), AND(NOT(e),g));
local temp1 = h + s1 + ch + CONSTANTS[i+1] + w[i];
local s0 = XOR(RROT(a,2), XOR(RROT(a,13), RROT(a,22)));
local maj = XOR(AND(a,b), XOR(AND(a,c), AND(b,c)));
local temp2 = s0 + maj;
h = g;
g = f;
f = e;
e = d + temp1;
d = c;
c = b;
b = a;
a = temp1 + temp2;
end
h0 = AND(h0 + a, 0xFFFFFFFF);
h1 = AND(h1 + b, 0xFFFFFFFF);
h2 = AND(h2 + c, 0xFFFFFFFF);
h3 = AND(h3 + d, 0xFFFFFFFF);
h4 = AND(h4 + e, 0xFFFFFFFF);
h5 = AND(h5 + f, 0xFFFFFFFF);
h6 = AND(h6 + g, 0xFFFFFFFF);
h7 = AND(h7 + h, 0xFFFFFFFF);
game:GetService("RunService").Heartbeat:Wait()
end
public.init = function()
queue.reset();
h0 = 0x6a09e667;
h1 = 0xbb67ae85;
h2 = 0x3c6ef372;
h3 = 0xa54ff53a;
h4 = 0x510e527f;
h5 = 0x9b05688c;
h6 = 0x1f83d9ab;
h7 = 0x5be0cd19;
return public;
end
public.update = function(bytes)
for b in bytes do
queue.push(b);
if queue.size() >= 64 then processBlock(); end
end
return public;
end
public.finish = function()
local bits = queue.getHead() * 8;
queue.push(0x80);
while ((queue.size()+7) % 64) < 63 do
queue.push(0x00);
end
local b0,b1,b2,b3,b4,b5,b6,b7 = dword2bytes(bits);
queue.push(b0);
queue.push(b1);
queue.push(b2);
queue.push(b3);
queue.push(b4);
queue.push(b5);
queue.push(b6);
queue.push(b7);
while queue.size() > 0 do
processBlock();
end
return public;
end
public.asBytes = function()
local b0, b1, b2, b3 = word2bytes(h0);
local b4, b5, b6, b7 = word2bytes(h1);
local b8, b9,b10,b11 = word2bytes(h2);
local b12,b13,b14,b15 = word2bytes(h3);
local b16,b17,b18,b19 = word2bytes(h4);
local b20,b21,b22,b23 = word2bytes(h5);
local b24,b25,b26,b27 = word2bytes(h6);
local b28,b29,b30,b31 = word2bytes(h7);
return { b0, b1, b2, b3, b4, b5, b6, b7, b8, b9,b10,b11,b12,b13,b14,b15
,b16,b17,b18,b19,b20,b21,b22,b23,b24,b25,b26,b27,b28,b29,b30,b31};
end
public.asHex = function()
local b0, b1, b2, b3 = word2bytes(h0);
local b4, b5, b6, b7 = word2bytes(h1);
local b8, b9,b10,b11 = word2bytes(h2);
local b12,b13,b14,b15 = word2bytes(h3);
local b16,b17,b18,b19 = word2bytes(h4);
local b20,b21,b22,b23 = word2bytes(h5);
local b24,b25,b26,b27 = word2bytes(h6);
local b28,b29,b30,b31 = word2bytes(h7);
local fmt = "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
return String.format(fmt, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9,b10,b11,b12,b13,b14,b15
,b16,b17,b18,b19,b20,b21,b22,b23,b24,b25,b26,b27,b28,b29,b30,b31);
end
return public;
end
return SHA2_256;
================================================
FILE: src/ServerScriptService/analytics/GameAnalytics/lockbox/init.lua
================================================
local lockbox = {ALLOW_INSECURE = false}
for i,v in pairs(script:GetChildren()) do
lockbox[v.Name] = v
end
return lockbox
================================================
FILE: src/ServerScriptService/analytics/GameAnalytics/lockbox/mac/hmac.lua
================================================
local lockbox = script.Parent.Parent
local Bit = require(lockbox.util.bit);
local String = string;
local Stream = require(lockbox.util.stream);
local Array = require(lockbox.util.array);
local XOR = Bit.bxor;
local HMAC = function()
local public = {};
local blockSize = 64;
local Digest = nil;
local outerPadding = {};
local innerPadding = {}
local digest;
public.setBlockSize = function(bytes)
blockSize = bytes;
return public;
end
public.setDigest = function(digestModule)
Digest = digestModule;
digest = Digest();
return public;
end
public.setKey = function(key)
local keyStream;
if(Array.size(key) > blockSize) then
keyStream = Stream.fromArray(Digest()
.update(Stream.fromArray(key))
.finish()
.asBytes());
else
keyStream = Stream.fromArray(key);
end
outerPadding = {};
innerPadding = {};
for i=1,blockSize do
local byte = keyStream();
if byte == nil then byte = 0x00; end
outerPadding[i] = XOR(0x5C,byte);
innerPadding[i] = XOR(0x36,byte);
end
return public;
end
public.init = function()
digest .init()
.update(Stream.fromArray(innerPadding));
return public;
end
public.update = function(messageStream)
digest.update(messageStream);
return public;
end
public.finish = function()
local inner = digest.finish().asBytes();
digest .init()
.update(Stream.fromArray(outerPadding))
.update(Stream.fromArray(inner))
.finish();
return public;
end
public.asBytes = function()
return digest.asBytes();
end
public.asHex = function()
return digest.asHex();
end
return public;
end
return HMAC;
================================================
FILE: src/ServerScriptService/analytics/GameAnalytics/lockbox/mac/init.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/ServerScriptService/analytics/GameAnalytics/lockbox/util/array.lua
================================================
local lockbox = script.Parent.Parent
local String = string;
local Queue = require(lockbox.util.queue);
local Bit = require(lockbox.util.bit);
local XOR = Bit.bxor;
local Array = {};
Array.size = function(array)
return #array;
end
Array.fromString = function(string)
local bytes = {};
local i=1;
local byte = String.byte(string,i);
while byte ~= nil do
bytes[i] = byte;
i = i + 1;
byte = String.byte(string,i);
end
return bytes;
end
Array.toString = function(bytes)
local chars = {};
local i=1;
local byte = bytes[i];
while byte ~= nil do
chars[i] = String.char(byte);
i = i+1;
byte = bytes[i];
end
return table.concat(chars,"");
end
Array.fromStream = function(stream)
local array = {};
local i=1;
local byte = stream();
while byte ~= nil do
array[i] = byte;
i = i+1;
byte = stream();
end
return array;
end
Array.readFromQueue = function(queue,size)
local array = {};
for i=1,size do
array[i] = queue.pop();
end
return array;
end
Array.writeToQueue = function(queue,array)
local size = Array.size(array);
for i=1,size do
queue.push(array[i]);
end
end
Array.toStream = function(array)
local queue = Queue();
local i=1;
local byte = array[i];
while byte ~= nil do
queue.push(byte);
i=i+1;
byte = array[i];
end
return queue.pop;
end
local fromHexTable = {};
for i=0,255 do
fromHexTable[String.format("%02X",i)]=i;
fromHexTable[String.format("%02x",i)]=i;
end
Array.fromHex = function(hex)
local array = {};
for i=1,String.len(hex)/2 do
local h = String.sub(hex,i*2-1,i*2);
array[i] = fromHexTable[h];
end
return array;
end
local toHexTable = {};
for i=0,255 do
toHexTable[i]=String.format("%02X",i);
end
Array.toHex = function(array)
local hex = {};
local i = 1;
local byte = array[i];
while byte ~= nil do
hex[i] = toHexTable[byte];
i=i+1;
byte = array[i];
end
return table.concat(hex,"");
end
Array.concat = function(a,b)
local concat = {};
local out=1;
local i=1;
local byte = a[i];
while byte ~= nil do
concat[out] = byte;
i = i + 1;
out = out + 1;
byte = a[i];
end
local i=1;
local byte = b[i];
while byte ~= nil do
concat[out] = byte;
i = i + 1;
out = out + 1;
byte = b[i];
end
return concat;
end
Array.truncate = function(a,newSize)
local x = {};
for i=1,newSize do
x[i]=a[i];
end
return x;
end
Array.XOR = function(a,b)
local x = {};
for k,v in pairs(a) do
x[k] = XOR(v,b[k]);
end
return x;
end
Array.substitute = function(input,sbox)
local out = {};
for k,v in pairs(input) do
out[k] = sbox[v];
end
return out;
end
Array.permute = function(input,pbox)
local out = {};
for k,v in pairs(pbox) do
out[k] = input[v];
end
return out;
end
Array.copy = function(input)
local out = {};
for k,v in pairs(input) do
out[k] = v;
end
return out;
end
Array.slice = function(input,start,stop)
local out = {};
for i=start,stop do
out[i-start+1] = input[i];
end
return out;
end
return Array;
================================================
FILE: src/ServerScriptService/analytics/GameAnalytics/lockbox/util/base64.lua
================================================
local lockbox = script.Parent.Parent
local String = string;
local Bit = require(lockbox.util.bit);
local Array = require(lockbox.util.array);
local Stream = require(lockbox.util.stream);
local AND = Bit.band;
local OR = Bit.bor;
local NOT = Bit.bnot;
local XOR = Bit.bxor;
local LROT = Bit.lrotate;
local RROT = Bit.rrotate;
local LSHIFT = Bit.lshift;
local RSHIFT = Bit.rshift;
local SYMBOLS = {
[0]="A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P",
"Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f",
"g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v",
"w","x","y","z","0","1","2","3","4","5","6","7","8","9","+","/"};
local LOOKUP = {};
for k,v in pairs(SYMBOLS) do
LOOKUP[k]=v;
LOOKUP[v]=k;
end
local Base64 = {};
Base64.fromStream = function(stream)
local bits = 0x00;
local bitCount = 0;
local base64 = {};
local byte = stream();
while byte ~= nil do
bits = OR(LSHIFT(bits,8),byte);
bitCount = bitCount + 8;
while bitCount >= 6 do
bitCount = bitCount - 6;
local temp = RSHIFT(bits,bitCount);
table.insert(base64,LOOKUP[temp]);
bits = AND(bits,NOT(LSHIFT(0xFFFFFFFF,bitCount)));
end
byte = stream();
end
if (bitCount == 4) then
bits = LSHIFT(bits,2);
table.insert(base64,LOOKUP[bits]);
table.insert(base64,"=");
elseif (bitCount == 2) then
bits = LSHIFT(bits,4);
table.insert(base64,LOOKUP[bits]);
table.insert(base64,"==");
end
return table.concat(base64,"");
end
Base64.fromArray = function(array)
local bits = 0x00;
local bitCount = 0;
local base64 = {};
local ind = 1;
local byte = array[ind]; ind = ind + 1;
while byte ~= nil do
bits = OR(LSHIFT(bits,8),byte);
bitCount = bitCount + 8;
while bitCount >= 6 do
bitCount = bitCount - 6;
local temp = RSHIFT(bits,bitCount);
table.insert(base64,LOOKUP[temp]);
bits = AND(bits,NOT(LSHIFT(0xFFFFFFFF,bitCount)));
end
byte = array[ind]; ind = ind + 1;
end
if (bitCount == 4) then
bits = LSHIFT(bits,2);
table.insert(base64,LOOKUP[bits]);
table.insert(base64,"=");
elseif (bitCount == 2) then
bits = LSHIFT(bits,4);
table.insert(base64,LOOKUP[bits]);
table.insert(base64,"==");
end
return table.concat(base64,"");
end
Base64.fromString = function(string)
return Base64.fromArray(Array.fromString(string));
end
Base64.toStream = function(base64)
return Stream.fromArray(Base64.toArray(base64));
end
Base64.toArray = function(base64)
local bits = 0x00;
local bitCount = 0;
local bytes = {};
for c in String.gmatch(base64,".") do
if (c == "=") then
bits = RSHIFT(bits,2); bitCount = bitCount - 2;
else
bits = LSHIFT(bits,6); bitCount = bitCount + 6;
bits = OR(bits,LOOKUP[c]);
end
while(bitCount >= 8) do
bitCount = bitCount - 8;
local temp = RSHIFT(bits,bitCount);
table.insert(bytes,temp);
bits = AND(bits,NOT(LSHIFT(0xFFFFFFFF,bitCount)));
end
end
return bytes;
end
Base64.toString = function(base64)
local bits = 0x00;
local bitCount = 0;
local chars = {};
for c in String.gmatch(base64,".") do
if (c == "=") then
bits = RSHIFT(bits,2); bitCount = bitCount - 2;
else
bits = LSHIFT(bits,6); bitCount = bitCount + 6;
bits = OR(bits,LOOKUP[c]);
end
while(bitCount >= 8) do
bitCount = bitCount - 8;
local temp = RSHIFT(bits,bitCount);
table.insert(chars,String.char(temp));
bits = AND(bits,NOT(LSHIFT(0xFFFFFFFF,bitCount)));
end
end
return table.concat(chars,"");
end
return Base64;
================================================
FILE: src/ServerScriptService/analytics/GameAnalytics/lockbox/util/bit.lua
================================================
local lockbox = script.Parent.Parent
e = require(lockbox).bit
if not e then
error("no bitwise support found", 2)
end
-- Workaround to support Lua 5.2 bit32 API with the LuaJIT bit one
if e.rol and not e.lrotate then
e.lrotate = e.rol
end
if e.ror and not e.rrotate then
e.rrotate = e.ror
end
return e
================================================
FILE: src/ServerScriptService/analytics/GameAnalytics/lockbox/util/init.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/ServerScriptService/analytics/GameAnalytics/lockbox/util/queue.lua
================================================
local Queue = function()
local queue = {};
local tail = 0;
local head = 0;
local public = {};
public.push = function(obj)
queue[head] = obj;
head = head + 1;
return;
end
public.pop = function()
if tail < head
then
local obj = queue[tail];
queue[tail] = nil;
tail = tail + 1;
return obj;
else
return nil;
end
end
public.size = function()
return head - tail;
end
public.getHead = function()
return head;
end
public.getTail = function()
return tail;
end
public.reset = function()
queue = {};
head = 0;
tail = 0;
end
return public;
end
return Queue;
================================================
FILE: src/ServerScriptService/analytics/GameAnalytics/lockbox/util/stream.lua
================================================
local lockbox = script.Parent.Parent
local Queue = require(lockbox.util.queue);
local String = string;
local Stream = {};
Stream.fromString = function(string)
local i=0;
return function()
i=i+1;
if(i <= String.len(string)) then
return String.byte(string,i);
else
return nil;
end
end
end
Stream.toString = function(stream)
local array = {};
local i=1;
local byte = stream();
while byte ~= nil do
array[i] = String.char(byte);
i = i+1;
byte = stream();
end
return table.concat(array,"");
end
Stream.fromArray = function(array)
local queue = Queue();
local i=1;
local byte = array[i];
while byte ~= nil do
queue.push(byte);
i=i+1;
byte = array[i];
end
return queue.pop;
end
Stream.toArray = function(stream)
local array = {};
local i=1;
local byte = stream();
while byte ~= nil do
array[i] = byte;
i = i+1;
byte = stream();
end
return array;
end
local fromHexTable = {};
for i=0,255 do
fromHexTable[String.format("%02X",i)]=i;
fromHexTable[String.format("%02x",i)]=i;
end
Stream.fromHex = function(hex)
local queue = Queue();
for i=1,String.len(hex)/2 do
local h = String.sub(hex,i*2-1,i*2);
queue.push(fromHexTable[h]);
end
return queue.pop;
end
local toHexTable = {};
for i=0,255 do
toHexTable[i]=String.format("%02X",i);
end
Stream.toHex = function(stream)
local hex = {};
local i = 1;
local byte = stream();
while byte ~= nil do
hex[i] = toHexTable[byte];
i=i+1;
byte = stream();
end
return table.concat(hex,"");
end
return Stream;
================================================
FILE: src/ServerScriptService/analytics/init.server.lua
================================================
local Analytics = require(script.GameAnalytics)
local key = require(script.key)
local DEBUG = false
local errorchache = 3
Analytics:Init(key.GameKey, key.SecretKey)
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
function playerRemoving(Player)
if Player:FindFirstChild("SessionEnded") == nil then
if Player:FindFirstChild("teleporting") then
return false
end
if Player:FindFirstChild("AnalyticsSessionId") then
local Length = 0
if Player:FindFirstChild("JoinTime") then
Length = os.time() - Player.JoinTime.Value
end
Analytics:SendEvent({
["category"] = "session_end",
["length"] = math.floor(Length),
}, Player)
end
local Tag = Instance.new("BoolValue")
Tag.Name = "SessionEnded"
Tag.Parent = Player
end
end
game.Players.PlayerRemoving:connect(playerRemoving)
game:BindToClose(function()
if game:GetService("RunService"):IsStudio() then return end
for i,Player in pairs(game.Players:GetPlayers()) do
playerRemoving(Player)
end
end)
function playerRequestNewSession(Player)
if Player:FindFirstChild("AnalyticsSessionId") == nil then
local SessionId = game:GetService("HttpService"):GenerateGUID(false):lower()
local Tag = Instance.new("StringValue")
Tag.Name = "AnalyticsSessionId"
Tag.Value = SessionId
Tag.Parent = Player
local playerData = network:invoke("getPlayerData", Player)
if playerData then
playerData.nonSerializeData.incrementPlayerData("sessionCount", 1)
end
Analytics:SendEvent({
["category"] = "user",
["session_id"] = SessionId,
["user_id"] = tostring(Player.userId),
}, Player)
local TimeStamp = Instance.new("NumberValue")
TimeStamp.Value = os.time()
TimeStamp.Name = "JoinTime"
TimeStamp.Parent = Player
return SessionId
end
end
network:create("newSession","BindableFunction","OnInvoke",playerRequestNewSession)
network:create("requestNewSession","RemoteFunction","OnServerInvoke",function() end)
function playerRequestContinueSessionFromTeleport(Player, SessionId, JoinTimeStamp)
if Player:FindFirstChild("AnalyticsSessionId") == nil then
local Tag = Instance.new("StringValue")
Tag.Name = "AnalyticsSessionId"
Tag.Value = SessionId
Tag.Parent = Player
-- TimeStamp sanity check
if os.time() - JoinTimeStamp < 0 or os.time() - JoinTimeStamp > 250000 then
JoinTimeStamp = os.time()
end
local TimeStamp = Instance.new("NumberValue")
TimeStamp.Value = JoinTimeStamp
TimeStamp.Name = "JoinTime"
TimeStamp.Parent = Player
spawn(function()
Player:WaitForChild("DataLoaded",30)
network:invoke("reportAnalyticsEvent",Player,"teleport:arrived:"..(_G.placeName or "unknown"))
end)
return true
end
end
network:create("continueSession", "BindableFunction", "OnInvoke", playerRequestContinueSessionFromTeleport)
network:create("requestContinueSession","RemoteFunction","OnServerInvoke",function() warn("requestContinueSession has been moved to the server.") end)
local PurchaseMade = (function(Player, Category, Product, Amount)
local Cents = math.floor(Amount * 0.7) * 0.35
Product = Product or "unspecified"
Product = tostring(Product):gsub('%W','')
Analytics:SendEvent({
["category"] = "business",
["event_id"] = tostring(Category)..":"..Product,
["amount"] = math.floor(Cents),
["currency"] = "USD",
["transaction_num"] = 1,
}, Player)
if DEBUG then
end
end)
network:create("purchaseMade", "BindableFunction", "OnInvoke", PurchaseMade)
local CurrencyEvent = (function(Player, Currency, Amount, Source)
local flowType = "Source"
if Amount < 0 then
flowType = "Sink"
Amount = math.abs(Amount)
end
Source = Source or "unknown:unknown"
Analytics:SendEvent({
["category"] = "resource",
["event_id"] = flowType..":"..Currency..":"..Source,
["amount"] = Amount,
}, Player)
if DEBUG then
end
end)
network:create("reportCurrency", "BindableFunction", "OnInvoke", CurrencyEvent)
local ReportProgression = function(Player, Status, EventId, Attempt, Score)
local EventId = Status..":"..EventId
if Status == "Start" then
Analytics:SendEvent({
["category"] = "progression",
["event_id"] = EventId,
}, Player)
elseif Status == "Fail" or Status == "Complete" then
Analytics:SendEvent({
["category"] = "progression",
["event_id"] = EventId,
["attempt_num"] = Attempt,
["score"] = Score
}, Player)
end
end
local ReportEvent = function(Player, EventId, Value)
Analytics:SendEvent({
["category"] = "design",
["event_id"] = EventId,
["value"] = Value,
}, Player)
end
network:create("reportAnalyticsEvent","BindableFunction","OnInvoke",ReportEvent)
local ReportError = (function(Player, Severity, Message)
if errorchache > 0 then
errorchache = errorchache - 1
Analytics:SendEvent({
["category"] = "error",
["severity"] = Severity,
["message"] = Message,
}, Player)
end
end)
network:create("reportError","BindableFunction","OnInvoke",ReportError)
local PlayerOpenBox = (function(Player, Box, VintageWin)
if VintageWin then
Analytics:SendEvent({
["category"] = "design",
["event_id"] = "vintagewin:"..string.lower(Box),
["value"] = 1
}, Player)
end
Analytics:SendEvent({
["category"] = "design",
["event_id"] = "box:"..string.lower(Box),
["value"] = 1
}, Player)
if DEBUG then
end
end)
--[[
local ErrorCache = {}
function ErrorExists(Error)
for i,Err in pairs(ErrorCache) do
if Err == Error then
return true
end
end
return false
end
game:GetService("ScriptContext").Error:connect(function (message, stack)
local Error = tostring(message).." : "..tostring(stack)
if not ErrorExists(Error) then
Analytics:SendEvent({
["category"] = "error",
["severity"] = "error",
["message"] = "Server: "..Error
})
end
end)
]]
--[[
game.ReplicatedStorage.ClientError.OnServerEvent:connect(function(Player, message, stack)
Analytics:SendEvent({
["category"] = "error",
["severity"] = "error",
["message"] = tostring(Player.userId)..": "..tostring(message).." : "..tostring(stack)
})
end)
]]
================================================
FILE: src/ServerScriptService/analytics/key.lua
================================================
local module = {}
if game.GameId == 712031239 then
module.GameKey = "3e764352635e9469f42bd2c8a622f32a"
module.SecretKey = "b0a85553e528578e189756b992e9d8407968e6d7"
else
module.GameKey = "7018964337c285f95a5e4eb88adee721"
module.SecretKey = "f4757b325194bf9eab8e8800fd55ec67e002c43f"
end
return module
================================================
FILE: src/ServerScriptService/analytics/testing.lua
================================================
local module = {}
module.GameKey = "8a70de271d7a3ff4f1bf1008d726e32e"
module.SecretKey = "93cad05e13866a6e0d90c92af7cc5cd5c120fecb"
return module
================================================
FILE: src/ServerScriptService/contents/datastoreInterface.lua
================================================
local module = {}
module.priority = 3
-- DATASTORE WRAPPER TO RETURN playerSaveFileData, table storing all there is to know about the player's
-- file on that save!
-- Authors: Polymorphic, berezaa
local RunService = game:GetService("RunService")
-- set this flag to true to do data modification in studio
-- misuse of this would be a mistake, so make sure you're
-- careful with it. search its name to see where you can
-- do the data modification
local PERFORMING_STUDIO_PLAYER_DATA_MODIFICATION = false
local utilities
local network
local DataStoreService = game:GetService("DataStoreService")
local TESTING = false
local isInternal = (game.PlaceId == 2061558182 or game.ReplicatedStorage:FindFirstChild("doNotSaveData") or RunService:IsStudio())
--[[
inventorySlotData {}
number id
number stacks
number position [1 - MAX_INVENTORY_SLOTS]
> position is a unique number, game will violently remove
contents that overlap if no extra space is available
modifierData modifierData
equipmentSlotData {}
number id
number position
1 = weapon
2 = helmet
3 = body
4 = left arm
5 = right arm
6 = left leg
7 = right leg
> position is a unique number, game will violently remove
contents that overlap if no inventory space is available
accessory
hotbarSlotData {}
number id
dataType dataType [look at mapping]
number position
modifierData {}
-- all these refer to *flat* bonuses, you can make them percentage increases of the base stat
-- by making the stat [name]Percent, for example: expPercentage = 0.1 would mean a 10% increase of all exp gain
-- and dexPercent = 0.1 would mean a 10% increase of dex (after all flat amounts are summed)
int... [dex, int, str, vit, luck, exp]
string sourceType
int sourceId
int sourcePlayerId
statisticsModifier {}
int expiration
{modifierData} modifiers
accessoryData {}
accessoryType[mapping] accessoryType
int id
int color
modifierData modifierData
--> hat, skin, undershirt, underwear, face
{accessoryData} accessories {}
--]]
-- 21 = production version 3/3/2020
---WARNING-----------------
---WARNING-----------------
---WARNING-----------------
---WARNING-----------------
-- the global version of datstores, incrementing this lets you wipe everyone's data or revert back to a previous version
-- do not change for fun!
--local datastoreVersion = 21
local datastoreVersion = 35
-- 21: main production
-- been set to 21 since alpha-1.0.0
---WARNING-----------------
---WARNING-----------------
---WARNING-----------------
---WARNING-----------------
-- demo override
--if game.GameId == 712031239 then
-- datastoreVersion = 30
--end
local function GetMostRecentSaveTime(pages)
for _, Pair in pairs(pages:GetCurrentPage()) do
return Pair.value
end
end
--@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
--@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
local function __intGetPlayerGlobalSaveFileData(player, providedVersion)
local PlayerId = player.userId
--------------------------------------------------------------------------------------------------------------------------------------------------
-- GODS
local LastGlobalSave
-- obtain latest GDS version data from GODS
local GODSSuccess, GODSError = pcall(function()
local suffix = (game.ReplicatedStorage:FindFirstChild("mirrorWorld") and "mirror") or ""
local GlobalOrderedStore = DataStoreService:GetOrderedDataStore(tostring(PlayerId), "GlobalPlayerSaveTimes" .. datastoreVersion .. suffix)
local pages = GlobalOrderedStore:GetSortedAsync(false, 1)
LastGlobalSave = GetMostRecentSaveTime(pages)
end)
-- GODS call failed, abort.
if not GODSSuccess then
return false, nil, "(GODS): "..GODSError
end
--------------------------------------------------------------------------------------------------------------------------------------------------
-- GODS -> GDS
local GlobalData
local function loadVersion(version)
local GDSSuccess, GDSError = pcall(function()
local suffix = (game.ReplicatedStorage:FindFirstChild("mirrorWorld") and "mirror") or ""
local GlobalDataStore = DataStoreService:GetDataStore(tostring(PlayerId), "GlobalPlayerData" .. datastoreVersion .. suffix)
GlobalData = GlobalDataStore:GetAsync(tostring(version))
end)
-- GDS call failed, abort.
if not GDSSuccess then
return false, "(GDS): "..GDSError
end
return true, ""
end
if providedVersion then
local success, message = loadVersion(providedVersion)
if not success then
return false, nil, message
end
end
-- if providedVersion failed or we weren't given a providedVersion, do as normal
if not GlobalData then
local success, message = loadVersion(LastGlobalSave)
if not success then
return false, nil, message
end
end
if (LastGlobalSave and GlobalData == nil) then
local issue = "globalSave-NoData"
spawn(function()
network:invoke("reportError", player, "critical", "LastGlobalSave provided but no GlobalData??")
end)
player:Kick("Critical error prevented your data from being loaded. The developers have been alerted. Please try again later. Error code: "..issue)
error("Game rejected data.")
return false, nil, "game rejected data"
elseif providedVersion and LastGlobalSave == nil then
local issue = "globalSave-providedNoLast"
spawn(function()
network:invoke("reportError", player, "critical", "providedVersion but no LastGlobalSave")
end)
player:Kick("Critical error prevented your data from being loaded. The developers have been alerted. Please try again later. Error code: "..issue)
error("Game rejected data.")
return false, nil, "game rejected data"
end
-- this tool may be useful later
--[[
-- ROLLBACK!
local minTime = 1583707058
local maxTime = 1583710658
if GlobalData and GlobalData.lastSaveTimestamp then
if GlobalData.lastSaveTimestamp >= minTime and GlobalData.lastSaveTimestamp <= maxTime then
if not GlobalData.rollback1 then
game:GetService("TeleportService"):Teleport(2759159666, player)
return false, nil, "Requires rollback"
end
end
end
]]
--------------------------------------------------------------------------------------------------------------------------------------------------
return true, GlobalData, "Successfully loaded!"
end
function module:getPlayerGlobalSaveFileData(player, providedVersion)
return __intGetPlayerGlobalSaveFileData(player, providedVersion)
end
-- internal interface with datastores to save JUST global data
local function __intUpdatePlayerGlobalSaveFileData(PlayerId, playerGlobalData)
if game.PlaceId == 3372071669 or isInternal then
return true, nil, playerGlobalData.version
end
--------------------------------------------------------------------------------------------------------------------------------------------------
-- Sanity checks
if not playerGlobalData.version then
return false, "no global version"
end
--------------------------------------------------------------------------------------------------------------------------------------------------
-- GDS
local suffix = (game.ReplicatedStorage:FindFirstChild("mirrorWorld") and "mirror") or ""
local GlobalDataStore = DataStoreService:GetDataStore(tostring(PlayerId), "GlobalPlayerData" .. datastoreVersion .. suffix)
local GDSSuccess, GDSError = pcall(function()
GlobalDataStore:SetAsync(tostring(playerGlobalData.version), playerGlobalData)
end)
if not GDSSuccess then
warn("GDS update Failed!")
return false, "(GDS): ".. GDSError
end
--------------------------------------------------------------------------------------------------------------------------------------------------
-- GODS
local suffix = (game.ReplicatedStorage:FindFirstChild("mirrorWorld") and "mirror") or ""
local GlobalOrderedDataStore = DataStoreService:GetOrderedDataStore(tostring(PlayerId), "GlobalPlayerSaveTimes" .. datastoreVersion .. suffix)
local GODSSuccess, GODSError = pcall(function()
GlobalOrderedDataStore:SetAsync("s" .. tostring(playerGlobalData.version), playerGlobalData.version)
end)
if not GODSSuccess then
warn("GODS update Failed!")
return false, "(GODS): " .. GODSError
end
--------------------------------------------------------------------------------------------------------------------------------------------------
return true, nil, playerGlobalData.version
end
function module:updatePlayerGlobalSaveFileData(playerId, playerGlobalData)
return __intUpdatePlayerGlobalSaveFileData(playerId, playerGlobalData)
end
--@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
--@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
-- internal interface with datastores to get data
local function __intGetPlayerSaveFileData(player, saveFileNumber, desiredVersion)
-- dont load datastores in studio
if (RunService:IsStudio() or RunService:IsRunMode()) and (not PERFORMING_STUDIO_PLAYER_DATA_MODIFICATION) then
-- new adventure
if game.PlaceId == 4561988219 then
local Data = {}
Data.newb = true
Data.globalData = {}
return true, Data
end
if game.PlaceId == 4041427413 then
local Data = {}
Data.newb = true
Data.globalData = {}
return true, Data
end
local Tag = player:FindFirstChild("dataSlot")
if Tag == nil then
Tag = Instance.new("IntValue")
Tag.Name = "dataSlot"
Tag.Parent = player
Tag.Value = 0
--return true, game.HttpService:JSONDecode('{"quests":{"active":[{"objectives":[{"completion":{"amount":30},"triggerType":"monster-killed","requirement":{"amount":30,"monsterName":"Shroom"}}],"id":2}],"completed":[{"completionTime":1549512122.07898211479187011719,"id":1},{"completionTime":1549573249.58895897865295410156,"id":4},{"completionTime":1549671842.90531301498413085938,"id":5}]},"hotbar":[{"position":2,"dataType":1,"id":22},{"position":6,"dataType":2,"id":10},{"position":1,"dataType":1,"id":6},{"position":3,"dataType":1,"id":29},{"position":4,"dataType":1,"id":45},{"position":7,"dataType":1,"id":22},{"position":8,"dataType":1,"id":22},{"position":9,"dataType":1,"id":22},{"position":10,"dataType":1,"id":22},{"position":5,"dataType":1,"id":22}],"professions":{"fishing":{"level":1,"exp":0}},"sessionCount":52,"holding":[],"lastPhysicalPosition":[-57,38,-381],"flags":{"revokeCheatWeapons":true,"statCheck":true,"fixcolors2":true,"badges2":true,"stealthRevoke":true,"removeSpiderQueenCrown":true},"internalData":{"suspicion":0},"newb":false,"equipment":[{"modifierData":[{"baseDamage":3},{"baseDamage":3},{"baseDamage":3},{"baseDamage":3},{"baseDamage":3},{"baseDamage":3},{"baseDamage":3}],"successfulUpgrades":7,"upgrades":7,"id":50,"position":1,"stacks":1},{"position":8,"stacks":1,"id":84},{"position":2,"stacks":1,"id":85}],"moneyFlag2":true,"abilities":[{"rank":5,"id":1},{"rank":3,"id":2},{"rank":1,"id":3},{"rank":10,"id":6},{"rank":1,"id":7},{"rank":5,"id":10}],"statusEffects":[],"inventory":[{"position":25,"stacks":7,"id":89},{"position":24,"stacks":33,"id":88},{"position":23,"stacks":1,"id":14},{"position":22,"stacks":1,"id":6},{"spawnChance":0.5,"id":60,"stacks":1,"position":22,"itemName":"goblin necklace"},{"spawnChance":0.0500000000000000027755575615629,"id":87,"stacks":12,"position":21,"itemName":"arrow"},{"spawnChance":0.299999999999999988897769753748,"id":88,"stacks":7,"position":21,"itemName":"mana potion 2"},{"position":20,"stacks":1,"id":64},{"position":19,"stacks":1,"id":14},{"position":18,"stacks":22,"id":22},{"spawnChance":0.0400000000000000008326672684689,"id":89,"stacks":99,"position":17,"itemName":"health potion 3"},{"position":16,"stacks":23,"id":96},{"position":16,"stacks":5,"id":22},{"position":15,"stacks":70,"id":95},{"position":15,"stacks":5,"id":22},{"position":14,"stacks":99,"id":96},{"position":14,"stacks":5,"id":22},{"position":13,"stacks":99,"id":96},{"position":13,"stacks":6,"id":22},{"position":12,"stacks":99,"id":96},{"spawnChance":0.100000000000000005551115123126,"id":47,"stacks":2,"position":12,"itemName":"intelligence potion"},{"position":11,"stacks":16,"id":22},{"position":11,"stacks":99,"id":96},{"position":10,"stacks":99,"id":96},{"position":10,"stacks":5,"id":22},{"position":9,"stacks":99,"id":96},{"position":9,"stacks":5,"id":22},{"position":8,"stacks":6,"id":22},{"position":8,"stacks":99,"id":96},{"position":7,"stacks":5,"id":22},{"position":7,"stacks":1,"id":50},{"position":7,"stacks":99,"id":95},{"position":6,"stacks":9,"id":22},{"spawnChance":0.900000000000000022204460492503,"id":86,"stacks":99,"position":6,"itemName":"hay"},{"position":6,"stacks":1,"id":81},{"position":5,"stacks":7,"id":77},{"position":5,"stacks":1,"id":37},{"position":5,"stacks":5,"id":22},{"position":4,"stacks":50,"id":31},{"position":4,"stacks":1,"id":21},{"position":4,"stacks":5,"id":22},{"spawnChance":0.0100000000000000002081668171172,"id":20,"stacks":1,"position":3,"itemName":"rake"},{"spawnChance":0.00800000000000000016653345369377,"id":45,"stacks":10,"position":3,"itemName":"vitality potion"},{"spawnChance":0.699999999999999955591079014994,"id":10,"stacks":2,"position":3,"itemName":"mushroom beard"},{"position":2,"stacks":56,"id":86},{"position":2,"stacks":5,"id":22},{"spawnChance":0.0400000000000000008326672684689,"id":7,"stacks":1,"position":2,"itemName":"wooden club"},{"position":1,"stacks":99,"id":6},{"spawnChance":1,"id":9,"stacks":81,"position":1,"itemName":"mushroom spore"},{"position":1,"stacks":1,"id":36}],"statistics":{"pointsUnassigned":0,"pointsAssigned":0,"modifierData":[],"dex":35,"int":1,"vit":17,"str":25},"lastLocation":2471035818,"treasureChests":{"ChestSpiderQnEZ":{"time":1549809595},"ChestJungleCave":{"time":1549807288},"ChestJungleHighUp":{"time":1549610051},"ChestCaveRoom1":{"time":1549513159},"ChestGhrotto":{"time":1549671139},"ChestJungleView":{"time":1549803003},"ChestBar":{"time":1549602753},"ChestWaterBridge":{"time":1549567414},"ChestUnderCave":{"time":1549567402},"ChestJungleCannon":{"time":1549567251},"IronChestDitch":{"chestType":"ironChest","disabled":true},"ChestJungleBarricade":{"time":1549608072},"IronChestSpiderCave":{"chestType":"ironChest","disabled":true},"ChestGoldFIsh":{"chestType":"goldChest","disabled":true},"ChestCave":{"time":1549514147},"ChestBridge":{"time":1549600487},"ChestUnderBridge":{"time":1549515639},"ChestCaveRoom2":{"time":1549513160}},"timestamp":511,"level":26,"gold":221250,"exp":4377.38674400269519537687301636,"hasCustomizedCharacter":true,"accessories":{"hair":1,"undershirt":1,"shirtColorId":1,"face":1,"skinColorId":1,"hairColorId":1,"underwear":1},"class":"Hunter","userSettings":{"keybinds":{"W":"pick up","Q":"interact"},"clearingInteraction":true},"abilityBooks":{"hunter":{"pointsAssigned":16},"adventurer":{"pointsAssigned":9}}}')
end
local inven = {gold = 10; level = 1; inventory = {}; equipment = {}; moneyFlag2 = true; isTestingDontSave = true; abilityBooks = {}; abilities = {};}
inven.globalData = {
}
inven.flags = inven.flags or {}
inven.flags.enchantWipe3 = true
inven.flags.completedGauntlet = true
inven.inventory = {
}
inven.equipment = {
{id = 5, position = 1}, -- main hand
--{id = 179, position = 2}, -- hat
}
return true, inven
end
local Data
local PlayerId = player.userId
local Slot = saveFileNumber
local providedVersion
if desiredVersion then
providedVersion = desiredVersion
end
--------------------------------------------------------------------------------------------------------------------------------------------------
-- GODS + GDS
local Suffix = "-slot"..tostring(Slot)
local LastSave
local GlobalDataSuccess, GlobalData, GlobalDataStatus = __intGetPlayerGlobalSaveFileData(player, providedVersion)
if not GlobalDataStatus then
return false, nil, GlobalDataStatus
end
if GlobalData then
LastSave = GlobalData.lastSave[Suffix]
end
--------------------------------------------------------------------------------------------------------------------------------------------------
-- ODS (legacy)
-- No data from new method, revert to old method for LastSave
if not LastSave then
warn("DSI>Obtaining data from legacy method")
-- obtain latest data from ODS
local ODSSuccess, ODSError = pcall(function()
local suffix = (game.ReplicatedStorage:FindFirstChild("mirrorWorld") and "mirror") or ""
local OrderedStore = DataStoreService:GetOrderedDataStore(tostring(PlayerId), "PlayerSaveTimes" .. datastoreVersion .. Suffix .. suffix) -- dont worry i hate this too
local pages = OrderedStore:GetSortedAsync(false, 1)
LastSave = GetMostRecentSaveTime(pages) or 0
end)
-- ODS call failed, abort.
if not ODSSuccess then
return false, nil, "(ODS): "..ODSError
end
end
--------------------------------------------------------------------------------------------------------------------------------------------------
-- Sanity Checks
--[[
if (providedTimeStamp and LastSave == nil) then
local issue = "lastSave-nil"
spawn(function()
network:invoke("reportError", player, "critical", "LastSave nil for providedTimeStamp")
end)
player:Kick("Critical error prevented your data from being loaded. The developers have been alerted. Please try again later. Error code: "..issue)
error("Game rejected data.")
return false, nil, "game rejected data"
elseif (providedTimeStamp and providedTimeStamp > 1) and (LastSave and LastSave <= 1) then
local issue = "lastSave-lessThan"
spawn(function()
network:invoke("reportError", player, "critical", "LastSave lessThan for providedTimeStamp")
end)
player:Kick("Critical error prevented your data from being loaded. The developers have been alerted. Please try again later. Error code: "..issue)
error("Game rejected data.")
return false, nil,"game rejected data"
end
if providedTimeStamp and providedTimeStamp > LastSave then
-- The player passed a timestamp that is newer than what the ODS has recorded
LastSave = providedTimeStamp
end
]]
--------------------------------------------------------------------------------------------------------------------------------------------------
-- Load from DataStore, this should be the same
local DSSuccess, DSError
-- player data is available
if LastSave > 1 then
DSSuccess, DSError = pcall(function()
-- This data slot should have data on it
local suffix = (game.ReplicatedStorage:FindFirstChild("mirrorWorld") and "mirror") or ""
local DataStore = DataStoreService:GetDataStore(tostring(PlayerId), "PlayerData" .. datastoreVersion .. Suffix .. suffix) -- Suffix .. suffix xD
Data = DataStore:GetAsync(tostring(LastSave))
Data.globalData = GlobalData or {}
local issue
--------------------------------------------------------------------------------------------------------------------------------------------------
-- More sanity checks
if Data == nil then
if --[[ providedTimeStamp ]] false then
spawn(function()
network:invoke("reportError", player, "critical", "DataStores returned nil data for provided timestamp")
end)
issue = "nil-provided"
else
spawn(function()
network:invoke("reportError", player, "critical", "DataStores returned nil data for recorded timestamp")
end)
issue = "nil-recorded"
end
elseif typeof(Data) ~= "table" then
spawn(function()
network:invoke("reportError", player, "critical", "DataStores returned invalid (non-table) data")
end)
issue = "invalid"
elseif Data.level == nil or Data.inventory == nil or Data.gold == nil or Data.equipment == nil then
spawn(function()
network:invoke("reportError", player, "critical", "DataStores returned data with missing values")
end)
issue = "missing"
end
if issue then
player:Kick("Critical error prevented your data from being loaded. The developers have been alerted. Please try again later. Error code: "..issue)
error("Game rejected data.")
end
-- apply timestamp (for legacy saves)
if Data and not Data.timestamp then
Data.timestamp = LastSave
end
end)
else
DSSuccess = true
Data = {}
Data.newb = true
Data.globalData = GlobalData or {}
end
-- DataStore failed, return error:
if not DSSuccess then
return false, nil, "(DS): "..DSError
end
--------------------------------------------------------------------------------------------------------------------------------------------------
-- All good
local Tag = player:FindFirstChild("dataSlot")
if Tag == nil then
Tag = Instance.new("IntValue")
Tag.Name = "dataSlot"
Tag.Parent = player
end
Tag.Value = Slot
return true, Data
end
-- internal interface with datastores to save data
local function __intUpdatePlayerSaveFileData(PlayerId, saveFileNumber, playerSaveFileData, playerGlobalData)
if game.ReplicatedStorage:FindFirstChild("doNotSaveData") then
return true, "don't save data here"
end
local Slot = saveFileNumber
playerSaveFileData.lastSaveTimestamp = os.time()
playerGlobalData.lastSaveTimestamp = os.time()
--------------------------------------------------------------------------------------------------------------------------------------------------
-- Sanity checks
if not playerGlobalData.version then
return false, "no global version"
end
if not playerSaveFileData.timestamp then
return false, "no data timestamp"
end
local Suffix = "-slot"..tostring(Slot)
--------------------------------------------------------------------------------------------------------------------------------------------------
-- GDS
local suffix = (game.ReplicatedStorage:FindFirstChild("mirrorWorld") and "mirror") or ""
local GlobalDataStore = DataStoreService:GetDataStore(tostring(PlayerId), "GlobalPlayerData" .. datastoreVersion .. suffix)
local GDSSuccess, GDSError = pcall(function()
GlobalDataStore:SetAsync(tostring(playerGlobalData.version), playerGlobalData)
end)
if not GDSSuccess then
warn("GDS update Failed!")
return false, "(GDS): ".. GDSError
end
--------------------------------------------------------------------------------------------------------------------------------------------------
-- DS
local suffix = (game.ReplicatedStorage:FindFirstChild("mirrorWorld") and "mirror") or ""
local DataStore = DataStoreService:GetDataStore(tostring(PlayerId), "PlayerData" .. datastoreVersion .. Suffix .. suffix) -- we need another programmer
local DSSuccess, DSError = pcall(function()
DataStore:SetAsync(tostring(playerSaveFileData.timestamp), playerSaveFileData)
end)
if not DSSuccess then
warn("DS update Failed!")
return false, "(DS): " .. DSError
end
--------------------------------------------------------------------------------------------------------------------------------------------------
-- GODS
local suffix = (game.ReplicatedStorage:FindFirstChild("mirrorWorld") and "mirror") or ""
local GlobalOrderedDataStore = DataStoreService:GetOrderedDataStore(tostring(PlayerId), "GlobalPlayerSaveTimes" .. datastoreVersion .. suffix)
local GODSSuccess, GODSError = pcall(function()
GlobalOrderedDataStore:SetAsync("s" .. tostring(playerGlobalData.version), playerGlobalData.version)
end)
if not GODSSuccess then
warn("GODS update Failed!")
return false, "(GODS): " .. GODSError
end
--------------------------------------------------------------------------------------------------------------------------------------------------
return true, nil, playerGlobalData.version
end
if PERFORMING_STUDIO_PLAYER_DATA_MODIFICATION then
delay(10, function()
-- player setup
local fakePlayer = {
userId = 133827873,
UserId = 133827873,
FindFirstChild = function()
return {}
end,
}
-- data loading
local success, data = __intGetPlayerSaveFileData(fakePlayer, 1)
-- data modification here
data.level = 30
-- data saving here
local success, reason, version = __intUpdatePlayerSaveFileData(fakePlayer.UserId, 1, data, data.globalData)
print(string.format(
"data modification for UserId %d was %s because %s. Current version: %d.",
fakePlayer.UserId,
success and "successful" or "unsuccessful",
success and "nothing went wrong" or reason,
version
))
end)
end
local function int__modifyPlayerSaveFileIncludeDefaults(player, saveFileNumber, playerSaveFileData)
playerSaveFileData.accessories = playerSaveFileData.accessories or {}
playerSaveFileData.professions = playerSaveFileData.professions or {}
playerSaveFileData.statistics = playerSaveFileData.statistics or {}
playerSaveFileData.statistics.str = playerSaveFileData.statistics.str or 0
playerSaveFileData.statistics.int = playerSaveFileData.statistics.int or 0
playerSaveFileData.statistics.dex = playerSaveFileData.statistics.dex or 0
playerSaveFileData.statistics.vit = playerSaveFileData.statistics.vit or 0
playerSaveFileData.statistics.modifierData = playerSaveFileData.statistics.modifierData or {}
playerSaveFileData.statistics.pointsAssigned = playerSaveFileData.statistics.pointsAssigned or 0
playerSaveFileData.statistics.pointsUnassigned = playerSaveFileData.statistics.pointsUnassigned or 0
playerSaveFileData.statusEffects = playerSaveFileData.statusEffects or {}
playerSaveFileData.flags = playerSaveFileData.flags or {
dataRecovery11_14 = true,
}
playerSaveFileData.gold = playerSaveFileData.gold or 10
playerSaveFileData.exp = playerSaveFileData.exp or 0
playerSaveFileData.level = playerSaveFileData.level or 1
playerSaveFileData.internalData = playerSaveFileData.internalData or {}
playerSaveFileData.internalData.suspicion = playerSaveFileData.internalData.suspicion or 0
if not playerSaveFileData.abilitiesReset2 then
playerSaveFileData.abilities = {}
playerSaveFileData.hotbar = {}
playerSaveFileData.abilitiesReset2 = true
end
-- TODO: REMOVE ON 01/12/2018 [DD/MM/YYYY]
if not playerSaveFileData.flags.stealthRevoke then
for i, abilitySlotData in pairs(playerSaveFileData.abilities) do
if abilitySlotData.id == 15 and not playerSaveFileData.abilityBooks.Admin then
table.remove(playerSaveFileData.abilities, i)
if playerSaveFileData.abilityBooks.Hunter then
playerSaveFileData.abilityBooks.Hunter.pointsAssigned = playerSaveFileData.abilityBooks.Hunter.pointsAssigned - (abilitySlotData.rank or 0)
end
elseif playerSaveFileData.abilityBooks.Admin then
end
end
playerSaveFileData.flags.stealthRevoke = true
end
if not playerSaveFileData.flags.fixcolors2 then
if playerSaveFileData.accessories then
if playerSaveFileData.accessories.skinColor then
playerSaveFileData.accessories.skinColorId = playerSaveFileData.accessories.skinColor
end
if playerSaveFileData.accessories.faceColor then
playerSaveFileData.accessories.faceColorId = playerSaveFileData.accessories.faceColor
end
if playerSaveFileData.accessories.hairColor then
playerSaveFileData.accessories.hairColorId = playerSaveFileData.accessories.hairColor
end
end
playerSaveFileData.flags.fixcolors2 = true
end
if not playerSaveFileData.flags.fixcolors3 then
if playerSaveFileData.accessories then
if playerSaveFileData.accessories.shirtColor then
playerSaveFileData.accessories.shirtColorId = playerSaveFileData.accessories.shirtColor
end
end
playerSaveFileData.flags.fixcolors3 = true
end
playerSaveFileData.accessories.shirtColorId = playerSaveFileData.accessories.shirtColorId or 1
if TESTING then
playerSaveFileData.gold = 10000
playerSaveFileData.level = 10
end
playerSaveFileData.keyPreferences = playerSaveFileData.keyPreferences or nil
playerSaveFileData.health = playerSaveFileData.health or nil
playerSaveFileData.savePosition = playerSaveFileData.savePosition or nil
playerSaveFileData.class = playerSaveFileData.class or "Adventurer"
playerSaveFileData.subclass = playerSaveFileData.subclass or nil
playerSaveFileData.treasureChests = playerSaveFileData.treasureChests or {}
playerSaveFileData.quests = playerSaveFileData.quests or {}
playerSaveFileData.quests.completed = playerSaveFileData.quests.completed or {}
playerSaveFileData.quests.active = playerSaveFileData.quests.active or {}
-------------------
playerSaveFileData.userSettings = playerSaveFileData.userSettings or {}
playerSaveFileData.sessionCount = playerSaveFileData.sessionCount or 1
-- information related to player's save, how to handle errors, etc
playerSaveFileData.nonSerializeData = {}
playerSaveFileData.nonSerializeData.saveFileNumber = saveFileNumber
playerSaveFileData.nonSerializeData.playerPointer = player
playerSaveFileData.nonSerializeData.isGlobalPVPEnabled = false
playerSaveFileData.nonSerializeData.whitelistPVPEnabled = {}
playerSaveFileData.nonSerializeData.temporaryEquipment = {}
playerSaveFileData.nonSerializeData.perksActivated = {}
end
function module:getLatestSaveVersion(player)
local playerId = player.UserId
local globalOrderedStore = DataStoreService:GetOrderedDataStore(tostring(playerId), "GlobalPlayerSaveTimes" .. datastoreVersion)
local pages = globalOrderedStore:GetSortedAsync(false, 1)
if not pages then
return 0
end
pages = pages:GetCurrentPage()
if not pages[1] then
return 0
end
return pages[1].value
end
function module:getPlayerSaveFileDataOlderThan(player, saveFileNumber, timestamp)
timestamp = tonumber(timestamp)
print(string.format("Attempting to find save data with timestamp older than %d.", timestamp))
local playerId = player.UserId
local globalOrderedStore = DataStoreService:GetOrderedDataStore(tostring(playerId), "GlobalPlayerSaveTimes" .. datastoreVersion)
local pages = globalOrderedStore:GetSortedAsync(false, 1)
if not pages then
print("Failed to acquire pages for save times. Reverting to default behavior.")
return self:getPlayerSaveFileData(player, saveFileNumber)
end
pages = pages:GetCurrentPage()
if not pages[1] then
print("Player does not have any lastSaveId possibilities. Reverting to default behavior.")
return self:getPlayerSaveFileData(player, saveFileNumber)
end
local lastSaveId = pages[1].value
lastSaveId = tonumber(lastSaveId)
local lastSaveOlderThanTimestamp
local lastSaveKey = "-slot"..saveFileNumber
local globalDataStore = DataStoreService:GetDataStore(tostring(playerId), "GlobalPlayerData" .. datastoreVersion)
local id = lastSaveId
while id > 0 do
local globalData = globalDataStore:GetAsync(tostring(id))
local lastSaveTimestamp = tonumber(globalData.lastSaveTimestamp)
print(string.format(
"Testing id %d: timestamp %d < %d?",
id,
lastSaveTimestamp,
timestamp
))
if lastSaveTimestamp <= timestamp then
print("Found id with older timestamp.")
lastSaveOlderThanTimestamp = globalData.lastSave[lastSaveKey]
-- this save file didn't exist before this timestamp, it's new
if not lastSaveOlderThanTimestamp then
print("This id does not have a file for this slot. Reverting to default behavior.")
return self:getPlayerSaveFileData(player, saveFileNumber)
else
print("Found save file for this id and this slot. Returning it.")
local success, playerData, message = self:getPlayerSaveFileData(player, saveFileNumber, lastSaveOlderThanTimestamp)
if success then
playerData.globalData.version = lastSaveId
end
return success, playerData, message
end
end
id = id - 1
end
print("Exhausted all lastSaveId possibilities. Reverting to default behavior.")
return self:getPlayerSaveFileData(player, saveFileNumber)
end
function module:getPlayerSaveFileData(player, saveFileNumber, desiredVersion)
local success, playerSaveFileData, errorMsg = __intGetPlayerSaveFileData(player, saveFileNumber, desiredVersion)
if not success then
warn("datastore-failure: ", player, errorMsg)
return success, playerSaveFileData, errorMsg
end
int__modifyPlayerSaveFileIncludeDefaults(player, saveFileNumber, playerSaveFileData)
return success, playerSaveFileData
end
function module:updatePlayerSaveFileData(playerId, playerSaveFileData)
-- do not save data in testing environment
if isInternal then
-- return true
return true, nil, 0
end
playerSaveFileData.newb = false
-- Record Keeping
local Slot = playerSaveFileData.nonSerializeData.saveFileNumber
local Suffix = "-slot"..tostring(Slot)
playerSaveFileData.timestamp = playerSaveFileData.timestamp + 1
playerSaveFileData.globalData.version = playerSaveFileData.globalData.version + 1
playerSaveFileData.globalData.lastSave = playerSaveFileData.globalData.lastSave or {}
playerSaveFileData.globalData.lastSave[Suffix] = playerSaveFileData.timestamp
playerSaveFileData.globalData.saveSlotData = playerSaveFileData.globalData.saveSlotData or {}
local lastLocation = game.ReplicatedStorage:FindFirstChild("lastLocationOverride") and game.ReplicatedStorage.lastLocationOverride.Value or game.PlaceId
if playerSaveFileData.lastLocationDeathOverride then
lastLocation = playerSaveFileData.lastLocationDeathOverride
playerSaveFileData.lastLocationDeathOverride = nil
end
playerSaveFileData.globalData.saveSlotData[Suffix] = {
level = playerSaveFileData.level;
class = playerSaveFileData.class;
lastLocation = lastLocation;
equipment = playerSaveFileData.equipment;
accessories = playerSaveFileData.accessories;
customized = true;
}
-- Processing
playerSaveFileData = utilities.copyTable(playerSaveFileData)
local nonSerializeData = playerSaveFileData.nonSerializeData
playerSaveFileData.nonSerializeData = nil
local globalData = playerSaveFileData.globalData
playerSaveFileData.globalData = nil
-- don't save lastPhysicalPosition if we're gonna get booted somewhere else
if game:GetService("ReplicatedStorage"):FindFirstChild("lastLocationOverride") then
playerSaveFileData.lastPhysicalPosition = nil
end
-- Last Minute Data
playerSaveFileData.lastLocation = lastLocation
-- Saving
return __intUpdatePlayerSaveFileData(playerId, Slot, playerSaveFileData, globalData)
end
function module:wipePlayerSaveFileData(player, currentPlayerData)
local playerSaveFileData = {}
int__modifyPlayerSaveFileIncludeDefaults(player, currentPlayerData.nonSerializeData.saveFileNumber, playerSaveFileData)
return module:updatePlayerSaveFileData(player.userId, playerSaveFileData)
end
function module.init(Modules)
utilities = Modules.utilities
network = Modules.network
end
return module
================================================
FILE: src/ServerScriptService/contents/inputStorage.lua
================================================
-- Server-side interface for saving player input preferences
local module = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
module.priority = 3
return module
================================================
FILE: src/ServerScriptService/contents/manager_ability.lua
================================================
local module = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local AbilityLookup = require(ReplicatedStorage.abilityLookup)
local abilityCooldownLookup = {}
local castedAbilityGUIDs = {}
--local cachedPlayerAbilityData = {}
local latencyForgiveness = 0.25
local maximumAbilityRenderDistance = 100
local network
local utilities
local abilityUtilities
-------------------------------------------------
---------------@ Core Functions @----------------
-------------------------------------------------
local function onPlayerAdded(player)
abilityCooldownLookup[player] = {}
castedAbilityGUIDs[player] = {}
local playerData = network:invoke("getPlayerData", player)
playerData.abilities[1] = {
level = 1;
experience = 0;
id = 1;
}
playerData.abilities[2] = {
level = 1;
experience = 0;
id = 2;
}
playerData.nonSerializeData.setPlayerData("abilities", playerData.abilities)
end
local function onPlayerRemoving(player)
abilityCooldownLookup[player] = nil
end
local function validateAbilityGUID(player, abilityID, guid)
if castedAbilityGUIDs[player] and castedAbilityGUIDs[player][abilityID] and castedAbilityGUIDs[player][abilityID][guid] then
return castedAbilityGUIDs[player][abilityID][guid]
end
return false
end
local function resetAbilityCooldown(player, abilityID)
if abilityCooldownLookup[player] ~= nil and abilityCooldownLookup[player][abilityID] ~= nil then
abilityCooldownLookup[player][abilityID] = nil
end
end
local function returnAbilityCooldown(player, abilityID)
if not abilityCooldownLookup[player] then return nil end
if not abilityCooldownLookup[player][abilityID] then return nil end
return abilityCooldownLookup[player][abilityID]
end
-------------------------------------------------
-------------@ Bindable Functions @--------------
-------------------------------------------------
-------------------------------------------------
--------------@ Remote Functions @---------------
-------------------------------------------------
--onUpdate Event
local function changeAbilityState(caster, requestedState, executionData)
local casterContainer = executionData.casterCharacter
if not casterContainer or not casterContainer.PrimaryPart or not utilities.isEntityManifestValid(casterContainer.PrimaryPart) then return "invalid_character" end
local serverExecuteTick = tick()
local casterData = nil
local player = game.Players:GetPlayerFromCharacter(casterContainer)
if player then
casterData = network:invoke("getPlayerData", player)
else
--Get Monsters Data Here
end
local abilityId = executionData.abilityId
local guid = executionData.abilityGuid
local abilityData = AbilityLookup[abilityId]
if not casterData or not abilityData then return "failed" end
----@ BEGIN STATE @----
if requestedState == "begin" then
if castedAbilityGUIDs[player][abilityId] == nil then
executionData["state"] = requestedState
executionData["guid"] = guid
local manaCost = abilityData.statistics.manaCost
local cooldown = abilityData.statistics.cooldown
--Get player level, add the exponent and calculate ability modifier TODO
executionData["abilityData"] = abilityData
local increasingStat, calculatedStat = abilityUtilities.calculateStats(casterData, abilityId)
if increasingStat and calculatedStat then
executionData["abilityData"][increasingStat] = calculatedStat
end
local canCast, error = abilityUtilities.canPlayerCast(player, casterData, abilityId)
if not canCast then
return canCast, error
end
if castedAbilityGUIDs[player][abilityId] and castedAbilityGUIDs[player][abilityId][guid] then return false, "already_begun" end
castedAbilityGUIDs[player][abilityId] = {}
castedAbilityGUIDs[player][abilityId][guid] = true
----@ ABILITY CAN BE CASTED @----
--Remove ManaCost from Players Mana
player.Character.PrimaryPart.mana.Value = (player.Character.PrimaryPart.mana.Value - manaCost)
--Calculate Cooldown Tick from Server vs Client
local clientTick = executionData.castTick
abilityCooldownLookup[player][abilityId] = serverExecuteTick
-- Cast Ability Server Side
abilityData:execute_server()
--Send Ability Cast to Clients
local nearbyPlayers = abilityUtilities.returnNearbyPlayers(player.Character.PrimaryPart.CFrame, maximumAbilityRenderDistance)
if nearbyPlayers then
network:fireClients("replicateAbilityLocally", nearbyPlayers, executionData, false)
end
--@ ABILITY BEGIN ENDED @--
else
warn("Ability already casted, player is attempting to re-cast from remote. Possible Exploiter or False Positive")
end
----@ UPDATE STATE @----
elseif requestedState == "update" then
if validateAbilityGUID(player, abilityId, guid) then
executionData["abilityData"] = abilityData
local increasingStat, calculatedStat = abilityUtilities.calculateStats(casterData, abilityId)
if increasingStat and calculatedStat then
executionData["abilityData"][increasingStat] = calculatedStat
end
-- Cast Ability Server Side
abilityData:execute_server_update()
--Send Ability Cast to Clients
local nearbyPlayers = utilities.returnNearbyPlayers(player.Character.PrimaryPart.CFrame, maximumAbilityRenderDistance)
if nearbyPlayers then
network:fireClients("replicateAbilityUpdateLocally", nearbyPlayers, executionData, false)
end
else
return false, "invalid_guid"
end
----@ END STATE @----
elseif requestedState == "end" then
if castedAbilityGUIDs[player][abilityId] ~= nil and castedAbilityGUIDs[player][abilityId][guid] ~= nil then
castedAbilityGUIDs[player][abilityId] = nil
end
end
end
-------------------------------------------------
----------------@ Main Function @----------------
-------------------------------------------------
function module.init(Modules)
network = Modules.network
utilities = Modules.utilities
abilityUtilities = Modules.abilityUtilities
--Register Player Added and Remove Functions Defined Above
network:connect("playerDataLoaded", "Event", onPlayerAdded)
game.Players.PlayerRemoving:Connect(onPlayerRemoving)
--Register all functions as network events/functions
network:create("requestAbilityStateUpdate", "RemoteEvent", "OnServerEvent", changeAbilityState)
network:create("validateAbilityGUID", "BindableFunction", "OnInvoke", validateAbilityGUID)
network:create("resetAbilityCooldown", "BindableEvent", "Event", resetAbilityCooldown)
network:create("requestAbilityCooldown", "RemoteFunction", "OnServerInvoke", returnAbilityCooldown)
network:create("returnAbilityCooldown", "BindableFunction", "OnInvoke", returnAbilityCooldown)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_bounty.lua
================================================
local module = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local network
local levels
local monsterLookup = require(ReplicatedStorage:WaitForChild("monsterLookup"))
local function playerRequest_claimBounty(player, monsterName)
local playerData = network:invoke("getPlayerData", player)
if not playerData then
return false, "PlayerData not found."
end
if not player:FindFirstChild("bountyHunter") then
return false, "Not a bounty hunter."
end
local monster = monsterLookup[monsterName]
if monster then
local page = monster.monsterBookPage
local bountyData = playerData.bountyBook
local monsterData = bountyData[monsterName]
if page and monsterData then
local bountyPageInfo = levels.bountyPageInfo[tostring(page)]
local lastBounty = monsterData.lastBounty or 0
local bountyInfo = bountyPageInfo[lastBounty + 1]
if bountyInfo and monsterData.kills >= bountyInfo.kills then
-- this line is duplicated in client monsterBook ui module
local money = math.floor(levels.getBountyGoldReward(bountyInfo, monster))
local rewards = {}
local wasRewarded = network:invoke("tradeItemsBetweenPlayerAndNPC", player, {}, 0, rewards, money, "etc:bounty")
if wasRewarded then
monsterData.lastBounty = lastBounty + 1
playerData.nonSerializeData.setPlayerData("bountyBook", bountyData)
return true
end
return false, "No room in inventory."
end
return false, "No bounty available."
end
return false, "Invalid monster."
end
end
function module.init(Modules)
network = Modules.network
levels = Modules.levels
network:create("playerRequest_claimBounty", "RemoteFunction", "OnServerInvoke", playerRequest_claimBounty)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_challenge.lua
================================================
local module = {}
local HttpService = game:GetService("HttpService")
local network
--[[
challengeRequestData {}
instance challenger
instance playerChallenged
tradeSessionData {}
string guid
string state
playerTradeSessionData playerTradeSessionData_player1
playerTradeSessionData playerTradeSessionData_player2
--]]
local pendingChallenge_guids = {}
local function clearPreviousChallengeRequests(player)
for guid, challengeRequestData in pairs(pendingChallenge_guids) do
if challengeRequestData.challenger == player or challengeRequestData.playerChallenged == player then
pendingChallenge_guids[guid] = nil
end
end
end
local function playerRequest_acceptChallengeRequest(player, guid)
if pendingChallenge_guids[guid] then
-- cancel all current trades involving player except for this trade.
local challengeRequestData = pendingChallenge_guids[guid]
clearPreviousChallengeRequests(challengeRequestData.challenger)
clearPreviousChallengeRequests(challengeRequestData.playerChallenged)
-- INVALIDATE THE GUI
pendingChallenge_guids[guid] = nil
if not challengeRequestData.challenger.Parent or not challengeRequestData.playerChallenged.Parent then
return false
end
if challengeRequestData.wager then
-- ensure they both have the gold to do the challenge
local playerData_challenger = network:invoke("getPlayerData", challengeRequestData.challenger)
local playerData_playerChallenged = network:invoke("getPlayerData", challengeRequestData.playerChallenged)
if playerData_challenger.gold < challengeRequestData.wager or playerData_playerChallenged.gold < challengeRequestData.wager then
return false
end
end
-- do something INSANE!!!!!!!!!!!!!!!!!!!!!!!!!!! (let them fight!)
network:invoke("requestPVPWhitelistPlayer_server", challengeRequestData.challenger, challengeRequestData.playerChallenged)
network:invoke("requestPVPWhitelistPlayer_server", challengeRequestData.playerChallenged, challengeRequestData.challenger)
network:fireClient("alertPlayerNotification", challengeRequestData.challenger, {text = "Your challenge with " .. challengeRequestData.playerChallenged.Name .. " was accepted, FIGHT!"})
network:fireClient("alertPlayerNotification", challengeRequestData.playerChallenged, {text = "You've accepted " .. challengeRequestData.challenger.Name .. "'s challenge! Fight!"})
return true
end
return false, "invalid or inactive guid"
end
local invitationCD = {}
local function playerRequest_requestChallenge(challenger, playerChallenged, wager)
if challenger and playerChallenged and challenger ~= playerChallenged and (not wager or (type(wager) == "number" and wager > 0)) then
if true then
if not invitationCD[challenger] or (tick() - invitationCD[challenger] > 3) then
invitationCD[challenger] = tick()
local guid = HttpService:GenerateGUID(false)
local challengeRequestData = {
challenger = challenger;
playerChallenged = playerChallenged;
wager = wager;
}
pendingChallenge_guids[guid] = challengeRequestData
-- under some circumstances, duel requests are accepted instantly
if (game.PlaceId == 4653017449) or (game.PlaceId == 2061558182) then
local guildId = game.ReplicatedStorage:FindFirstChild("guildId")
if guildId then
guildId = guildId.Value
local challengerGuildId = challenger:FindFirstChild("guildId")
local challengedGuildId = playerChallenged:FindFirstChild("guildId")
if challengerGuildId and challengedGuildId then
if challengerGuildId.Value == guildId then
if challengedGuildId.Value == guildId then
-- compare ranks
local challengerData = network:invoke("getGuildMemberData", challenger, guildId)
local challengedData = network:invoke("getGuildMemberData", playerChallenged, guildId)
if
challengerData and
challengedData and
challengerData.rank and
challengedData.rank and
network:invoke("getRankNumberFromRank", challengerData.rank) > network:invoke("getRankNumberFromRank", challengedData.rank)
then
-- a member of this guild has challenged a lower-ranked member of this guild to a duel, instantly accept
playerRequest_acceptChallengeRequest(playerChallenged, guid)
return true
end
else
-- a member of this hall has challenged a non-member, instantly accept
playerRequest_acceptChallengeRequest(playerChallenged, guid)
return true
end
end
elseif challengerGuildId and (not challengedGuildId) then
if challengerGuildId.Value == guildId then
-- a member of this hall has challenged a non-member, instantly accept
playerRequest_acceptChallengeRequest(playerChallenged, guid)
return true
end
end
end
end
network:fireClient("signal_playerChallengeRequest", playerChallenged, challenger, guid)
return true
else
return false, "stop sending challenges too fast"
end
end
elseif challenger == playerChallenged then
return false, "you can't challenge yourself."
end
return false, "invalid request"
end
local function onPlayerCharacterDied(player)
local playerData = network:invoke("getPlayerData", player)
if playerData then
for _, whitelistPVPPlayer in pairs(playerData.nonSerializeData.whitelistPVPEnabled) do
network:invoke("revokePVPWhitelistPlayer_server", player, whitelistPVPPlayer)
network:invoke("revokePVPWhitelistPlayer_server", whitelistPVPPlayer, player)
end
end
end
function module.init(Modules)
network = Modules.network
network:create("playerRequest_requestChallenge", "RemoteFunction", "OnServerInvoke", playerRequest_requestChallenge)
network:create("playerRequest_acceptChallengeRequest", "RemoteFunction", "OnServerInvoke", playerRequest_acceptChallengeRequest)
network:create("signal_playerChallengeRequest", "RemoteEvent")
network:create("signal_playerChallengeState", "RemoteEvent")
network:connect("playerCharacterDied", "Event", onPlayerCharacterDied)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_chat.lua
================================================
local module = {}
local textService = game:GetService("TextService")
local network
local function onPlayerChatted(player, message)
if message == "" then
return
end
local successForTextFilterResult, textFilterResult = pcall(function() textService:FilterStringAsync(message, player.userId) end)
if successForTextFilterResult then
for i, oPlayer in pairs(game.Players:GetPlayers()) do
if oPlayer ~= player then
local success, filterMessage = pcall(function() textFilterResult:GetChatForUserAsync(oPlayer.userId) end)
if success then
network:fireClient("playerChatted", oPlayer, player.Name, filterMessage)
else
network:fireClient("playerChatted", oPlayer, nil, "A message from " .. player.Name .. " failed to send.")
end
end
end
end
end
function module.init(Modules)
network = Modules.network
network:create("playerChatted", "RemoteEvent", "OnServerEvent", onPlayerChatted)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_crafting.lua
================================================
local module = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local itemLookup = require(ReplicatedStorage:WaitForChild("itemData"))
local network
local function playerRequest_craftItem(player, itemId)
local item = itemLookup[itemId]
if item and item.recipe then
local success, reason = network:invoke("tradeItemsBetweenPlayerAndNPC",
player,
item.recipe,
0,
{{id = item.id; stacks = 1}},
0,
"etc:crafting"
)
return success, reason
end
end
function module.init(Modules)
network = Modules.network
network:create("playerRequest_craftItem", "RemoteFunction", "OnServerInvoke", playerRequest_craftItem)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_damage.lua
================================================
local module = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local network
local placeSetup
local utilities
local mapping
local levels
local damage
local detection
local configuration
local projectile
local events
local itemLookupContainer = replicatedStorage.itemData
local itemLookup = require(itemLookupContainer)
local itemFolderLookup = replicatedStorage.assets:WaitForChild("items")
local perkLookup = require(replicatedStorage.perkLookup)
local abilityLookup = require(replicatedStorage.abilityLookup)
local monsterLookup = require(replicatedStorage.monsterLookup)
local entityManifestCollectionFolder
local rand = Random.new()
local critChanceRandom = Random.new()
local dodgeChanceRandom = Random.new()
local monsterDamageValidationData = {}
local weaponValidationData = {}
local playerDamageAnimationState = {}
local arrowsShotByPlayers = {}
local playerAbilityHitData = {}
local magicBallsByPlayer = {}
local WEAPON_TYPES_TO_SCAN = {"dagger"; "staff"; "sword"; "greatsword"; "dual"; "swordAndShield"}
local SAMPLE_POINTS_TO_TAKE = 5
-- how long client has to call for damage of an arrow
-- note: lifetime of projectile is 3 sec, but allow some latency!
local ARROW_DATA_MAX_LIFETIME = 3.5
-- time players have to chain another slash
local SLASH_CHAIN_WINDOW = 0.3
-- 125 ms forgiveness
local DAMAGE_ANIMATION_REQUEST_MISMATCH_LATENCY_FORGIVENESS = 0.125
local CRIT_DAMAGE_MULTIPLIER = 2
-- base value to decide how effective defensive stats are (VIT + DEX)
local DEFENSE_MODULATOR = 100
local getDamageMitigationByVIT do
-- note for the future: THIS MUST BE IN ORDER (INDEX WISE) BY ORDER OF THRESHOLDS FROM GREATEST TO LEAST!!!!
local thresholds = {
{threshold = 1.0; value = 0.0 / 3};
{threshold = 0.4; value = 0.5 / 3};
{threshold = 0.2; value = 1.0 / 3};
{threshold = 0.1; value = 2.0 / 3}
}
local function getThresholdForRatio(ratio)
local currThreshold
for i, mitigationData in ipairs(thresholds) do
if not currThreshold or ratio <= mitigationData.threshold then
currThreshold = mitigationData.threshold
else
break
end
end
return currThreshold
end
local function getNextThreshold(threshold)
for i, mitigationData in ipairs(thresholds) do
if mitigationData.threshold == threshold then
return thresholds[i + 1] and thresholds[i + 1].threshold
end
end
return nil
end
local function getPreviousThreshold(threshold)
for i, mitigationData in ipairs(thresholds) do
if mitigationData.threshold == threshold then
return thresholds[i - 1] and thresholds[i - 1].threshold
end
end
return nil
end
function getDamageMitigationByVIT(currentHealth, maxHealth, damageBeingDone, vit)
local startRatio = math.clamp(currentHealth / maxHealth, 0, 1)
local startRatioThreshold = getThresholdForRatio(startRatio)
local damageRatio = math.clamp((currentHealth - damageBeingDone) / maxHealth, 0, 1)
local damageRatioThreshold = getThresholdForRatio(damageRatio)
local healthAfterDamage = math.clamp(currentHealth - damageBeingDone, 0, maxHealth)
local thresholdsPassed = {}
for i, mitigationData in ipairs(thresholds) do
if mitigationData.threshold <= startRatioThreshold and mitigationData.threshold >= damageRatioThreshold then
table.insert(thresholdsPassed, {threshold = mitigationData.threshold; damageMarker = mitigationData.threshold * maxHealth; value = mitigationData.value})
end
end
local healthRemaining = currentHealth
local damageRemaining = damageBeingDone
local damageMitigation = 0
local vitRemaining = vit
for i = #thresholdsPassed, 1, -1 do
local mitigationData = thresholdsPassed[i]
if vitRemaining > 0 then
if i == #thresholdsPassed then
-- this is processed first since we're doing it reverse order
local healthInThisThreshold = math.abs(maxHealth * mitigationData.threshold - (currentHealth - damageBeingDone))
local vitConsumed = math.clamp(healthInThisThreshold / mitigationData.value, 0, vitRemaining)
vitRemaining = vitRemaining - vitConsumed
damageMitigation = damageMitigation + vitConsumed * mitigationData.value
elseif i == 1 then
-- this is processed last since we're doing reverse order
local nextThreshold = getNextThreshold(mitigationData.threshold)
local healthInThisThreshold = currentHealth - maxHealth * nextThreshold
local vitConsumed = math.clamp(healthInThisThreshold / mitigationData.value, 0, vitRemaining)
vitRemaining = vitRemaining - vitConsumed
damageMitigation = damageMitigation + vitConsumed * mitigationData.value
else
local nextThreshold = getNextThreshold(mitigationData.threshold)
local healthInThisThreshold = mitigationData.threshold * maxHealth - nextThreshold * maxHealth
local vitConsumed = math.clamp(healthInThisThreshold / mitigationData.value, 0, vitRemaining)
vitRemaining = vitRemaining - vitConsumed
damageMitigation = damageMitigation + vitConsumed * mitigationData.value
end
end
end
local damageBeingDonePostMitigation = math.clamp(damageBeingDone - damageMitigation, 0, math.huge)
return damageMitigation
end
end
local perksMaid = {} do
function perksMaid:isEquipmentPerkActivable(playerData, id)
end
function perksMaid:flagPerkAsActivated(player, id)
end
end
-- called when killing damage is applied to any entity
local function processEntityKillingBlow(serverHitbox, killer, damageData)
local killedMonster
local killedPlayer
if serverHitbox.entityType.Value == "monster" then
killedMonster = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox)
elseif serverHitbox.entityType.Value == "character" then
killedPlayer = game.Players:GetPlayerFromCharacter(serverHitbox.Parent)
end
if killer == nil then
return
end
local sourcePlayer
if killer.entityType.Value == "character" then
sourcePlayer = game.Players:GetPlayerFromCharacter(killer.Parent)
end
local sendNotification = true
if killedPlayer or (killedMonster and (killedMonster.boss or killedMonster.resilient or killedMonster.giant or killedMonster.superGiant or killedMonster.gigaGiant)) then
local possibleVerbs = {"defeated", "felled", "purged", "vanquished", "ended", "wiped", "finished"}
if serverHitbox and serverHitbox:FindFirstChild("maxHealth") and damageData.damage >= serverHitbox.maxHealth.Value * 0.5 then
possibleVerbs = {"absolutely obliterated", "completely demolished", "undeniably destroyed", "OOF`d"}
elseif damageData.damageType == "magical" then
possibleVerbs = {"disintegrated", "melted", "burned to a crisp", "blasted", "vaporized"}
elseif damageData.damageType == "ranged" then
possibleVerbs = {"sniped", "popped", "silenced", "struck"}
end
local verb = possibleVerbs[rand:NextInteger(1,#possibleVerbs)]
verb = verb or "defeated"
if damageData.isCritical then
verb = "critically "..verb
end
if killer then
local killerName = "???"
if killer.entityType.Value == "character" then
killerName = killer.Parent.Name
elseif killer.entityType.Value == "monster" then
killerName = killer.Name
local monster = network:invoke("getMonsterDataByMonsterManifest_server", killer)
if not monster then
return
end
if monster.specialName then
killerName = monster.specialName
end
if monster.gigaGiant then
killerName = "Colossal " .. killerName
elseif monster.superGiant then
killerName = "Super Giant " .. killerName
elseif monster.giant then
killerName = "Giant " .. killerName
end
end
if killer.state.Value == "dead" or killer.health.Value <= 0 or killer.Parent == nil then
killerName = "the late " .. killerName
end
local killedName
if killedPlayer then
killedName = killedPlayer.Name
elseif killedMonster then
killedName = serverHitbox.Name
if killedMonster.specialName then
killedName = killedMonster.specialName
end
if killedMonster.gigaGiant then
killedName = "Colossal " .. killedName
elseif killedMonster.superGiant then
killedName = "Super Giant " .. killedName
elseif killedMonster.giant then
killedName = "Giant " .. killedName
end
end
local text = "☠ " .. killedName .. " was " .. verb .. " by "..killerName.." ☠"
-- i just killed myself... should i be doing something different?
if sourcePlayer and killedPlayer and sourcePlayer == killedPlayer then
local deathTrapKillMessage = sourcePlayer:FindFirstChild("deathTrapKillMessage")
if deathTrapKillMessage then
sendNotification = false
text = deathTrapKillMessage.Value
deathTrapKillMessage:Destroy()
end
end
network:fireAllClients("signal_alertChatMessage", {
Text = text,
Font = Enum.Font.SourceSansBold,
Color = killedPlayer and Color3.fromRGB(255, 130, 100) or Color3.fromRGB(0, 255, 170),
})
end
if sendNotification and killer.entityType.Value == "character" and killedPlayer then
network:fire("playerKilledByPlayer", killedPlayer, sourcePlayer, damageData)
if game.PlaceId == 2103419922 then
-- level up on kill (Fight to survive!)
local killedData = network:invoke("getPlayerData", killedPlayer)
local gold = (killedData and killedData.gold or 0) + (killedData and killedData.level or 1) * 100
local playerData = network:invoke("getPlayerData", sourcePlayer)
local expForNextLevel = levels.getEXPToNextLevel(playerData.level)
playerData.nonSerializeData.incrementPlayerData("exp", expForNextLevel + 1)
pcall(function()
if playerData.level == 10 then
game.BadgeService:AwardBadge(sourcePlayer.userId, 2124528259)
network:fireAllClients("signal_alertChatMessage", {
Text = sourcePlayer.Name .. " is in purgatory.",
Font = Enum.Font.SourceSansBold,
Color = Color3.fromRGB(200, 200, 100),
})
elseif playerData.level == 100 then
game.BadgeService:AwardBadge(sourcePlayer.userId, 2124528261)
network:fireAllClients("signal_alertChatMessage", {
Text = sourcePlayer.Name .. " is ALIVE!",
Font = Enum.Font.SourceSansBold,
Color = Color3.fromRGB(150, 255, 30),
})
sourcePlayer:Kick("YOU ARE ALIVE!")
end
end)
playerData.nonSerializeData.incrementPlayerData("gold", gold)
end
network:fireAllClients("signal_playerKilledByPlayer", killedPlayer, sourcePlayer, damageData, verb)
end
end
end
-- monster is dealing damage to a player
-- player is dealing damage to a player
-- monster is dealing damage to a monster
-- todo: clean this up, doesnt make sense what this handles.
local function playerDamageRequest_server(sourcePlayer, serverHitbox, damageData)
if not serverHitbox:FindFirstChild("health") or not serverHitbox:FindFirstChild("maxHealth") then return false end
-- sourcePlayer, serverHitbox, damageToDeal, sourceType, sourceId
if serverHitbox.health.Value > 0 and serverHitbox:FindFirstChild("killingBlow") == nil then
local player = game.Players:GetPlayerFromCharacter(serverHitbox.Parent)
if player then
events:fireEventLocal("playerWillTakeDamage", player, damageData)
end
events:fireEventLocal("entityWillDealDamage", sourcePlayer, serverHitbox, damageData)
local newHealth = serverHitbox.health.Value - damageData.damage
if newHealth <= 0 then
local killerGUID = damageData.sourceEntityGUID
-- print("$",killerGUID)
local killer = utilities.getEntityManifestByEntityGUID(killerGUID)
-- print("$", killer)
local killingBlowTag = Instance.new("StringValue")
killingBlowTag.Name = "killingBlow"
killingBlowTag.Value = "damage"
local killingBlowSource = Instance.new("ObjectValue")
killingBlowSource.Name = "source"
killingBlowSource.Value = killer
killingBlowSource.Parent = killingBlowTag
killingBlowTag.Parent = serverHitbox
processEntityKillingBlow(serverHitbox, killer, damageData)
end
serverHitbox.health.Value = newHealth
if serverHitbox.health.Value <= 0 then
events:fireEventLocal("entityDiedTakingDamage", sourcePlayer, serverHitbox, damageData)
end
network:fireAllClients("signal_damage", serverHitbox, damageData)
end
return true
end
local function isWithinBounds(value, b1, b2, atkspd)
atkspd = atkspd or 1
return value / atkspd >= b1 / atkspd and value / atkspd <= b2 / atkspd
end
-- handles the processing for damageRequests made by non-bow weapons
-- ber: removed for causing issues with fast attack speeds
-- TODO: calculate a rolling average of damage requests and punish if its too high
local function isPlayerEquipmentDamageRequestWithinAnimationDamageSequence(player, serverHitbox)
return true
--[[
local currentPlayerDamageAnimationState = playerDamageAnimationState[player]
local playerData = network:invoke("getPlayerData", player)
if currentPlayerDamageAnimationState then
if weaponValidationData[currentPlayerDamageAnimationState.weaponType] then
if not currentPlayerDamageAnimationState.damageBlacklist[serverHitbox] then
local success = isWithinBounds(
tick() - currentPlayerDamageAnimationState.timestamp,
weaponValidationData[currentPlayerDamageAnimationState.weaponType][currentPlayerDamageAnimationState.state .. "_startDamageSequence_time"] - DAMAGE_ANIMATION_REQUEST_MISMATCH_LATENCY_FORGIVENESS,
weaponValidationData[currentPlayerDamageAnimationState.weaponType][currentPlayerDamageAnimationState.state .. "_stopDamageSequence_time"] + DAMAGE_ANIMATION_REQUEST_MISMATCH_LATENCY_FORGIVENESS,
1 + playerData.nonSerializeData.statistics_final.attackSpeed
)
-- blacklist damaging this hitbox
if success then
currentPlayerDamageAnimationState.damageBlacklist[serverHitbox] = true
end
return success
end
end
else
return true
end
return false
]]
end
local function isMagicBallDataUsableForDamageRequest(magicBallData, playerPositionAtDamageRequestTime, targetPositionAtDamageRequestTime)
if tick() - magicBallData.timestamp >= ARROW_DATA_MAX_LIFETIME then return false end
local playerPositionFromServerAtFiring = magicBallData.serverCharacterPosition
local playerPositionFromClientAtFiring = magicBallData.executionData["cast-origin"]
local unitDirection, targetPositionFromClientPredictive = projectile.getUnitVelocityToImpact_predictiveByAbilityExecutionData(
playerPositionFromClientAtFiring,
magicBallData.sourceWeaponBaseData.projectileSeeed or 50,
magicBallData.executionData,
0.0001
)
local degreesOffPredictedCourse = math.deg(math.acos(((targetPositionAtDamageRequestTime - playerPositionFromClientAtFiring).unit):Dot(unitDirection.unit)))
local degreesOffPredictedCourse2 = math.deg(math.acos(((targetPositionFromClientPredictive - playerPositionFromClientAtFiring).unit):Dot(unitDirection.unit)))
local degreesOffPredictedCourse3 = math.deg(math.acos(((targetPositionFromClientPredictive * Vector3.new(1, 0, 1) - playerPositionFromClientAtFiring * Vector3.new(1, 0, 1)).unit):Dot((unitDirection * Vector3.new(1, 0, 1)).unit)))
-- accept if its within 7 degrees off of perfect
return degreesOffPredictedCourse2 <= 7
end
local function isArrowDataUsableForDamageRequest(arrowData, playerPositionAtDamageRequestTime, targetPositionAtDamageRequestTime)
if tick() - arrowData.timestamp >= ARROW_DATA_MAX_LIFETIME then return false end
local playerPositionFromServerAtFiring = arrowData.serverCharacterPosition
local playerPositionFromClientAtFiring = arrowData.executionData["cast-origin"]
local unitDirection, targetPositionFromClientPredictive = projectile.getUnitVelocityToImpact_predictiveByAbilityExecutionData(
playerPositionFromClientAtFiring,
(arrowData.sourceWeaponBaseData.projectileSeeed or 200) * math.clamp(arrowData.executionData.bowChargeTime / configuration.getConfigurationValue("maxBowChargeTime"), 0.1, 1),
arrowData.executionData,
false
)
local degreesOffPredictedCourse = math.deg(math.acos(((targetPositionAtDamageRequestTime - playerPositionFromClientAtFiring).unit):Dot(unitDirection.unit)))
local degreesOffPredictedCourse2 = math.deg(math.acos(((targetPositionFromClientPredictive - playerPositionFromClientAtFiring).unit):Dot(unitDirection.unit)))
local degreesOffPredictedCourse3 = math.deg(math.acos(((targetPositionFromClientPredictive * Vector3.new(1, 0, 1) - playerPositionFromClientAtFiring * Vector3.new(1, 0, 1)).unit):Dot((unitDirection * Vector3.new(1, 0, 1)).unit)))
-- accept if its within 7 degrees off of perfect
return degreesOffPredictedCourse2 <= 7
end
local function calculatePlayerDamage(player, damageType, targetLevel, isTargetPlayer)
local playerData = network:invoke("getPlayerData", player)
local levelDifference = (playerData.level or 0) - targetLevel
local levelMulti = math.clamp((10 + (levelDifference/2)) / 10,0.2,2)
local damageRangeMultiplier = rand:NextInteger(95, 105) / 100
local stats = playerData.nonSerializeData.statistics_final
-- pvp has no level difference multiplier
-- very unfair with level differences otherwise.
if isTargetPlayer and configuration.getConfigurationValue("doNotApplyLevelMultiToPVP", player) then
levelMulti = 1
end
local damage = stats[damageType .. "Damage"]
return math.ceil(damage * levelMulti * damageRangeMultiplier)
--local damageToDealToMonster = math.floor((stats.equipmentDamage or 1) * levelMulti * damageRangeMultiplier) + stats.str
end
local arrowMultiHitCache = {}
local function playerRequest_damageEntity(player, serverHitbox, damagePosition, sourceType, sourceId, sourceTag, guid)
local playerData = network:invoke("getPlayerData", player)
if not playerData then
return false
end
local stats = playerData.nonSerializeData.statistics_final
if player.Character and player.Character.PrimaryPart and playerData and serverHitbox and (serverHitbox:IsDescendantOf(entityManifestCollectionFolder) or serverHitbox:IsDescendantOf(entityManifestCollectionFolder)) then
-- can this person even attack the target...?
if sourceType ~= "monster" and not damage.canPlayerDamageTarget(player, serverHitbox) then
return false
end
if serverHitbox:FindFirstChild("isDamageImmune") and serverHitbox.isDamageImmune.Value then
return false
end
local damageData, attackerStats, defenderStats
local isServerHitboxPlayer = (serverHitbox.entityType.Value == "character")
local abilityDamageMulti = 1
local hitsDoneToEntityManifestBySourceTag = 0
local isPlayerAttacker
if sourceType == "ability" or sourceType == "equipment" then
isPlayerAttacker = true
if sourceType == "ability" then
local abilityDamageGUIDData = network:invoke("validateAbilityGUID", player, sourceId, guid)
-- if guid is valid then player casted ability properly
if abilityDamageGUIDData then
print("2")
-- if this causes problems add abilityExecutionData after playerData
local abilityBaseData = abilityLookup[sourceId]
local abilityDamageType = "physical"
attackerStats = playerData.nonSerializeData.statistics_final
attackerStats.level = playerData.level
local health, maxHealth, targetLevel, targetDefense, defenderDex, defenderVit, targetPlayer, targetPlayerData = 0, 0, 0, 0, 0.1, 0.1, nil, nil do
if isServerHitboxPlayer then
health = serverHitbox.health.Value
maxHealth = serverHitbox.maxHealth.Value
targetPlayer = game.Players:GetPlayerFromCharacter(serverHitbox.Parent)
if targetPlayer then
targetPlayerData = network:invoke("getPlayerData", targetPlayer)
if targetPlayerData then
-- targetDefense = targetPlayerData.nonSerializeData.statistics_final[abilityDamageType .. "Defense"]
defenderStats = targetPlayerData.nonSerializeData.statistics_final
defenderStats.level = targetPlayerData.level
end
targetLevel = targetPlayer.level.Value
end
else
health = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "health")
maxHealth = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "maxHealth")
targetLevel = serverHitbox.level.Value
defenderStats = {
level = serverHitbox.level.Value;
dex = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "dex") or 0;
vit = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "vit") or 0;
str = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "str") or 0;
int = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "int") or 0;
defense = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "defense") or 0;
physicalDefense = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "defense") or 0;
magicalDefense = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "defense") or 0;
}
end
end
abilityDamageMulti = 1
-- note: this stuff does literally nothing with the dmg changes
local baseDamage = calculatePlayerDamage(player, abilityDamageType, targetLevel, isServerHitboxPlayer)
local abilityDamage = math.ceil(baseDamage * abilityDamageMulti)
local damageCategory = "direct"
local firstHitDataForSourceTag
--[[local WEIRD_BLOCK do
for i, v in pairs(abilityDamageGUIDData.previousEntityHits) do
if abilityBaseData.securityData and abilityBaseData.securityData.isDamageContained then
-- use any first hit
firstHitDataForSourceTag = v.hitData[1]
end
if v.entityManifest == serverHitbox then
for _, hitData in pairs(v.hitData) do
if hitData.sourceTag == sourceTag then
hitsDoneToEntityManifestBySourceTag = hitsDoneToEntityManifestBySourceTag + 1
end
end
break
end
end
end]]
-- todo: turn this into something more elegant.
if configuration.getConfigurationValue("doUseAbilitySecurityData", player) then
if abilityBaseData.securityData then
local adjustServerHitboxPosition = detection.projection_Box(serverHitbox.CFrame, serverHitbox.Size, player.Character.PrimaryPart.Position)
if abilityBaseData.securityData.maxHitLockout then
local sumHits = 0 do
for i, v in pairs(abilityDamageGUIDData.previousEntityHits) do
sumHits = sumHits + #v.hitData
end
end
if sumHits >= abilityBaseData.securityData.maxHitLockout then
warn(player, abilityBaseData.name, "hit maxHitLockout")
return false
end
end
if abilityBaseData.projectileSpeed and abilityBaseData.securityData.projectileOrigin then
local posDiff = 0 do
if abilityBaseData.securityData.projectileOrigin == "character" then
posDiff = (adjustServerHitboxPosition - player.Character.PrimaryPart.Position).magnitude
end
end
if posDiff > 3 * abilityBaseData.projectileSpeed * (tick() - abilityDamageGUIDData.timestamp) then
warn(player, abilityBaseData.name, "projectile hit way too fast")
return false
end
end
if (abilityBaseData.securityData.playerHitMaxPerTag or math.huge) <= hitsDoneToEntityManifestBySourceTag then
warn(player, abilityBaseData.name, "hit same entity too many times")
return false
end
if abilityBaseData.securityData.isDamageContained and firstHitDataForSourceTag then
local posDiff = (adjustServerHitboxPosition - firstHitDataForSourceTag.hitPosition).magnitude
local containRadius = abilityStatistics["blast radius"] or abilityStatistics["range"] or abilityStatistics["radius"] or abilityStatistics["distance"] or 20
if posDiff > (containRadius) * 3 then
warn(player, abilityBaseData.name, "hit entity outside of containDamage")
return false
end
end
end
end
--[[for statsName, statsValue in pairs(abilityStatistics) do
local scalingStat = string.match(statsName, "(%w+)_scaling")
if scalingStat and playerData.nonSerializeData.statistics_final[scalingStat] then
abilityDamage = abilityDamage + playerData.nonSerializeData.statistics_final[scalingStat] * statsValue
elseif statsName == "enemy_missing_health" then
abilityDamage = abilityDamage + (maxHealth - health) * statsValue
elseif statsName == "enemy_current_health" then
abilityDamage = abilityDamage + health * statsValue
elseif statsName == "enemy_max_health" then
abilityDamage = abilityDamage + maxHealth * statsValue
elseif statsName == "user_missing_health" then
warn(statsName .. " not yet implemented.")
elseif statsName == "user_current_health" then
warn(statsName .. " not yet implemented.")
elseif statsName == "user_max_health" then
warn(statsName .. " not yet implemented.")
end
end]]
damageData = {}
damageData.damage = abilityDamage
damageData.sourceType = sourceType
damageData.sourceId = sourceId
damageData.damageType = abilityDamageType
damageData.isDamageDirect = false
damageData.sourcePlayerId = player.userId
damageData.damageTime = os.time()
damageData.category = damageCategory
damageData.sourceEntityGUID = utilities.getEntityGUIDByEntityManifest(player.Character.PrimaryPart)
end
elseif sourceType == "equipment" then
local equipmentData = network:invoke("getPlayerEquipmentDataByEquipmentPosition", player, mapping.equipmentPosition.weapon)
if equipmentData then
local weaponBaseData = itemLookup[equipmentData.id]
local weaponManfiestFolder = itemFolderLookup[weaponBaseData.module.Name]
if weaponBaseData and weaponBaseData.module then
local DAMAGE_TYPE_OVERRIDE
local passesSecurityCheck, securityData = false, {} do
if weaponBaseData.equipmentType == "bow" then
if arrowsShotByPlayers[player] and #arrowsShotByPlayers[player] > 0 then
local playerPositionAtDamageRequestTime = player.Character.PrimaryPart.Position
local targetPositionAtDamageRequestTime = serverHitbox.Position
for i, arrowData in pairs(arrowsShotByPlayers[player]) do
if isArrowDataUsableForDamageRequest(arrowData, playerPositionAtDamageRequestTime, targetPositionAtDamageRequestTime) then
passesSecurityCheck = true
securityData = arrowData
if not arrowData.canAOE then
if arrowData.piercesRemaining <= 0 then
table.remove(arrowsShotByPlayers[player], i)
else
arrowData.piercesRemaining = arrowData.piercesRemaining - 1
end
else
DAMAGE_TYPE_OVERRIDE = "magical"
end
if (tick() - arrowData.timestamp) > 5 then -- lived too long
table.remove(arrowsShotByPlayers[player], i)
end
break
end
end
end
elseif weaponBaseData.equipmentType == "staff" and sourceTag == "magic-ball" then
if magicBallsByPlayer[player] and #magicBallsByPlayer[player] > 0 then
local playerPositionAtDamageRequestTime = player.Character.PrimaryPart.Position
local targetPositionAtDamageRequestTime = serverHitbox.Position
for i, magicBallData in pairs(magicBallsByPlayer[player]) do
if isMagicBallDataUsableForDamageRequest(magicBallData, playerPositionAtDamageRequestTime, targetPositionAtDamageRequestTime) then
passesSecurityCheck = true
securityData = magicBallData
table.remove(magicBallsByPlayer[player], i)
break
end
end
end
else
local manifest = weaponManfiestFolder:FindFirstChild("manifest")
if not manifest and weaponManfiestFolder:FindFirstChild("container") then
local container = weaponManfiestFolder.container:FindFirstChild("RightHand") or weaponManfiestFolder.container:FindFirstChild("LeftHand")
if container then
manifest = container:FindFirstChild("manifest") or container.PrimaryPart
end
end
if manifest then
local diameter = math.max(manifest.Size.X, manifest.Size.Y, manifest.Size.Z) + 6 -- add 6 to pad for laggy players
local adjusted_position = detection.projection_Box(serverHitbox.CFrame, serverHitbox.Size, player.Character.PrimaryPart.Position)
local distFromMonster = (adjusted_position - player.Character.PrimaryPart.Position).magnitude
if distFromMonster <= diameter * (1.75 + playerData.nonSerializeData.statistics_final.attackRangeIncrease) and isPlayerEquipmentDamageRequestWithinAnimationDamageSequence(player, serverHitbox) then
passesSecurityCheck = true
end
end
end
end
if passesSecurityCheck then
local EQUIPMENT_DAMAGE_TYPE = DAMAGE_TYPE_OVERRIDE or "physical"
local targetLevel, targetDefense, targetPlayer, targetPlayerData = 0, 0, nil do
if isServerHitboxPlayer then
-- temporarily no difference when attacking players!
targetPlayer = game.Players:GetPlayerFromCharacter(serverHitbox.Parent)
if targetPlayer then
targetPlayerData = network:invoke("getPlayerData", targetPlayer)
if targetPlayerData then
targetDefense = targetPlayerData.nonSerializeData.statistics_final["defense"]
defenderStats = targetPlayerData.nonSerializeData.statistics_final
defenderStats.level = targetPlayerData.level
end
targetLevel = targetPlayer.level.Value
end
-- targetLevel = playerData.level
else
targetLevel = serverHitbox.level.Value
defenderStats = {
level = serverHitbox.level.Value;
dex = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "dex") or 0;
vit = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "vit") or 0;
str = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "str") or 0;
int = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "int") or 0;
defense = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "defense") or 0;
physicalDefense = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "defense") or 0;
magicalDefense = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "defense") or 0;
}
end
end
local levelDifference = playerData.level - targetLevel
local stats = playerData.nonSerializeData.statistics_final
attackerStats = stats
attackerStats.level = playerData.level
damageData = {}
damageData.damage = 0 -- gets overwritten later
damageData.damageType = sourceTag == "magic-ball" and "magical" or EQUIPMENT_DAMAGE_TYPE
damageData.sourceType = sourceType
damageData.sourceId = sourceId
damageData.sourcePlayerId = player.userId
damageData.damageTime = os.time()
damageData.category = weaponBaseData.equipmentType == "bow" and "projectile" or "direct"
damageData.sourceEntityGUID = utilities.getEntityGUIDByEntityManifest(player.Character.PrimaryPart)
damageData.equipmentType = weaponBaseData.equipmentType
end
end
end
end
elseif sourceType == "monster" then
if player.Character.PrimaryPart.health.Value > 0 then
local stats = playerData.nonSerializeData.statistics_final
defenderStats = stats
defenderStats.level = playerData.level
local damage = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "damage")
attackerStats = {
level = serverHitbox.level.Value;
dex = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "dex") or 0;
vit = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "vit") or 0;
str = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "str") or 0;
int = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "int") or 0;
defense = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "defense") or 0;
damage = network:invoke("getMonsterDataByMonsterManifest_server", serverHitbox, "damage") or 0;
damageMulti = 1;
magicalDamage = nil;--monsterDamage;
physicalDamage = nil;--monsterDamage;
}
damageData = {}
damageData.damage = attackerStats.damage
damageData.sourceType = "monster"
damageData.sourceId = sourceId
damageData.damageType = nil;--damageType
damageData.category = nil;--damageCategory
damageData.sourceEntityGUID = utilities.getEntityGUIDByEntityManifest(serverHitbox)
--[[
damageData.damage = damageToDeal
damageData.sourceType = sourceType
damageData.sourceId = sourceId
damageData.sourcePlayerId = player.userId
damageData.damageTime = os.time()
--]]
end
end
-- damageData, attackerStats, defenderStats
if damageData then
local ATK = attackerStats.damage--(damageData.damageType == "magical" and attackerStats.magicalDamage) or attackerStats.physicalDamage
local DEF = defenderStats.defense--(damageData.damageType == "magical" and defenderStats.magicalDefense) or defenderStats.physicalDefense
local coeffecient = 1
--[[
if isPlayerAttacker then
local level = playerData.level or 0
coeffecient = coeffecient * (1 + .02*level)
end
]]
-- local levelBonusDamageMulti = 1 --(1 + .02*attackerStats.level)
-- coeffecient = coeffecient * levelBonusDamageMulti
-- local levelDifference = (attackerStats.level or 0) - (defenderStats.level or 0)
-- local levelMulti = math.clamp((10 + (levelDifference/2)) / 10,0.2,2)
-- coeffecient = coeffecient * levelMulti
if attackerStats.damageMulti then
coeffecient = coeffecient * attackerStats.damageMulti
end
-- damageData.damage = coeffecient * ATK*(1/(1+2.718281828459^(-((ATK-DEF)/ (0.1*DEF) )) ))
damageData.damage = math.max(0, math.floor(coeffecient * (ATK - DEF)))
-- damageData.damage = coeffecient * (ATK ^ 2) / (ATK + DEF)
-- damageData.damage = coeffecient * ((1.87*ATK)^4)/((1.87*ATK+DEF)^3)
-- stuff for monsters idk --
if sourceType == "monster" then
local monsterDamage, damageType, damageCategory do
if monsterLookup[serverHitbox.Name] then
local statesData = monsterLookup[serverHitbox.Name].statesData
if statesData.processDamageRequest then
monsterDamage, damageType, damageCategory = statesData.processDamageRequest(sourceId, damageData.damage)
end
end
end
if monsterDamage then
damageData.damage = monsterDamage
damageData.damageType = damageType;--damageType
damageData.category = damageCategory;--damageCategory
else
damageData.damageType = "physical";--damageType
damageData.category = "direct";--damageCategory
end
elseif sourceType == "ability" then
-- if this causes problems add abilityExecutionData after playerData
local abilityBaseData = abilityLookup[sourceId]
if abilityBaseData._serverProcessDamageRequest then
local abilityDamage, abilityDamageType, abilityDamageCategory = abilityBaseData._serverProcessDamageRequest(sourceTag, damageData.damage, serverHitbox, hitsDoneToEntityManifestBySourceTag, player)
if not abilityDamage then
warn("FAILED TO PASS VALID SOURCE-TAG")
return false
else
damageData.damage = abilityDamage;
damageData.damageType = abilityDamageType;
damageData.category = abilityDamageCategory;
end
end
end
damageData.position = damagePosition
-- ranged damage bonus?
if sourceType == "equipment" then
local equipmentData = network:invoke("getPlayerEquipmentDataByEquipmentPosition", player, mapping.equipmentPosition.weapon)
if equipmentData then
local weaponBaseData = itemLookup[equipmentData.id]
if weaponBaseData and weaponBaseData.equipmentType == "bow" then
damageData.damage = damageData.damage + (attackerStats.rangedDamage or 0)
end
end
end
-- CRIT!
if attackerStats.criticalStrikeChance and attackerStats.criticalStrikeChance > 0 and attackerStats.criticalStrikeChance >= critChanceRandom:NextNumber() then
damageData.damage = damageData.damage * CRIT_DAMAGE_MULTIPLIER
damageData.isCritical = true
end
-- ABILITY DMG
if abilityDamageMulti then
damageData.damage = damageData.damage * abilityDamageMulti
end
-- BLOCKED!
if damageData.damageType ~= "magical" then
if defenderStats.blockChance and dodgeChanceRandom:NextNumber() <= defenderStats.blockChance then
damageData.damage = damageData.damage * 0.25
damageData.supressed = true
network:fireAllClients("replicatePlayerAnimationSequence", player, "emoteAnimations", "block")
end
end
-- PERKS
if sourceType == "ability" or sourceType == "equipment" then
if serverHitbox.health.Value == serverHitbox.maxHealth.Value then
elseif serverHitbox.health.Value / serverHitbox.maxHealth.Value <= 0.3 then
end
if sourceType == "equipment" and playerDamageAnimationState[player] and playerDamageAnimationState[player].state == "strike2" then
elseif sourceType == "equipment" and playerDamageAnimationState[player] and playerDamageAnimationState[player].state == "strike3" then
local hasTripleSlash = false
local playerData = network:invoke("getPlayerData", player)
if playerData and playerData.abilities then
for _, ability in pairs(playerData.abilities) do
if ability.id == 3 and ability.variant == "tripleSlash" then
hasTripleSlash = true
end
end
end
if hasTripleSlash then
if network:invoke("getIsPlayerOfClass_server", player, "berserker") then
damageData.damage = damageData.damage * 2
end
damageData.damage = damageData.damage * 1.4
end
end
end
-- PARRY
if damageData.category then
local targetPlayer = sourceType == "monster" and player or game.Players:GetPlayerFromCharacter(serverHitbox.Parent)
if targetPlayer then
local successAAED, activeAbilityExecutionData = utilities.safeJSONDecode(targetPlayer.Character.PrimaryPart.activeAbilityExecutionData.Value)
if successAAED then
if activeAbilityExecutionData.id == 8 then
if damageData.category == "direct" or damageData.category == "projectile" then
damageData.damage = damageData.damage * 0.5
damageData.supressed = true
end
elseif activeAbilityExecutionData.id == 17 then
if damageData.category == "direct" or damageData.category == "projectile" then
damageData.damage = damageData.damage * 0.35
damageData.supressed = true
network:fireAllClients("signal_damageModificationByActiveAbility",
-- player that is using the ability that modified
targetPlayer,
activeAbilityExecutionData.id,
damageData,
-- thing damaging you
player.Character.PrimaryPart
)
end
end
end
end
end
-- working with this is actually giving me so much anxiety
local attackerData = isPlayerAttacker and playerData
local sourceManifest = isPlayerAttacker and player.Character.PrimaryPart or serverHitbox
local targetManifest = sourceType == "monster" and player.Character.PrimaryPart or serverHitbox
local targetPlayer = sourceType == "monster" and player or game.Players:GetPlayerFromCharacter(serverHitbox.Parent)
local targetPlayerData = targetPlayer and network:invoke("getPlayerData", targetPlayer)
-- go through attacker perks (if applicable)
local attackerPerks = attackerData and attackerData.nonSerializeData.statistics_final.activePerks or {}
for perkName, active in pairs(attackerPerks) do
if active then
local perkData = perkLookup[perkName]
if perkData.onDamageGiven then
perkData.onDamageGiven(sourceManifest, sourceType, sourceId, targetManifest, damageData)
end
if damageData.isCritical then
if perkData.onCritGiven then
perkData.onCritGiven(sourceManifest, sourceType, sourceId, targetManifest, damageData)
end
end
end
end
if attackerData and attackerData.nonSerializeData.statistics_final.damageGivenMulti then
damageData.damage = damageData.damage * attackerData.nonSerializeData.statistics_final.damageGivenMulti
end
-- go through defender perks (if applicable)
local defenderPerks = targetPlayerData and targetPlayerData.nonSerializeData.statistics_final.activePerks or {}
for perkName, active in pairs(defenderPerks) do
if active then
local perkData = perkLookup[perkName]
if perkData.onDamageTaken then
perkData.onDamageTaken(sourceManifest, sourceType, sourceId, targetManifest, damageData)
end
end
end
if targetPlayerData and targetPlayerData.nonSerializeData.statistics_final.damageTakenMulti then
damageData.damage = damageData.damage * targetPlayerData.nonSerializeData.statistics_final.damageTakenMulti
end
-- INT BOOST
if damageData.damageType == "magical" then
damageData.damage = damageData.damage * (1 + (attackerStats.int * 1/160))
-- STR BOOST
else
damageData.damage = damageData.damage * (1 + (attackerStats.str * 1/160))
end
-- nerf rangers
if guid and (damageData.equipmentType == "bow") then
local damageLossPerMultiHit = 0.5
if not arrowMultiHitCache[guid] then
arrowMultiHitCache[guid] = {}
-- clear this cache to save memory
delay(5, function()
arrowMultiHitCache[guid] = nil
end)
end
if not arrowMultiHitCache[guid][targetManifest] then
arrowMultiHitCache[guid][targetManifest] = 0
end
arrowMultiHitCache[guid][targetManifest] = arrowMultiHitCache[guid][targetManifest] + 1
local loss = damageLossPerMultiHit ^ (arrowMultiHitCache[guid][targetManifest] - 1)
damageData.damage = damageData.damage * loss
end
-- ranger's stance damage implementation
if (damageData.equipmentType == "bow") and sourceTag == "ranger stance" then
if not player.Character then return end
local manifest = player.Character.PrimaryPart
if not manifest then return end
local guid = utilities.getEntityGUIDByEntityManifest(manifest)
if not guid then return end
local statuses = network:invoke("getStatusEffectsOnEntityManifestByEntityGUID", guid)
local rangerStanceStatus = nil
for _, status in pairs(statuses) do
if status.statusEffectType == "ranger stance" then
rangerStanceStatus = status
break
end
end
if rangerStanceStatus then
damageData.damage = damageData.damage * rangerStanceStatus.statusEffectModifier.damageBonus
end
end
-- fire out on the event system to let other things modify the damage at will
damageData.target = serverHitbox
events:fireEventLocal("playerWillDealDamage", player, damageData)
local successfullyDealtDamage do
if sourceType == "monster" then
-- monster is damaging player
successfullyDealtDamage = network:invoke("playerDamageRequest_server", nil, player.Character.PrimaryPart, damageData)
else
if isServerHitboxPlayer then
damageData.damage = damageData.damage * configuration.getConfigurationValue("abilityPVPDampening")
-- player is damaging player
successfullyDealtDamage = network:invoke("playerDamageRequest_server", player, serverHitbox, damageData)
else
-- player is damaging monster
successfullyDealtDamage = network:invoke("monsterDamageRequest_server", player, serverHitbox, damageData)
end
end
end
if successfullyDealtDamage then
if sourceType == "ability" then
network:fire("abilityDealtDamageToEntity", player, sourceId, guid, serverHitbox, sourceTag)
end
-- VIT LEACH ON BOWS
if sourceType == "equipment" then
local equipmentData = network:invoke("getPlayerEquipmentDataByEquipmentPosition", player, mapping.equipmentPosition.weapon)
if equipmentData then
local weaponBaseData = itemLookup[equipmentData.id]
if weaponBaseData and weaponBaseData.equipmentType == "bow" then
local healAmt = 0
if stats.vit >= 30 then
healAmt = 25
end
if stats.vit >= 70 then
healAmt = 40
end
if stats.vit >= 120 then
healAmt = 100
end
-- 10% proc chance
if math.random() < 1/10 and healAmt ~= 0 then
local character = player.Character
if not character then return end
local manifest = character.PrimaryPart
if not manifest then return end
local health = manifest:FindFirstChild("health")
if not health then return end
local maxHealth = manifest:FindFirstChild("maxHealth")
if not maxHealth then return end
health.Value = math.min(health.Value + healAmt, maxHealth.Value)
network:fireAllClients("effects_requestEffect", "bloodHeal", {
player = player,
playerManifest = manifest,
target = serverHitbox,
})
end
end
if playerData.class == "Warrior" or playerData.class == "Knight" or playerData.class == "Paladin" or playerData.class == "Berserker" then
local healAmt = 0
local manaAmt = 0
local stamAmt = 0
if stats.vit >= 30 then
healAmt = 2
end
if stats.vit >= 70 then
healAmt = 4
end
if stats.vit >= 120 then
healAmt = 6
end
if stats.int >= 30 then
manaAmt = 1
end
if stats.int >= 70 then
manaAmt = 2
end
if stats.int >= 150 then
manaAmt = 3
end
if stats.dex >= 50 then
stamAmt = 0.5
end
if stats.dex >= 120 then
stamAmt = 1
end
local character = player.Character
if not character then return end
local manifest = character.PrimaryPart
if not manifest then return end
local health = manifest:FindFirstChild("health")
if not health then return end
local maxHealth = manifest:FindFirstChild("maxHealth")
if not maxHealth then return end
local mana = manifest:FindFirstChild("mana")
if not mana then return end
local maxMana = manifest:FindFirstChild("maxMana")
if not maxMana then return end
local stamina = manifest:FindFirstChild("stamina")
if not stamina then return end
local maxStamina = manifest:FindFirstChild("maxStamina")
if not maxStamina then return end
health.Value = math.min(health.Value + healAmt, maxHealth.Value)
mana.Value = math.min(mana.Value + manaAmt, maxMana.Value)
stamina.Value = math.min(stamina.Value + stamAmt, maxStamina.Value)
end
end
end
playerData.nonSerializeData.setNonSerializeDataValue("lastTimeInCombat", tick())
end
return successfullyDealtDamage or false
end
end
end
local function onReportMonsterInDamageState_server(monsterName, manifest, player)
local characterPosition
end
local function isDamageAnimationSequence(animationSequence)
for i, weaponType in pairs(WEAPON_TYPES_TO_SCAN) do
if animationSequence == weaponType .. "Animations" then
return true
end
end
return false, nil
end
local function isPlayerDamageAnimationStateTransitionValid(player, weaponType, animationSequence, animationName)
local currentPlayerDamageAnimationState = playerDamageAnimationState[player]
local playerData = network:invoke("getPlayerData", player)
if currentPlayerDamageAnimationState then
if weaponValidationData[weaponType] then
if animationName == "strike1" or animationName == "strike2" then
if tick() - currentPlayerDamageAnimationState.timestamp >= weaponValidationData[weaponType][animationName .. "_animationLength"] / (1 + playerData.nonSerializeData.statistics_final.attackSpeed) then
return true
end
end
end
if animationName == "strike1" then
if currentPlayerDamageAnimationState.state == "strike2" then
-- compare: minTimeForStrike2ToGetToStrike1
if weaponValidationData[weaponType] then
local success = isWithinBounds(
tick() - currentPlayerDamageAnimationState.timestamp,
weaponValidationData[weaponType].strike1_slash2PeriodStart_time - DAMAGE_ANIMATION_REQUEST_MISMATCH_LATENCY_FORGIVENESS,
weaponValidationData[weaponType].strike1_slash2PeriodStart_time + SLASH_CHAIN_WINDOW + DAMAGE_ANIMATION_REQUEST_MISMATCH_LATENCY_FORGIVENESS,
1 + playerData.nonSerializeData.statistics_final.attackSpeed
)
return success
end
return true
elseif currentPlayerDamageAnimationState.state == "strike3" then
if weaponValidationData[weaponType] then
local success = isWithinBounds(
tick() - currentPlayerDamageAnimationState.timestamp,
weaponValidationData[weaponType].strike3_stopDamageSequence_time - DAMAGE_ANIMATION_REQUEST_MISMATCH_LATENCY_FORGIVENESS,
weaponValidationData[weaponType].strike3_stopDamageSequence_time + SLASH_CHAIN_WINDOW + DAMAGE_ANIMATION_REQUEST_MISMATCH_LATENCY_FORGIVENESS,
1 + playerData.nonSerializeData.statistics_final.attackSpeed
)
return success
end
return true
end
elseif animationName == "strike2" then
if currentPlayerDamageAnimationState.state == "strike1" then
-- compare: minTimeForStrike1ToGetToStrike2
if weaponValidationData[weaponType] then
local success = isWithinBounds(
tick() - currentPlayerDamageAnimationState.timestamp,
weaponValidationData[weaponType].strike2_slash1PeriodStart_time - DAMAGE_ANIMATION_REQUEST_MISMATCH_LATENCY_FORGIVENESS,
weaponValidationData[weaponType].strike2_slash1PeriodStart_time + SLASH_CHAIN_WINDOW + DAMAGE_ANIMATION_REQUEST_MISMATCH_LATENCY_FORGIVENESS,
1 + playerData.nonSerializeData.statistics_final.attackSpeed
)
return success
end
return true
elseif currentPlayerDamageAnimationState.state == "strike2" then
-- -- compare: minTimeForStrike2ToFinish
-- if weaponValidationData[weaponType] then
-- return tick() - currentPlayerDamageAnimationState.timestamp >= weaponValidationData[weaponType].minTimeForStrike2ToFinish
-- end
--
-- return true
return false
end
elseif animationName == "strike3" then
if currentPlayerDamageAnimationState.state == "strike2" then
return true
end
end
else
return true
end
return false
end
local function playerHasArrowToShoot(player)
local playerData = network:invoke("getPlayerData", player)
local equipmentData do
for i, equipmentSlotData in pairs(playerData.equipment) do
if equipmentSlotData.position == mapping.equipmentPosition.arrow then
equipmentData = equipmentSlotData
end
end
end
if equipmentData then
for i, inventorySlotData in pairs(playerData.inventory) do
if inventorySlotData.id == equipmentData.id and inventorySlotData.stacks >= 1 then
return true, equipmentData.id
end
end
else
return false, "arrow not primed"
end
end
local function getChargeTimeMultiplierFromChargeTime(chargeTime)
chargeTime = chargeTime < 0 and 0 or chargeTime
local m, b
if chargeTime < configuration.getConfigurationValue("maxBowChargeTime") then
local chargeTime_a = configuration.getConfigurationValue("maxBowChargeTime")
local multiplier_a = 1
local chargeTime_b = 0
local multiplier_b = 0
m = (multiplier_b - multiplier_a) / (chargeTime_b - chargeTime_a)
b = multiplier_a - m * chargeTime_a
else
local chargeTime_a = configuration.getConfigurationValue("maxBowChargeTime")
local multiplier_a = 1
local chargeTime_b = configuration.getConfigurationValue("bowPullBackTime")
local multiplier_b = configuration.getConfigurationValue("bowMaxChargeDamageMultiplier")
m = (multiplier_b - multiplier_a) / (chargeTime_b - chargeTime_a)
b = multiplier_a - m * chargeTime_a
end
local multiplier = math.clamp(chargeTime * m + b, 0.1, configuration.getConfigurationValue("bowMaxChargeDamageMultiplier"))
return multiplier
end
local playerBowAnimationStateData = {}
local function onPlayerAnimationReplicated(player, animationSequence, animationName, executionData)
if not player or not player.Character or not player.Character.PrimaryPart then return false end
local isValid = isDamageAnimationSequence(animationSequence)
if isValid or animationSequence == "bowAnimations" then
local equipmentData = network:invoke("getPlayerEquipmentDataByEquipmentPosition", player, mapping.equipmentPosition.weapon)
if equipmentData then
local itemBaseData = itemLookup[equipmentData.id]
if itemBaseData.category == "equipment" then
local weaponType = itemBaseData.equipmentType
if weaponType == "bow" then
local success, info = playerHasArrowToShoot(player)
if success then
if animationName == "stretching_bow" then
playerBowAnimationStateData[player] = {
weaponType = weaponType;
state = animationName;
timestamp = tick();
damageBlacklist = {}
};
elseif animationName == "firing_bow_stance" then
local success = network:invoke("tradeItemsBetweenPlayerAndNPC", player, {{id = 87; stacks = 1}}, 0, {}, 0, nil)
if success then
if not arrowsShotByPlayers[player] then
arrowsShotByPlayers[player] = {}
end
table.insert(arrowsShotByPlayers[player], {
serverCharacterPosition = player.Character.PrimaryPart.Position;
executionData = executionData;
timestamp = tick();
chargeTimeMultiplier = 1;
piercesRemaining = 999;
canAOE = false;
sourceWeaponBaseData = itemBaseData;
})
end
elseif animationName == "firing_bow" then
if not executionData.canceled then
if playerBowAnimationStateData[player] and playerBowAnimationStateData[player].state == "stretching_bow" then
local playerData = network:invoke("getPlayerData", player)
local stats = playerData.nonSerializeData.statistics_final
local isMagical = stats.int >= 30
local attackSpeedScalar = 1 + playerData.nonSerializeData.statistics_final.attackSpeed
local timeElapsed = (tick() - playerBowAnimationStateData[player].timestamp) * attackSpeedScalar
local minBowChargeTime = configuration.getConfigurationValue("minBowChargeTime")
local maxBowChargeTime = configuration.getConfigurationValue("maxBowChargeTime")
if timeElapsed >= minBowChargeTime --[[change this to real anim time]] then
local playerCurrentPosition = player.Character.PrimaryPart.Position
playerBowAnimationStateData[player] = nil
-- let other parts of the system make sure we need arrows, eh?
local arrowData = {
needsArrow = true
}
events:fireEventLocal("playerWillUseArrow", player, arrowData)
local maxNumArrows = utilities.calculateNumArrowsFromDex(stats.dex)
local inventory = (network:invoke("getPlayerData", player) or {}).inventory or {}
local arrowsOwned = 0
for i, inventorySlotData in pairs(inventory) do
if inventorySlotData.id == 87 then
arrowsOwned = inventorySlotData.stacks
end
end
local numArrows = math.min(arrowsOwned, maxNumArrows)
local success, info = playerHasArrowToShoot(player)
if success and arrowData.needsArrow then
success = network:invoke("tradeItemsBetweenPlayerAndNPC", player, {{id = info; stacks = numArrows}}, 0, {}, 0, nil)
end
if success then
if not arrowsShotByPlayers[player] then
arrowsShotByPlayers[player] = {}
end
for i = 1, numArrows do
table.insert(arrowsShotByPlayers[player], {
serverCharacterPosition = playerCurrentPosition;
executionData = executionData;
timestamp = tick();
chargeTimeMultiplier = getChargeTimeMultiplierFromChargeTime(timeElapsed); --math.clamp(timeElapsed / maxBowChargeTime, 0.1, configuration.getConfigurationValue("bowMaxChargeDamageMultiplier"));
piercesRemaining = utilities.calculatePierceFromStr(stats.str);
canAOE = isMagical;
sourceWeaponBaseData = itemBaseData;
arrowId = info;
damageMultiplier = math.clamp(1 - ((numArrows-1) * 0.15), 0.10, 1)
})
end
else
warn("failed to take arrow(s) from player!")
end
-- playerDamageAnimationState[player] = {
-- weaponType = weaponType;
-- state = animationName;
-- timestamp = tick();
-- serverCharacterPosition = playerCurrentPosition;
-- executionData = executionData;
-- damageBlacklist = {}
-- };
end
end
else
playerBowAnimationStateData[player] = nil
end
else
playerBowAnimationStateData[player] = nil
end
else
playerBowAnimationStateData[player] = nil
end
else
if not playerDamageAnimationState or isPlayerDamageAnimationStateTransitionValid(player, weaponType, animationSequence, animationName) then
playerDamageAnimationState[player] = {
weaponType = weaponType;
state = animationName;
timestamp = tick();
damageBlacklist = {}
}
end
end
end
end
end
end
local function onPlayerRemoving(player)
playerAbilityHitData = nil
playerBowAnimationStateData[player] = nil
playerDamageAnimationState[player] = nil
arrowsShotByPlayers[player] = nil
magicBallsByPlayer[player] = nil
-- clear data from arrowsShotByPlayers
for i, arrowCollectionData in pairs(arrowsShotByPlayers) do
for ii = #arrowCollectionData, 1, -1 do
if tick() - arrowCollectionData[ii].timestamp >= ARROW_DATA_MAX_LIFETIME then
table.remove(arrowCollectionData, ii)
end
end
end
-- clear data from magicBallsByPlayer
for i, magicBallCollectionData in pairs(magicBallsByPlayer) do
for ii = #magicBallCollectionData, 1, -1 do
if tick() - magicBallCollectionData[ii].timestamp >= ARROW_DATA_MAX_LIFETIME then
table.remove(magicBallCollectionData, ii)
end
end
end
end
function module.init(Modules)
network = Modules.network
placeSetup = Modules.placeSetup
utilities = Modules.utilities
mapping = Modules.mapping
levels = Modules.levels
damage = Modules.damage
detection = Modules.detection
configuration = Modules.configuration
projectile = Modules.projectile
events = Modules.events
entityManifestCollectionFolder = placeSetup.getPlaceFolder("entityManifestCollection")
network:create("entityKillingBlow", "BindableEvent", "Event", processEntityKillingBlow)
network:create("playerKilledByPlayer", "BindableEvent")
network:create("signal_playerKilledByPlayer", "RemoteEvent")
network:create("signal_damageModificationByActiveAbility", "RemoteEvent")
network:create("playerRequest_damageEntity", "RemoteEvent", "OnServerEvent", playerRequest_damageEntity)
network:create("playerRequest_damageEntity_server", "BindableEvent", "Event", playerRequest_damageEntity)
network:create("playerDamageRequest_server", "BindableFunction", "OnInvoke", playerDamageRequest_server)
network:create("reportMonsterInDamageState_server", "BindableEvent", "Event", onReportMonsterInDamageState_server)
-- if game.PlaceId == 2061558182 then
spawn(function()
-- listen to player animation replication
network:connect("playerAnimationReplicated", "Event", onPlayerAnimationReplicated)
game.Players.PlayerRemoving:connect(onPlayerRemoving)
-- process player animations here :smile:
local animationsLocation = game.StarterPlayer.StarterPlayerScripts.assets.animations
local playerBaseCharacter = replicatedStorage.playerBaseCharacter:Clone()
playerBaseCharacter.Parent = workspace
for _, obj in pairs(playerBaseCharacter:GetChildren()) do
if obj.Name == "HumanoidRootPart" then
obj.Anchored = true
obj.CanCollide = true
obj.Transparency = 0.75
elseif obj:IsA("BasePart") then
obj.Anchored = false
obj.CanCollide = true
obj.Transparency = 0.75
end
end
-- playerBaseCharacter:SetPrimaryPartCFrame(CFrame.new(0, 100, 25))
-- generic melee weapons
for i, weaponType in pairs(WEAPON_TYPES_TO_SCAN) do
local weaponAnimationModule = animationsLocation[weaponType .. "Animations"]
local weaponAnimationData = require(weaponAnimationModule)
local validationTable = {}
-- startDamageSequence stopDamageSequence
for name, animationData in pairs(weaponAnimationData) do
local animation = Instance.new("Animation")
animation.AnimationId = animationData.animationId
local animationTrack = playerBaseCharacter.AnimationController:LoadAnimation(animation)
animationTrack:Play()
while animationTrack.Length == 0 do wait(0.1) end
if name == "strike1" then
validationTable.strike1_animationLength = animationTrack.Length
validationTable.strike1_startDamageSequence_time = animationTrack:GetTimeOfKeyframe("startDamageSequence")
validationTable.strike1_stopDamageSequence_time = animationTrack:GetTimeOfKeyframe("stopDamageSequence")
validationTable.strike1_slash2PeriodStart_time = animationTrack:GetTimeOfKeyframe("slash2PeriodStart")
elseif name == "strike2" then
validationTable.strike2_animationLength = animationTrack.Length
validationTable.strike2_startDamageSequence_time = animationTrack:GetTimeOfKeyframe("startDamageSequence")
validationTable.strike2_stopDamageSequence_time = animationTrack:GetTimeOfKeyframe("stopDamageSequence")
validationTable.strike2_slash1PeriodStart_time = animationTrack:GetTimeOfKeyframe("slash1PeriodStart")
elseif name == "strike3" then
validationTable.strike3_animationLength = animationTrack.Length
validationTable.strike3_startDamageSequence_time = animationTrack:GetTimeOfKeyframe("startDamageSequence")
validationTable.strike3_stopDamageSequence_time = animationTrack:GetTimeOfKeyframe("stopDamageSequence")
end
end
weaponValidationData[weaponType] = validationTable
end
-- process bow animation data here, bows are a bit unique
do
-- we're using the actual bow's streching animation and firing animation
-- because this lets us know exactly how long the minimum amount of time
-- must elapse (time of stretching anim + time of firing anim = minimum time)
-- we can offset this later by some multiple to make the speed faster!
local bowToolAnimationsModule = animationsLocation.bowToolAnimations_noChar
local bowToolAnimations = require(bowToolAnimationsModule)
end
-- process monster animations here :smile:
for monsterName, monsterData in pairs(monsterLookup) do
local monsterModel = monsterData.entity:Clone()
for _, obj in pairs(monsterModel:GetChildren()) do
if obj.Name == "HumanoidRootPart" then
obj.Anchored = true
obj.CanCollide = true
obj.Transparency = 0.75
elseif obj:IsA("BasePart") then
obj.Anchored = false
obj.CanCollide = true
obj.Transparency = 0.75
end
end
monsterModel.Parent = workspace
monsterModel:SetPrimaryPartCFrame(CFrame.new(0, 100, 0))
-- open animations folder
monsterDamageValidationData[monsterName] = {}
-- process animations
if monsterModel:FindFirstChild("animations") then
local animations = monsterModel.animations
local statesScript = monsterData.module:FindFirstChild("states") and require(monsterData.module:FindFirstChild("states"))
if statesScript == nil or not statesScript.states then
statesScript = require(replicatedStorage.defaultMonsterState)
end
for stateName, stateData in pairs(statesScript.states) do
if stateData.animationTriggersDamage then
local animationName = stateData.animationEquivalent or stateName
if animations:FindFirstChild(animationName) then
local animationTrack = monsterModel.AnimationController:LoadAnimation(monsterModel.animations[animationName])
local recordingStartTime = animationTrack.Length * 0.3
local recordingFinishTime = animationTrack.Length * 0.7
local connections = {}
local fetched = false
local animationStartTime = tick()
local damageExtents = {}
local function onAnimationPlayed()
animationStartTime = tick()
local timeDiff = animationTrack.Length * 0.7 - animationTrack.Length * 0.3
local sampleTimes = {}
local pointsoverride --math.clamp(math.floor(timeDiff / SAMPLE_POINTS_TIME_GRANULARITY + 0.5), 3, math.huge)
for i = 1, pointsoverride or SAMPLE_POINTS_TO_TAKE do
sampleTimes[i] = animationTrack.Length * 0.3 + timeDiff * (i - 1) / ((pointsoverride or SAMPLE_POINTS_TO_TAKE) - 1)
end
local maxDistanceAway
while animationTrack.IsPlaying do
for i, sampleTime in pairs(sampleTimes) do
if tick() - animationStartTime >= sampleTime then
for i, damageHitboxData in pairs(monsterData.damageHitboxCollection) do
local boundingBoxCF, boundingBoxSize = monsterModel:GetBoundingBox()
local damageHitboxCF = monsterModel[damageHitboxData.partName].CFrame * (damageHitboxData.originOffset or CFrame.new())
if not damageExtents[damageHitboxData.partName] then
damageExtents[damageHitboxData.partName] = {}
end
table.insert(damageExtents[damageHitboxData.partName], damageHitboxCF:toObjectSpace(boundingBoxCF))
table.remove(sampleTimes, i)
end
end
end
wait()
end
for i, v in pairs(connections) do
v:disconnect()
end
fetched = true
end
table.insert(connections, monsterModel.AnimationController.AnimationPlayed:connect(onAnimationPlayed))
while not (animationTrack.Length > 0) do wait(0.25) end
animationTrack.Looped = false
-- wait for this to update? idk
wait()
animationTrack:Play()
-- wait for animation stop
while not fetched do wait(0.25) end
-- local boundingBoxCF, boundingBoxSize = monsterModel:GetBoundingBox()
-- for i, v in pairs(damageExtents) do
-- for i, vv in pairs(v) do
-- local line = Instance.new("Part")
-- line.Anchored = true
-- line.CanCollide = false
-- line.TopSurface = Enum.SurfaceType.Smooth
-- line.BottomSurface = Enum.SurfaceType.Smooth
-- line.Material = Enum.Material.Neon
-- line.BrickColor = BrickColor.new("Institutional white")
--
-- line.Size = Vector3.new(0.25, 0.25, 0.25)
-- line.CFrame = boundingBoxCF * vv:inverse()
--
-- line.Parent = workspace
--
-- game:GetService("Debris"):AddItem(line, 2.5)
-- end
-- end
monsterDamageValidationData[monsterName] = damageExtents
wait(3)
end
end
end
else
warn("invalid monster", monsterName)
end
monsterModel:Destroy()
end
playerBaseCharacter:Destroy()
end)
-- end
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_dayNightCycle.lua
================================================
-- Fluid day and night script
-- berezaa 6/26/18
local DAY_TIME_SECONDS = 1200
if game.PlaceId == 4561988219 or game.PlaceId == 4041427413 then
DAY_TIME_SECONDS = 600
end
local module = {}
local network
local dynamicParts = {}
local ReplicatedStorage = game.ReplicatedStorage
local assets = ReplicatedStorage.assets
local sunRays = assets.misc.SunRays
local depthOfField = assets.misc.DepthOfField
local sky = assets.misc.Sky
local atmosphere = assets.misc.Atmosphere
-- Run the clock and handle dynamic map elements on the server.
-- Set Lighting.ClockTime on the client. Tween using the server
-- time only as a reference.
local Rand = Random.new(os.time())
for _, v in pairs(workspace:GetDescendants()) do
if v.Name == "WindowPart" or (v.Name == "Light" and v.Parent.Name == "Lantern") then
table.insert(dynamicParts,{Part = v, Num = Rand:NextNumber()})
end
end
-- Sunrise: 5.0 - 6.5
-- Sunset: 17.6 - 18.6
--[[
game.ReplicatedStorage.timeOfDay.Value = 10
game.Lighting.ClockTime = 10
game.Lighting.ShadowSoftness = 0.2
]]
local ClockTime = game.Lighting.ClockTime
game.ReplicatedStorage.timeOfDay.Value = ClockTime
if game.ReplicatedStorage:FindFirstChild("lightingSettings") and game.ReplicatedStorage.lightingSettings:FindFirstChild("timeLock") then
game.ReplicatedStorage.timeOfDay.Value = game.ReplicatedStorage.lightingSettings.timeLock.Value
game.Lighting.ClockTime = game.ReplicatedStorage.timeOfDay.Value
end
-- Start at sunset
local function on(Part)
if Part.Name == "WindowPart" then
Part.Material = Enum.Material.Neon
Part.Color = Part:FindFirstChild("WindowColor") and Part.WindowColor.Value or Color3.fromRGB(141, 140, 108)
local Light = Part:FindFirstChild("WindowLight")
if Light == nil then
Light = Instance.new("PointLight")
Light.Name = "WindowLight"
Light.Range = 10
Light.Parent = Part
end
Light.Enabled = true
elseif Part.Name == "Light" and Part.Parent and Part.Parent.Name == "Lantern" then
Part.Color = Color3.fromRGB(248,217,109)
Part.Material = Enum.Material.Neon
local Light = Part:FindFirstChild("LanternLight")
Part.Transparency = 0.2
if Light then
Light.Enabled = true
Light.Range = 25
Light.Brightness = 1
end
end
end
local function off(Part)
if Part.Name == "WindowPart" then
Part.Material = Enum.Material.Plastic
Part.Color = Color3.fromRGB(27,42,53)
local Light = Part:FindFirstChild("WindowLight")
if Light then
Light.Enabled = false
end
elseif Part.Name == "Light" and Part.Parent and Part.Parent.Name == "Lantern" then
Part.Color= Color3.fromRGB(180, 210, 228)
Part.Material = Enum.Material.Glass
Part.Transparency = 0.4
local Light = Part:FindFirstChild("LanternLight")
if Light then
Light.Enabled = false
end
end
end
local initiated = false
local function mergeColors(dayColor, nightColor, Brightness)
local dr, dg, db = Color3.toHSV(dayColor)
local nr, ng, nb = Color3.toHSV(nightColor)
return Color3.fromHSV(nr + (dr - nr) * Brightness, ng + (dg - ng) * Brightness, nb + (db - nb) * Brightness)
end
if game.Lighting:FindFirstChild("SunRays") == nil then
sunRays:Clone().Parent = game.Lighting
end
if game.Lighting:FindFirstChild("DepthOfField") == nil then
depthOfField:Clone().Parent = game.Lighting
end
if game.Lighting:FindFirstChild("Sky") == nil then
sky:Clone().Parent = game.Lighting
end
if game.Lighting:FindFirstChild("Atmosphere") == nil then
atmosphere:Clone().Parent = game.Lighting
end
game.Lighting.EnvironmentDiffuseScale = 0.8
game.Lighting.EnvironmentSpecularScale = 0.1
local function getVesterianDay()
return os.time() / DAY_TIME_SECONDS
end
local vesterianDayTag = Instance.new("NumberValue")
vesterianDayTag.Name = "vesterianDay"
vesterianDayTag.Value = getVesterianDay()
vesterianDayTag.Parent = game.ReplicatedStorage
spawn(function()
-- if game.PlaceId == 2061558182 then return end
while wait(1/5) do
local vesterianDay = getVesterianDay()
vesterianDayTag.Value = vesterianDay
local vesterianDayProgress = vesterianDay - math.floor(vesterianDay)
ClockTime = vesterianDayProgress * 24
if game.ReplicatedStorage:FindFirstChild("lightingSettings") and game.ReplicatedStorage.lightingSettings:FindFirstChild("timeLock") then
ClockTime = game.ReplicatedStorage.lightingSettings.timeLock.Value
end
game.ReplicatedStorage.timeOfDay.Value = ClockTime
-- game.Lighting.ClockTime = ClockTime
local Brightness = 0
-- Night
if ClockTime < 5.0 or ClockTime > 18.5 then
if not initiated then
initiated = true
for i,Object in pairs(dynamicParts) do
local Part = Object.Part
if Part.Name == "Light" and Part.Parent.Name == "Lantern" then
on(Part)
elseif Part.Name == "WindowPart" then
off(Part)
end
end
end
Brightness = 0
-- Turn off the window lights throughout the night.
if ClockTime >= 22 and ClockTime <= 24 then
local Progress = (ClockTime - 22) / 2
for i,Object in pairs(dynamicParts) do
local Part = Object.Part
if Part and Progress >= Object.Num then
if Part.Name == "WindowPart" then
off(Part)
end
end
end
end
-- Sunrise
elseif ClockTime >= 5.0 and ClockTime <= 6.5 then
local Progress = (ClockTime - 5.0) / 1.5
Brightness = Progress
for i,Object in pairs(dynamicParts) do
local Part = Object.Part
if Part and Progress >= Object.Num then
-- Turn off everything in the morning
off(Part)
end
end
-- Sunset
elseif ClockTime >= 17.5 and ClockTime <= 18.5 then
local Progress = (ClockTime - 17.5)
Brightness = 1 - Progress
for i,Object in pairs(dynamicParts) do
local Part = Object.Part
if Part and Progress >= Object.Num then
-- Turn on lanterns and windows at sunset
on(Part)
end
end
-- Day
else
Brightness = 1
if not initiated then
initiated = true
for i,Object in pairs(dynamicParts) do
local Part = Object.Part
if Part.Name == "Light" and Part.Parent.Name == "Lantern" then
off(Part)
elseif Part.Name == "WindowPart" then
off(Part)
end
end
end
end
game.Lighting.Brightness = 1
local fogEndMulti = 0.47
if game.ReplicatedStorage:FindFirstChild("fogEndMulti") then
fogEndMulti = game.ReplicatedStorage.fogEndMulti.Value
end
local light = game.ReplicatedStorage:FindFirstChild("lightingSettings")
local dayAmbient = light and light:FindFirstChild("dayAmbient") and light.dayAmbient.Value or (Color3.fromRGB(100, 100, 100))
local nightAmbient = light and light:FindFirstChild("nightAmbient") and light.nightAmbient.Value or Color3.fromRGB(50, 50, 100)
-- local dayOutdoorAmbient = light and light:FindFirstChild("dayOutdoorAmbient") and light.dayOutdoorAmbient.Value or Color3.fromRGB(140, 140, 140)
-- local nightOutdoorAmbient = light and light:FindFirstChild("nightOutdoorAmbient") and light.nightOutdoorAmbient.Value or Color3.fromRGB(110, 110, 120)
-- game.Lighting.OutdoorAmbient = mergeColors(dayOutdoorAmbient, nightOutdoorAmbient, Brightness)
game.Lighting.OutdoorAmbient = Color3.new(0,0,0)
--[[
game.Lighting.Ambient = mergeColors(dayAmbient, nightAmbient, Brightness)
local dayFogColor = light and light:FindFirstChild("dayFogColor") and light.dayFogColor.Value or Color3.fromRGB(151, 213, 214)
local nightFogColor = light and light:FindFirstChild("nightFogColor") and light.nightFogColor.Value or Color3.fromRGB(0, 66, 120)
game.Lighting.FogColor = mergeColors(dayFogColor, nightFogColor, Brightness)
game.Lighting.Atmosphere.Density = 0.438 - 0.164 * Brightness
game.Lighting.Atmosphere.Color = mergeColors(dayFogColor, nightFogColor, Brightness)
game.Lighting.Atmosphere.Haze = 2.15 - 2.15 * Brightness
game.Lighting.Atmosphere.Glare = 10 * Brightness
game.Lighting.ExposureCompensation = Brightness
game.Lighting.FogEnd = (500 + 2500 * Brightness) * fogEndMulti
]]
-- game.Lighting.Ambient = Color3.fromRGB(50 + 50 * Brightness, 50 + 50 * Brightness, 50 + 50 * Brightness)
-- game.Lighting.OutdoorAmbient = Color3.fromRGB(90 + 50 * Brightness, 90 + 50 * Brightness, 90 + 50 * Brightness)
end
end)
-- some time of day perks are handled here
local perkLookup = require(game.ReplicatedStorage.perkLookup)
function module.init(Modules)
network = Modules.network
local function updateTimeOfDayPerksForPlayer(player, dt)
local playerData = network:invoke("getPlayerData", player)
if not playerData then return end
local perks = playerData.nonSerializeData.statistics_final.activePerks
for perkName, active in pairs(perks) do
if active then
local perkData = perkLookup[perkName]
if perkData.onTimeOfDayUpdated then
perkData.onTimeOfDayUpdated(player, game.Lighting.ClockTime, dt)
end
end
end
end
local function update(dt)
for _, player in pairs(game:GetService("Players"):GetPlayers()) do
updateTimeOfDayPerksForPlayer(player, dt)
end
end
local function startUpdating()
while true do
update(wait(1))
end
end
spawn(startUpdating)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_effects.lua
================================================
local collectionService = game:GetService("CollectionService")
local module = {}
local function playMapSound(sound)
--assert(sound:IsA("Sound"), "Non-sound tagged as a mapSound: "..sound:GetFullName())
if sound:IsA("Sound") then
sound:Play()
end;
end
collectionService:GetInstanceAddedSignal("mapSound"):Connect(playMapSound)
for _, sound in pairs(collectionService:GetTagged("mapSound")) do
playMapSound(sound)
end
function module.init(Modules)
local network = Modules.network
network:create("effects_requestEffect", "RemoteEvent")
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_enchant.lua
================================================
local module = {}
local network
local numberGenerator_enchantment = Random.new()
-- item location view is either "inventory" or "equipment"
local function onEnchantEquipmentRequestReceived(player, enchantmentInventorySlotDataFromPlayer, equipmentInventorySlotDataFromPlayer, itemLocationView, playerInput)
itemLocationView = itemLocationView or "inventory"
local playerData = network:invoke("getPlayerData", player)
if playerData and equipmentInventorySlotDataFromPlayer.id and (itemLocationView == "inventory" or itemLocationView == "equipment") then
local itemLocationViewSlot_equipment, itemLocationViewSlotData_equipment do
if (itemLocationView == "inventory") then
itemLocationViewSlot_equipment, itemLocationViewSlotData_equipment = getTrueInventorySlotDataByInventorySlotDataFromPlayer(player, equipmentInventorySlotDataFromPlayer)
elseif (itemLocationView == "equipment") then
itemLocationViewSlot_equipment, itemLocationViewSlotData_equipment = getTrueEquipmentSlotDataByEquipmentSlotDataFromPlayer(player, equipmentInventorySlotDataFromPlayer)
end
end
if not itemLocationViewSlotData_equipment then
return false, "could not find equipment to encahnt"
end
if typeof(enchantmentInventorySlotDataFromPlayer) == "Instance" and enchantmentInventorySlotDataFromPlayer:FindFirstChild("itemEnchanted") then
-- enchant menu
local itemBaseData_equipment = itemLookup[itemLocationViewSlotData_equipment.id]
local cost = enchantment.enchantmentPrice(itemLocationViewSlotData_equipment)
if not cost then
return false, "invalid enchantment item"
end
if not (playerData.gold >= cost) then -- structured this way to prevent nonsense with NaN
return false, "not enough monies"
end
if (itemLocationViewSlotData_equipment.upgrades or 0) >= (itemLocationViewSlotData_equipment.maxUpgrades or 7) then
return false, "max upgraded"
end
local success = enchantment.applyEnchantment(itemLocationViewSlotData_equipment)
if success then
local wasSpent = network:invoke("tradeItemsBetweenPlayerAndNPC", player, {}, cost, {}, 0, "etc:enchant")
if not wasSpent then
network:invoke("reportError", player, "error", "Enchantment went through but money wasn't spent??")
end
if itemLocationView == "equipment" then
playerData.nonSerializeData.playerDataChanged:Fire("equipment")
else
playerData.nonSerializeData.playerDataChanged:Fire("inventory")
end
enchantmentInventorySlotDataFromPlayer.itemEnchanted:Play()
if enchantmentInventorySlotDataFromPlayer:FindFirstChild("steady") then
enchantmentInventorySlotDataFromPlayer.steady:Emit(50)
end
if itemLocationViewSlotData_equipment.blessed then
network:fireAllClients("signal_alertChatMessage", {Text = player.Name .. "'s ".. (itemBaseData_equipment.name or "equipment") .." is blessed by The Orb."; Font = Enum.Font.SourceSansBold; Color = Color3.fromRGB(0, 81, 255)} )
end
return true
end
return false, "idk what went wrong"
elseif enchantmentInventorySlotDataFromPlayer.id then
-- scroll item
local itemBaseData_enchantment = itemLookup[enchantmentInventorySlotDataFromPlayer.id]
local itemBaseData_equipment = itemLookup[equipmentInventorySlotDataFromPlayer.id]
if not itemLocationViewSlotData_equipment.enchantments then
itemLocationViewSlotData_equipment.enchantments = {}
end
if itemBaseData_enchantment and itemBaseData_enchantment.enchantsEquipment and itemBaseData_enchantment.applyScroll and itemBaseData_equipment then
local trueInventorySlot_enchantment, inventorySlotData_enchantment = getTrueInventorySlotDataByInventorySlotDataFromPlayer(player, enchantmentInventorySlotDataFromPlayer)
if inventorySlotData_enchantment and itemLocationViewSlotData_equipment then
local upgradeCost = itemBaseData_enchantment.upgradeCost or 1
local canEnchant, indexToRemove = enchantment.enchantmentCanBeAppliedToItem(inventorySlotData_enchantment, itemLocationViewSlotData_equipment)
if canEnchant then
local successfullyApplyScroll = numberGenerator_enchantment:NextNumber() <= itemBaseData_enchantment.successRate
-- .applyScroll is still used on state scrolls to determine if the equipment matches
local success, statusText, additionalInfo = itemBaseData_enchantment.applyScroll(player, itemLocationViewSlotData_equipment, successfullyApplyScroll, playerInput, itemBaseData_enchantment)
additionalInfo = additionalInfo or {}
if additionalInfo.successfullyApplied ~= nil then
successfullyApplyScroll = additionalInfo.successfullyApplied
end
local status
if success then
table.remove(playerData.inventory, trueInventorySlot_enchantment)
local itemDestroyed
if successfullyApplyScroll then
if itemBaseData_enchantment.enchantments then
local enchantmentWeightTable = {}
for i, enchantment in pairs(itemBaseData_enchantment.enchantments) do
if enchantment.selectionWeight and enchantment.selectionWeight > 0 then
if not enchantment.manual then
table.insert(enchantmentWeightTable, enchantment)
end
end
end
local selection, index = utilities.selectFromWeightTable(enchantmentWeightTable)
local trueIndex
for i, enchantment in pairs(itemBaseData_enchantment.enchantments) do
if enchantment == selection then
trueIndex = i
end
end
if selection and trueIndex then
--local upgradeTier = itemBaseData_enchantment.enchantments[index].tier or 1
--local tierColor = enchantment.tierColors[upgradeTier + 1]
local tierColor = Color3.fromRGB(112, 241, 255)
status = {Text = "The magic scroll lights up and its power is transferred to your equipment."; Color = additionalInfo.textColor3 or tierColor; Font = Enum.Font.SourceSansBold}
-- if a weaker upgrade is overridden
-- (this no longer occurs)
if indexToRemove then
status = {Text = "The scroll lights up and its power replaces an existing upgrade on your equipment."; Color = additionalInfo.textColor3 or tierColor; Font = Enum.Font.SourceSansBold}
table.remove(itemLocationViewSlotData_equipment.enchantments, indexToRemove)
end
table.insert(itemLocationViewSlotData_equipment.enchantments, {
id = itemBaseData_enchantment.id;
state = trueIndex;
})
else
error("Enchantment data state not found!")
end
end
else
-- scroll failed and it wants to destroy the item
if itemBaseData_enchantment.destroyItemOnFail and numberGenerator_enchantment:NextNumber() <= (itemBaseData_enchantment.destroyItemRate or 1) then
-- update position
if (itemLocationView == "inventory") then
itemLocationViewSlot_equipment, itemLocationViewSlotData_equipment = getTrueInventorySlotDataByInventorySlotDataFromPlayer(player, equipmentInventorySlotDataFromPlayer)
elseif (itemLocationView == "equipment") then
itemLocationViewSlot_equipment, itemLocationViewSlotData_equipment = getTrueEquipmentSlotDataByEquipmentSlotDataFromPlayer(player, equipmentInventorySlotDataFromPlayer)
end
status = {Text = "The magic scroll's curse destroys your equipment."; Color = additionalInfo.textColor3 or Color3.fromRGB(255, 96, 99); Font = Enum.Font.SourceSansBold}
if itemLocationView == "inventory" then
table.remove(playerData.inventory, itemLocationViewSlot_equipment)
elseif itemLocationView == "equipment" then
table.remove(playerData.equipment, itemLocationViewSlot_equipment)
end
itemDestroyed = true
else
-- let a state of -1 indicate a failed upgrade
if itemBaseData_enchantment.enchantments then
table.insert(itemLocationViewSlotData_equipment.enchantments, {
id = itemBaseData_enchantment.id;
state = -1;
})
end
status = {Text = "The magic scroll lights up and explodes into tiny shreds."; Color = additionalInfo.textColor3 or Color3.fromRGB(167, 167, 167); Font = Enum.Font.SourceSansBold}
end
end
if not itemDestroyed then
-- update the upgrades on the item
local upgrades = 0
local successfulUpgrades = 0
for i, enchantmentData in pairs(itemLocationViewSlotData_equipment.enchantments or {}) do
local enchantmentSource = itemLookup[enchantmentData.id]
local upgradeCost = enchantmentSource.upgradeCost or 1
local enchant = enchantmentSource.enchantments[enchantmentData.state]
if enchant and enchant.manual then
upgradeCost = 0
end
upgrades = upgrades + upgradeCost
-- state less than 0 indicates a failed upgrade
if enchantmentData.state >= 0 then
successfulUpgrades = successfulUpgrades + upgradeCost
end
end
itemLocationViewSlotData_equipment.upgrades = upgrades
itemLocationViewSlotData_equipment.successfulUpgrades = successfulUpgrades
end
if statusText then
status = {Text = statusText; Color = additionalInfo.textColor3 or Color3.fromRGB(190,190,190); Font = Enum.Font.SourceSansBold}
end
playerData.nonSerializeData.playerDataChanged:Fire("inventory")
-- update item stats if it was equipment
if itemLocationView == "equipment" then
playerData.nonSerializeData.playerDataChanged:Fire("equipment")
end
network:fireAllClients("playerAppliedScroll", player, itemBaseData_enchantment.id, successfullyApplyScroll)
return true, successfullyApplyScroll, itemLocationViewSlotData_equipment, status
end
if statusText then
status = {Text = statusText; Color = additionalInfo.textColor3 or Color3.fromRGB(255, 60, 60); Font = Enum.Font.SourceSansBold}
return false, false, nil, status
end
return false
end
end
end
end
end
return false
end
function module.init(Modules)
network = Modules.network
network:create("playerRequest_enchantEquipment", "RemoteFunction", "OnServerInvoke", onEnchantEquipmentRequestReceived)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_events.lua
================================================
local module = {}
local network
local events
function module.init(Modules)
network = Modules.network
events = Modules.events
network:create("fireEvent", "RemoteEvent", "OnServerEvent", function(player, ...)
events:fireEventLocal(...)
end)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_exploit.lua
================================================
local module = {}
local network
local function onPlayerAdded()
end
local function onPlayerRemoving(player)
end
local function init__exploiterCheckHeartbeat()
end
function module.init(Modules)
network = Modules.network
network:create("raisePlayerSuspicion", "BindableFunction", "OnServerInvoke", nil)
game.Players.PlayerAdded:connect(onPlayerAdded)
game.Players.PlayerRemoving:connect(onPlayerRemoving)
spawn(init__exploiterCheckHeartbeat)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_firepit.lua
================================================
local module = {}
local CollectionService = game:GetService("CollectionService")
local network
local statusEffectsV3 = require(game.ServerStorage.statusEffectsV3)
local firepits = CollectionService:GetTagged("firepit")
local isPlayerNearFirepit = {}
local DISTANCE_TO_BE_CLOSE = 15
local buff_data = {
icon = "rbxassetid://595268981";
modifierData = {
manaRegen = 2;
healthRegen = 80;
};
DO_NOT_SAVE = true;
}
local function onPlayerRemoving(player)
isPlayerNearFirepit[player] = nil
end
local function loop()
while wait(1/2) do
if #firepits > 0 then
for _, player in pairs(game.Players:GetPlayers()) do
if player.Character and player.Character.PrimaryPart then
local isNearFirepit, distanceAway = false, DISTANCE_TO_BE_CLOSE
for _, firepit in pairs(firepits) do
local distFrom = (player.Character.PrimaryPart.Position - firepit.Position).magnitude
if distFrom < DISTANCE_TO_BE_CLOSE and (not firepit:FindFirstChild("Fire") or firepit.Fire.Enabled) then
isNearFirepit = true
distanceAway = distFrom
break
end
end
if isNearFirepit ~= not not isPlayerNearFirepit[player] then
if isNearFirepit then
local wasApplied, statusEffectGUID = network:invoke("applyStatusEffectToEntityManifest", player.Character.PrimaryPart, "empower", buff_data, player.Character.PrimaryPart, "firepit", 0)
if wasApplied then
local untarget = Instance.new("BoolValue")
untarget.Name = "isTargetImmune"
untarget.Value = true
untarget.Parent = player.Character.PrimaryPart
isPlayerNearFirepit[player] = statusEffectGUID
end
else
if player.Character.PrimaryPart:FindFirstChild("isTargetImmune") then
player.Character.PrimaryPart.isTargetImmune:Destroy()
end
local wasRevoked = network:invoke("revokeStatusEffectByStatusEffectGUID", isPlayerNearFirepit[player])
--if wasRevoked then
isPlayerNearFirepit[player] = nil
--end
end
elseif isNearFirepit and distanceAway < 3 then
-- is touching firepit, add stacks of ablaze
local statusEffect = statusEffectsV3.createStatusEffect(
player.Character.PrimaryPart,
nil,
"ablaze",
{
damage = 10;
duration = 10;
}, "firepit-ablaze"
)
if statusEffect then
statusEffect:start()
end
end
end
end
end
end
end
function module.init(Modules)
network = Modules.network
game.Players.PlayerRemoving:connect(onPlayerRemoving)
spawn(loop)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_fishing.lua
================================================
local module = {}
local CollectionService = game:GetService("CollectionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local network
local placeSetup
local physics
local assetsFolder = ReplicatedStorage.assets
local httpService = game:GetService("HttpService")
local itemLookupContainer = ReplicatedStorage.itemData
local itemLookup = require(itemLookupContainer)
local LATENCY_FORGIVENESS_AFTER_BOBBING = 2.2
local spawnRegionCollectionsFolder
-- keep leveling data for the fish here
local fishpedia = {
["Fresh Fish"] = {id = "fish"; level = 1; expMulti = 1; favoredRodLevel = 1;};
["Zebra Fish"] = {id = "pretty pink fish"; level = 1; expMulti = 1; favoredRodLevel = 1;};
["Rock Fish"] = {id = "tall blue fish"; level = 2; expMulti = 1; favoredRodLevel = 1;};
}
local function getPoolEntry(fishName, chanceRolls)
if fishpedia[fishName] then
return {
id = fishpedia[fishName].id;
level = fishpedia[fishName].level;
rolls = chanceRolls;
favoredRodLevel = fishpedia[fishName].favoredRodLevel;
expMulti = fishpedia[fishName].expMulti;
}
end
return false, "entry does not exist in fishpedia"
end
-- have the rolls add up to approximately 100 for each map for consistent balance
local fishPool = {
["2061558182"] = {
getPoolEntry("Fresh Fish", 50);
getPoolEntry("Zebra Fish", 50);
};
["2119298605"] = {
{id = "fish"; level = 1; rolls = 20;}
};
}
local baseHitbox
local playerFishingDataContainer = {}
local rand = Random.new()
local function tick__fishBobbing(player, guid)
wait(rand:NextInteger(4, 10))
-- make sure we don't fire for an old fish attempt
if playerFishingDataContainer[player] and playerFishingDataContainer[player].guid == guid then
-- check if the bobby is actual in a pile still
local inRange = false
for _, spawnRegionCollection in pairs(spawnRegionCollectionsFolder:GetChildren()) do
for _, child in pairs(spawnRegionCollection.FishFolder:GetChildren()) do
if child.Parent and child:isA("Part") then
if (child.Position - playerFishingDataContainer[player].castPosition).magnitude <= script.spot.Size.Y / 2 +.1 then
inRange = true
end
end
end
end
if inRange then
playerFishingDataContainer[player].lastTimeBobbed = tick()
network:fireClient("signal_fishingBobBobbed", player)
end
-- tick again
tick__fishBobbing(player, guid)
end
end
local function playerRequest_startFishing(player, castPosition)
if not playerFishingDataContainer[player] then
-- tracking cast position now for security and practical reasons
local char = player.Character
if char then
if (char.PrimaryPart.Position - castPosition).magnitude > 100 then return end
else
return
end
local playerData = network:invoke("getPlayerData", player)
if playerData then
-- todo: check for rod, account for strength, etc
local guid = httpService:GenerateGUID(false)
local playerFishingData = {}
--playerFishingData.rod = nil
playerFishingData.lastTimeBobbed = 0
playerFishingData.guid = guid
playerFishingData.castPosition = castPosition
playerFishingDataContainer[player] = playerFishingData
spawn(function()
wait(1)
if playerFishingDataContainer[player] and playerFishingDataContainer[player].guid == guid then
tick__fishBobbing(player, guid)
end
end)
return true
end
end
end
-- added security
local function playerRequest_reelFishingRod(player, bobPosition)
if playerFishingDataContainer[player] then
if tick() - playerFishingDataContainer[player].lastTimeBobbed <= LATENCY_FORGIVENESS_AFTER_BOBBING then -- latency + being quick enough to react, ya dig
playerFishingDataContainer[player] = nil
--local fishToSpawnId = {30;30;30;30;30;30;30;30; 38; 39; 40; 41; 42}
-- grab dat fish
local mapPool = fishPool[tostring(game.PlaceId)]
if mapPool == nil then return end
local fishingLevel = network:invoke("getProfessionLevel", player, "fishing") or 1
-- do a check with the rod to give a slight edge to the rarer fish. level will unlock the fish and the rod will give you a better chance at catching better fish
-- formula will be: check if player's rod is >= to the fish's favoredRodLevel, and if so, increase the chances of that specific fish by x% by creating more roll entries
-- % increase determined by how many rod levels you are above the favored rod level
-- so if a high level player for whatever reason wants to catch a lower level fish, they have the option to do it by changing rods versus the game forcing them to catch a high level fish even more based off of their level
local primaryEquipment = network:invoke("getPlayerEquipmentDataByEquipmentPosition", player, 1)
if primaryEquipment == nil then return end
local primaryId = primaryEquipment.id
local rodLevel = 0
if primaryId == 37 then
-- old fishing pole
rodLevel = 1
else
return -- that aint no rod
end
local validPool = {}
for _, fish in pairs(mapPool) do
if fish.level <= fishingLevel then
if fish.favoredRodLevel < rodLevel then
for _ = 1, fish.rolls do
table.insert(validPool, fish.id)
end
else
for _ = 1, fish.rolls + math.clamp((fishingLevel - fish.level)*2, 0, fish.rolls) do -- can up to double the rolls on a given map
table.insert(validPool, fish.id)
end
end
end
end
if #validPool == 0 then return end
local lottery = validPool[rand:NextInteger(1, #validPool)]
local fishToSpawnItemId = itemLookup[lottery].id
-- todo: validation
local fish = network:invoke("spawnItemOnGround", {id = fishToSpawnItemId}, bobPosition + Vector3.new(0, 0.5, 0), {player})
local facingVelocity = (bobPosition - player.Character.PrimaryPart.Position).unit
local r = CFrame.new(Vector3.new(), facingVelocity) * CFrame.Angles(-math.pi / 4, math.pi / 8 * (rand:NextInteger() - 0.5) * 2, 0)
local fishVelocity = -Vector3.new(r.lookVector.X, r.lookVector.Y * 1.4, r.lookVector.Z) * 50
fish.Velocity = fishVelocity
local attachment = Instance.new("Attachment")
attachment.Parent = fish
fish:SetNetworkOwner(player)
fish.HumanoidRootPart:SetNetworkOwner(player)
local playerData = network:invoke("getPlayerData", player)
if playerData then
-- award xp
--playerData.nonSerializeData.incrementPlayerData("exp",xp)
end
return true, fish, fishVelocity
else
playerFishingDataContainer[player] = nil
end
end
end
local function onPlayerRemoving(player)
playerFishingDataContainer[player] = nil
end
local FISH_SPAWN_CYCLE_TIME = 1.5
function module.init(Modules)
network = Modules.network
placeSetup = Modules.placeSetup
physics = Modules.physics
spawnRegionCollectionsFolder = placeSetup.getPlaceFolder("fishingRegionCollections")
baseHitbox = assetsFolder.entities.spot:Clone()
baseHitbox.CanCollide = true
baseHitbox.Anchored = true
CollectionService:AddTag(baseHitbox, "fishingSpot")
physics:setWholeCollisionGroup(baseHitbox, "fishingSpots")
game.Players.PlayerRemoving:connect(onPlayerRemoving)
network:create("playerRequest_startFishing", "RemoteFunction", "OnServerInvoke", playerRequest_startFishing)
network:create("playerRequest_reelFishingRod", "RemoteFunction", "OnServerInvoke", playerRequest_reelFishingRod)
network:create("signal_fishingBobBobbed", "RemoteEvent")
spawn(function()
while wait(0 or FISH_SPAWN_CYCLE_TIME ) do
if spawnRegionCollectionsFolder == nil then return end
for _, spawnRegionCollection in pairs(spawnRegionCollectionsFolder:GetChildren()) do
--monsterType, monsterSpawnAmount
local _, monsterSpawnAmount = string.match(spawnRegionCollection.Name, "(.+)-(%d+)")
monsterSpawnAmount = tonumber(monsterSpawnAmount)
if monsterSpawnAmount then
local amntToSpawn = monsterSpawnAmount - #spawnRegionCollection.FishFolder:GetChildren()
if amntToSpawn > 0 then
-- look up fish stuff to get the stats
local fish = baseHitbox:Clone()
local spawnParts = {}
for _, child in pairs(spawnRegionCollection:GetChildren()) do
if child:isA("Part") then
table.insert(spawnParts, child)
end
end
local randPart = spawnParts[rand:NextInteger(1,#spawnParts)]
-- fix the randomization here
local randomX = rand:NextInteger(randPart.Position.X - randPart.Size.X/2, randPart.Position.X + randPart.Size.X/2)
local randomZ = rand:NextInteger(randPart.Position.Z - randPart.Size.Z/2, randPart.Position.Z + randPart.Size.Z/2)
fish.Position = Vector3.new(randomX, randPart.Position.Y + randPart.Size.Y/2 -.25 , randomZ) --- .75
fish.effect.Position = fish.Position - Vector3.new(0,.75,0)
fish.Parent = spawnRegionCollection.FishFolder
game.Debris:AddItem(fish, rand:NextInteger(20, 60))
end
end
end
end
end)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_gift.lua
================================================
local module = {}
local network
-- TODO: hook back into manager_player
local function playerRequest_claimGifts(player)
local playerData = playerDataContainer[player]
if playerData and playerData.globalData then
if ((not playerData.globalData.alphaGiftClaimed2) and (not playerData.alphaGiftClaimed2)) or ((not playerData.globalData.alphaGiftClaimed) and (not playerData.alphaGiftClaimed)) then
if game:GetService("BadgeService"):UserHasBadgeAsync(player.userId, 2124445897) then
if int__tradeItemsBetweenPlayerAndNPC(player, {}, 0, {{id = 158, stacks = 1}}, 50000, "gift:alpha") then
playerData.globalData.alphaGiftClaimed2 = true
playerData.alphaGiftClaimed2 = true
playerData.globalData.alphaGiftClaimed = true
playerData.alphaGiftClaimed = true
return true
else
return false, "Hmm, is your inventory full or something?"
end
end
end
if ((not playerData.globalData.spiderGiftClaimed) and (not playerData.spiderGiftClaimed)) then
if player.Name == "berezaa" or game:GetService("MarketplaceService"):PlayerOwnsAsset(player, 4027043310) then
if int__tradeItemsBetweenPlayerAndNPC(player, {}, 0, {{id = 177, stacks = 1}}, 50000, "gift:spider") then
playerData.globalData.spiderGiftClaimed = true
playerData.spiderGiftClaimed = true
return true
else
return false, "Hmm, is your inventory full or something?"
end
end
end
end
return false
end
function module.init(Modules)
network = Modules.network
network:create("playerRequest_claimGifts", "RemoteFunction", "OnServerInvoke", playerRequest_claimGifts)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_guild.lua
================================================
local module = {}
local HttpService = game:GetService("HttpService")
local TeleportService = game:GetService("TeleportService")
local network
module.priority = 3
local guildRankValues = {
member = 1;
officer = 2;
general = 3;
leader = 4;
}
local function getRankNumberFromRank(rank)
return guildRankValues[rank]
end
local DATA_MODIFY_COOLDOWN = 5
local guildNameStore = game:GetService("DataStoreService"):GetDataStore("guildNames")
local guildPurchases = {
createGuild = 1e6;
level = {
[1] = {
members = 10;
};
[2] = {
members = 20;
cost = 10e6,
};
[3] = {
members = 40;
cost = 100e6,
};
[4] = {
members = 80;
cost = 1e9,
};
[5] = {
members = 140;
cost = 10e9,
}
}
};
local guildDataFolder = Instance.new("Folder")
guildDataFolder.Name = "guildDataFolder"
guildDataFolder.Parent = game.ReplicatedStorage
local messagingService = game:GetService("MessagingService")
local guildDataCache = {}
local guildMessagingConnections = {}
--[[
sendGuildMessage(guid, {
messageType = "data_update";
key = key;
value = value;
sender = player.Name;
senderId = player.userId;
})
]]
-- Update cache and replicated storage entry with new data.
local function guildDataUpdated(guid, guildDataEntry)
guildDataCache[guid] = guildDataEntry
local dataFolderEntry = guildDataFolder:FindFirstChild(guid)
if dataFolderEntry == nil then
dataFolderEntry = Instance.new("StringValue")
dataFolderEntry.Name = guid
dataFolderEntry.Parent = guildDataFolder
end
dataFolderEntry.Value = HttpService:JSONEncode(guildDataEntry)
for i,player in pairs(game.Players:GetPlayers()) do
if player:FindFirstChild("guildId") and player.guildId.Value == guid then
network:fireClient("signal_guildDataUpdated", player, guildDataEntry)
end
end
end
local function guildMessageRecieved(guid, message)
local data = message.Data
local timestamp = message.Sent
-- Message to guild members.
if data.messageType == "chat" then
local sender = data.sender
local message = data.message
for i,player in pairs(game.Players:GetPlayers()) do
if player:FindFirstChild("guildId") and player.guildId.Value == guid then
local chatMessage = "[Guild] " .. sender .. ": "..data.message
network:fireClient("signal_alertChatMessage", player, {Text = chatMessage; Font = Enum.Font.SourceSansBold; Color = Color3.fromRGB(160, 116, 255)} )
end
end
elseif data.messageType == "data_update" then
local guildData = guildDataCache[guid]
if guildData then
guildData.lastUpdated = timestamp
guildData[data.key] = data.value
guildDataUpdated(guid, guildData)
end
elseif data.messageType == "member_data" then
-- soft update for things like leveling up, changing class
local guildData = guildDataCache[guid]
if guildData then
local userId = data.userId
guildData.members[tostring(userId)] = data.memberData
guildDataUpdated(guid, guildData)
end
end
-- allow an optional notice to all guild members via chat
if data.notice then
local sender = data.sender or "???"
-- "Out" means don't include the message on the server that sent it
-- (for level up message)
if game.Players:FindFirstChild(sender) == nil or not data.notice.Out then
if data.notice.Color then
data.notice.Color = Color3.fromRGB(data.notice.Color.r, data.notice.Color.g, data.notice.Color.b)
end
for i,player in pairs(game.Players:GetPlayers()) do
if player:FindFirstChild("guildId") and player.guildId.Value == guid then
-- data.notice: {Text = chatMessage; Font = Enum.Font.SourceSansBold; Color = Color3.fromRGB(145, 71, 255)}
network:fireClient("signal_alertChatMessage", player, data.notice)
end
end
end
end
end
-- Send a message via messaging service.
local function sendGuildMessage(guid, messageData)
local messageSuccess, messageError = pcall(function()
messagingService:PublishAsync("guild-"..guid, messageData)
end)
return messageSuccess, messageError
end
local function sendGuildChat(player, message)
local guildId = player:FindFirstChild("guildId") and player.guildId.Value
if guildId and guildId ~= "" then
local filteredText
local filterSuccess, filterError = pcall(function()
filteredText = game.Chat:FilterStringForBroadcast(message, player)
end)
if not filterSuccess then
return false, "filter error: "..filterError
elseif #filteredText > 200 then
return false, "Message may not be longer than 200 characters."
elseif #filteredText < 2 then
return false, "Message must be at least 2 characters long"
end
return sendGuildMessage(guildId, {
messageType = "chat";
sender = player.Name;
senderId = player.userId;
message = filteredText;
})
else
return false, "You're not in a guild!"
end
end
-- Used to add/remove a player from a guild.
local function setPlayerGuildId(player, guildId)
if player:FindFirstChild("guildId") then
player.guildId.Value = guildId or ""
end
local guildFounderData = network:invoke("getPlayerData", player)
if guildFounderData and guildFounderData.globalData then
guildFounderData.globalData.guildId = guildId
guildFounderData.nonSerializeData.setPlayerData("globalData", guildFounderData.globalData)
end
end
-- Fetches guild data, returns cache data if available.
local function getGuildData(player, guid)
if guid == "" then
return nil
end
local guildDataEntry = guildDataCache[guid]
if guildDataEntry then
return guildDataEntry
else
local guildDataStore = game:GetService("DataStoreService"):GetDataStore("guild", guid)
local dataSuccess, dataError = pcall(function()
guildDataEntry = guildDataStore:GetAsync("guildData")
end)
if dataSuccess then
if guildDataEntry then
-- if this guild no longer has me as a member, purge my id
local members = guildDataEntry.members
if members and (not members[tostring(player.UserId)]) then
setPlayerGuildId(player, nil)
return nil
end
-- okay, we're good to go, return properly
guildDataUpdated(guid, guildDataEntry)
return guildDataEntry
else
-- guild no longer exists, purge my id
setPlayerGuildId(player, nil)
return nil
end
else
network:invoke("reportError", player, "warning", "Guild Get Fail: ".. (dataError or "???"))
end
end
end
local function playerRequest_getGuildData(requestingPlayer, player)
local guildId = player:FindFirstChild("guildId")
if not guildId then
return false, "No guildId found."
end
return getGuildData(player, guildId.Value)
end
-- Fetches a player's data entry within a guild, if it exists
local function getGuildPlayerData(player, guid)
--[[
userId = partyMemberPlayer.userId;
level = partyMemberPlayer.level.Value;
class = partyMemberPlayer.class.Value;
rank = (partyMemberData.isLeader and "leader") or "member";
founder = true;
points = 0;
]]
local guildData = getGuildData(player, guid)
if guildData == nil then
return false, "Guild data not found."
end
local members = guildData.members
local guildPlayerData = members[tostring(player.userId)]
if guildPlayerData == nil then
return false, "Not a member of guild."
end
return guildPlayerData
end
local function playerRequest_getGuildMemberData(requestingPlayer, player)
local guildId = player:FindFirstChild("guildId")
if not guildId then
return false, "No guildId found."
end
return getGuildPlayerData(player, guildId.Value)
end
-- Master function used for donating to the guild, other actions
local function modifyGuildDataValue(player, guid, action, key, value, atomic, notice)
action = action or "set"
local guildData = getGuildData(player, guid)
if guildData == nil then
return false, "No guild data."
end
if os.time() - guildData.lastModified >= DATA_MODIFY_COOLDOWN then
local guildDataStore = game:GetService("DataStoreService"):GetDataStore("guild", guid)
local canceledReason
local dataSuccess, dataError = pcall(function()
guildDataStore:UpdateAsync("guildData", function(existingData)
if atomic then
-- if guildData.lastModified == existingData.lastModified then
-- allow messagingService "soft updates" to sneak in through here
guildData.lastModified = os.time()
existingData = guildData
-- else
-- canceledReason = "Atomic transaction out of sync"
-- return nil
-- end
end
if action == "increment" then
existingData[key] = (existingData[key] or 0) + value
elseif action == "set" then
existingData[key] = value
else
canceledReason = "Invalid operation"
return nil
end
guildDataUpdated(guid, existingData)
return existingData
end)
end)
if not dataSuccess then
network:invoke("reportError", player, "warning", "Failed to update guild value: "..dataError)
return false, "DataStore error"
elseif canceledReason then
return false, canceledReason
else
return true, "Success!"
end
else
return false, "Try again later."
end
end
-- Removes guild data cache and closes subscriptions.
local function unsubscribeGuildData(guid)
guildDataCache[guid] = nil
if guildMessagingConnections[guid] then
guildMessagingConnections[guid]:Disconnect()
guildMessagingConnections[guid] = nil
end
local dataFolderEntry = guildDataFolder:FindFirstChild(guid)
if dataFolderEntry then
dataFolderEntry:Destroy()
end
end
-- Build a subscription for a guild.
local function subscribeGuild(guid)
if guildMessagingConnections[guid] then
return false, "Guild subscription already exists."
end
if guildDataCache[guid] == nil then
return false, "Guild data does not exist."
end
local subscribeSuccess, subscribeError = pcall(function()
local subscription = messagingService:SubscribeAsync("guild-"..guid, function(message)
guildMessageRecieved(guid, message)
end)
guildMessagingConnections[guid] = subscription
end)
if subscribeSuccess then
return true, "Subscribed!"
else
return false, subscribeError
end
end
-- Fires whenever a player joins or leaves the server or a guild, used to subscribe/unsubscribe.
local function playerGuildChanged(player, guid)
local activeGuilds = {}
if guid then
activeGuilds[guid] = true
end
-- Player needs a guild but guild data is not active?
if guid and not guildDataCache[guid] then
getGuildData(player, guid)
end
-- Cycle through all other players and see which guilds are active.
for i, otherPlayer in pairs(game.Players:GetPlayers()) do
if otherPlayer ~= player and otherPlayer:FindFirstChild("teleporting") == nil and otherPlayer:FindFirstChild("DataSaveFailed") == nil then
local guildTag = otherPlayer:FindFirstChild("guildId")
if guildTag and guildTag.Value ~= "" then
activeGuilds[guildTag.Value] = true
end
end
end
-- Cycle through guild cache and remove subscriptions for un-used guilds.
for guildId, guildData in pairs(guildDataCache) do
if not activeGuilds[guildId] then
unsubscribeGuildData(guildId)
end
end
-- Cycle through active guilds and build subscription for any guilds not yet initiated.
for guildId, active in pairs(activeGuilds) do
if active and guildMessagingConnections[guildId] == nil then
-- Check to confirm cached data exists (it should!)
local guildData = guildDataCache[guildId]
if guildData then
local subscriptionSuccess, subscriptionError = subscribeGuild(guid)
if not subscriptionSuccess then
network:invoke("reportError", player, "warning", "Failed to build guild subscription: "..subscriptionError)
end
else
network:invoke("reportError", player, "critical", "Attempt to build subscription for guild with no data!")
return
end
end
end
end
-- Slap 'em with that guildId tag
local function onPlayerAdded(player)
if player:FindFirstChild("guildId") == nil then
local guildTag = Instance.new("StringValue")
guildTag.Name = "guildId"
guildTag.Parent = player
end
end
for i,player in pairs(game.Players:GetPlayers()) do
onPlayerAdded(player)
end
game.Players.PlayerAdded:connect(onPlayerAdded)
local function onPlayerLoaded(player, playerData)
-- Tag the player's guild so others can see it.
local guildTag = player.guildId
local globalData = playerData.globalData
if globalData and globalData.guildId then
local guid = globalData.guildId
local guildData = getGuildData(player, guid)
if guildData then
-- Check that the player is still a member of the guild.
if guildData.members[tostring(player.userId)] then
guildTag.Value = guid
playerGuildChanged(player, guid) -- The Msg. subscription isn't made until this point.
else
-- Removed from guild.
local updatedPlayerData = network:invoke("getPlayerData", player)
if updatedPlayerData.globalData then
updatedPlayerData.globalData.guildId = nil
updatedPlayerData.nonSerializeData.setPlayerData("globalData", updatedPlayerData.globalData)
end
network:fireClient("alertPlayerNotification", player, {text = "You were removed from your guild."; textColor3 = Color3.fromRGB(255, 57, 60)}, 10)
playerGuildChanged(player, nil)
end
end
end
-- hook up some connections
player.level.Changed:connect(function()
local guid = player.guildId.Value
if guid ~= "" then
local guildData = getGuildData(player, guid)
if guildData then
local guildMemberData = guildData.members[tostring(player.userId)]
if guildMemberData then
guildMemberData.level = player.level.Value
guildMemberData.lastUpdate = os.time();
local notice = {Text = player.Name .. " has reached Lvl."..player.level.Value.."!"; Font = "SourceSansBold"; Color = {r=161, g=132, b=194}; Out = true}
sendGuildMessage(guid, {
messageType = "member_data";
memberData = guildMemberData;
sender = player.Name;
senderId = player.userId;
notice = notice;
})
end
end
end
end)
player.class.Changed:connect(function()
local guid = player.guildId.Value
if guid ~= "" then
local guildData = getGuildData(player, guid)
if guildData then
local guildMemberData = guildData.members[tostring(player.userId)]
if guildMemberData then
guildMemberData.class = player.class.Value
local notice = {Text = player.Name .. " has become a "..player.class.Value.."!"; Font = "SourceSansBold"; Color = {r=161, g=132, b=194}}
sendGuildMessage(guid, {
messageType = "member_data";
memberData = guildMemberData;
sender = player.Name;
senderId = player.userId;
notice = notice;
})
end
end
end
end)
end
local function onPlayerRemoving(player)
playerGuildChanged(player, nil)
end
game.Players.PlayerRemoving:connect(onPlayerRemoving)
-- Creating a guild.
local function playerRequest_createGuild(player, properties)
local playerData = network:invoke("getPlayerData", player)
if playerData == nil then
return false, "no player data."
end
local globalData = playerData.globalData
if globalData == nil then
return false, "no global data."
end
local isStudio = game:GetService("RunService"):IsStudio()
-- davidii doesn't think this is necessary a'ight? who cares if they make a lot of guilds
-- if globalData.lastCreatedGuild and (os.time() - globalData.lastCreatedGuild <= 60 * 60 * 48) and (not isStudio) then
-- return false, "You must wait 48 hours before founding a new guild."
-- end
if player:FindFirstChild("teleporting") or player:FindFirstChild("DataSaveFail") then
return false, "There is an issue accessing your data, please try again later."
end
if globalData.guildId then
return false, "Already in a guild."
end
local partyData = network:invoke("getPartyDataByPlayer", player)
if partyData == nil then
return false, "Only the leader of a full party can found a guild."
end
local numPlayers = 0
local guildFounders = {}
for i, partyMemberData in pairs(partyData.members) do
numPlayers = numPlayers + 1
local partyMemberPlayer = partyMemberData.player
local partyMemberPlayerData = network:invoke("getPlayerData", partyMemberPlayer)
if partyMemberPlayerData == nil or partyMemberPlayerData.globalData == nil then
return false, "A player in your party is missing data."
elseif partyMemberPlayerData.globalData.guildId then
return false, "A player in your party is already in a guild."
elseif partyMemberData.isLeader and partyMemberPlayer ~= player then
return false, "Only the leader of a full party can found a guild."
elseif partyMemberPlayer.level.Value < 10 then
return false, "All players in your party must be at least Lvl. 10 to found a guild."
end
guildFounders[tostring(partyMemberPlayer.userId)] = {
lastUpdate = os.time();
name = partyMemberPlayer.Name;
userId = partyMemberPlayer.userId;
level = partyMemberPlayer.level.Value;
class = partyMemberPlayer.class.Value;
rank = (partyMemberData.isLeader and "leader") or "member";
founder = true;
points = 0;
}
end
if (numPlayers ~= 6) and (not isStudio) then
return false, "There must be exactly six members in your party to found a guild."
end
local guildCreationCost = guildPurchases.createGuild
if playerData.gold >= guildCreationCost then
local guildId = HttpService:GenerateGUID(false)
-- Run the desired guild name through the Roblox Filter.
local desiredGuildName = properties.name
if properties.name == nil then
return false, "No guild name provided."
elseif typeof(properties.name) ~= "string" then
return false, "Guild name must be a string."
end
local filteredText
local filterSuccess, filterError = pcall(function()
filteredText = game.Chat:FilterStringForBroadcast(desiredGuildName, player)
end)
if not filterSuccess then
return false, "filter error: "..filterError
elseif not filteredText or string.find(filteredText, "#") then
return false, "Guild name rejected by Roblox filter."
elseif #filteredText > 20 then
return false, "Guild name must be no more than 20 characters long."
elseif #filteredText < 3 then
return false, "Guild name must be at least 3 characters long."
end
-- Check the guild name database(TM) for a conflict.
local isNameTaken
local nameCheckSuccess, nameCheckError = pcall(function()
isNameTaken = guildNameStore:GetAsync(filteredText)
end)
if not nameCheckSuccess then
network:invoke("reportError", player, "warning", "Guild namestore fail: ".. (nameCheckError or "???"))
return false, "Guild name lookup failed due to DataStore error."
elseif isNameTaken then
return false, "That Guild name is already taken."
end
local guildName = filteredText
-- now we jump into the meat.
local guildDataStore = game:GetService("DataStoreService"):GetDataStore("guild", guildId)
local abortDueToExistingData
local guildData
local guildSuccessfullyCreated
-- oopsies required from here on out.
local function applyGuildIdToFounders(guildIdToApply)
for userId, userData in pairs(guildFounders) do
local guildFounder = game.Players:GetPlayerByUserId(tonumber(userId))
if guildFounder and guildFounder.Parent then
setPlayerGuildId(guildFounder, guildIdToApply)
end
end
end
-- apply guild membership to all other users (this happens before yielding update async - may cause problems).
applyGuildIdToFounders(guildId)
local dataSuccess, dataError = pcall(function()
guildDataStore:UpdateAsync("guildData", function(existingData)
-- In the astronomically rare chance a guild with this data already exists.
if existingData ~= nil then
abortDueToExistingData = true
return nil
end
-- We're good.
guildData = {
name = guildName;
previousNames = {};
members = guildFounders;
lastModified = os.time();
id = guildId;
version = 1;
level = 1;
points = 0;
bank = 0;
}
-- we did it!.
guildSuccessfullyCreated = true
return guildData
end)
end)
if not dataSuccess then
network:invoke("reportError", player, "warning", "Guild DataStore fail: ".. (dataError or "???"))
applyGuildIdToFounders(nil) -- oopsie!
return false, "DataStore failure!"
elseif abortDueToExistingData then
applyGuildIdToFounders(nil) -- oopsie!
return false, "Guild ID already exists!"
end
-- final stretch.
if guildSuccessfullyCreated then
local globalData = playerData.globalData
if globalData == nil or player.Parent == nil or player:FindFirstChild("teleporting") or player:FindFirstChild("DataSaveFail") then
applyGuildIdToFounders(nil) -- oopsie!
return false, "Issue with founder data"
end
-- take money and apply guild leader data.
globalData.lastCreatedGuild = os.time()
playerData.nonSerializeData.incrementPlayerData("gold",-guildCreationCost,"guild:create")
globalData.guildId = guildId
playerData.nonSerializeData.setPlayerData("globalData", globalData)
-- run some really scary code that claims the guild name.
spawn(function()
local i = 1
local guildNameClaimed
local guildNameClaimError
repeat
local guildSuccess, guildError = pcall(function()
guildNameStore:SetAsync(filteredText, true)
end)
if guildSuccess then
guildNameClaimed = true
else
network:invoke("reportError", player, "warning", "Guild name claim fail: ".. (guildError or "???"))
wait(2^i)
end
until guildNameClaimed
end)
playerGuildChanged(player, guildId)
-- we did it guys.
return true, "A new guild has been born!"
else
applyGuildIdToFounders(nil) -- oopsie!
return false, "Guild failed to be created for some reason"
end
else
return false, "You don't have enough money to found a guild."
end
end
local pendingGuildInvites = {}
local function playerRequest_invitePlayerToGuild(player, invitedPlayer)
if invitedPlayer.guildId.Value ~= "" then
return false, "Player is already in a guild."
end
local guid = player.guildId.Value
local guildPlayerData = getGuildPlayerData(player, guid)
if guildPlayerData == nil then
return false, "Could not find your guild."
elseif guildPlayerData.rank ~= "leader" and guildPlayerData.rank ~= "officer" and guildPlayerData.rank ~= "general" then
return false, "You do not have permission to invite new members."
end
local guildData = getGuildData(player, guid)
if guildData == nil then
return false, "Could not find guild data."
end
local memberCap = guildPurchases.level[guildData.level].members
local existingMembers = 0
for userId, userData in pairs(guildData.members) do
existingMembers = existingMembers + 1
end
if existingMembers >= memberCap then
return false, "Your guild is already at full capacity."
end
local playerResponse = network:invokeClient("serverPrompt_playerInvitedToServer", invitedPlayer, player, guid)
if playerResponse then
-- check nothing changed
if player and player.Parent and invitedPlayer and invitedPlayer.Parent and invitedPlayer.guildId.Value == "" then
local guildData = getGuildData(player, guid)
local guildPlayerData = getGuildPlayerData(player, guid)
if guildData and guildPlayerData then
-- add player to the guild
local guildMembers = guildData.members
-- failsafe to allow someone to be re-invited if their lost their guild id for whatever reason
guildMembers[tostring(invitedPlayer.userId)] = guildMembers[tostring(invitedPlayer.userId)] or {
lastUpdate = os.time();
name = invitedPlayer.Name;
userId = invitedPlayer.userId;
level = invitedPlayer.level.Value;
class = invitedPlayer.class.Value;
rank = "member";
points = 0;
}
local notice = {Text = invitedPlayer.Name .. " has joined the Guild. (Invited by "..player.Name..")"; Font = "SourceSansBold"; Color = {r=161, g=132, b=194}}
local success, reason = modifyGuildDataValue(player, guid, "set", "members", guildMembers, true, notice)
if success then
sendGuildMessage(guid, {
messageType = "member_data";
memberData = guildMembers[tostring(invitedPlayer.userId)];
sender = player.Name;
senderId = player.userId;
notice = notice;
})
setPlayerGuildId(invitedPlayer, guid)
guildDataUpdated(guid, guildData)
end
return success, reason
end
else
return false, "Something changed during the player input and they can no longer be added."
end
else
return false, "Player did not accept the guild invite."
end
end
local function playerRequest_exileUserIdFromGuild(player, exiledUserId)
local guid = player.guildId.Value
local guildPlayerData = getGuildPlayerData(player, guid)
if not guildPlayerData then
return false, "Could not find your guild."
end
local guildData = getGuildData(player, guid)
if guildData == nil then
return false, "Guild data not found"
end
local exiledGuildPlayerData = guildData.members[tostring(exiledUserId)]
if exiledGuildPlayerData == nil then
return false, "Player is not in your guild."
end
local exiledUsername = exiledGuildPlayerData.name
local playerGuildRankValue = guildRankValues[guildPlayerData.rank]
local exiledGuildRankValue = guildRankValues[exiledGuildPlayerData.rank]
if playerGuildRankValue <= exiledGuildRankValue then
return false, "You cannot exile someone who does not rank beneath you."
elseif exiledGuildPlayerData.founder and guildPlayerData.rank ~= "leader" then
return false, "Only the leader can exile a founding member."
end
local guildMembers = guildData.members
guildMembers[tostring(exiledUserId)] = nil;
local notice = {Text = exiledUsername .. " has been exiled from the guild by "..player.Name.."!"; Font = "SourceSansBold"; Color = {r=161, g=132, b=194}}
local success, reason = modifyGuildDataValue(player, guid, "set", "members", guildMembers, true, notice)
if success then
local exiledPlayer = game.Players:GetPlayerByUserId(exiledUserId)
-- ez boot if they are in the same server.
if exiledPlayer then
setPlayerGuildId(exiledPlayer, nil)
end
-- booted
sendGuildMessage(guid, {
messageType = "member_data";
memberData = nil;
sender = player.Name;
senderId = player.userId;
notice = notice;
})
guildDataUpdated(guid, guildData)
return true, "Successfully exiled player."
else
return false, "Failed to exile: "..reason
end
end
local function abandonGuild(player, guid)
local success, problem = pcall(function()
setPlayerGuildId(player, nil)
local guildDataStore = game:GetService("DataStoreService"):GetDataStore("guild", guid)
local guildData = guildDataStore:RemoveAsync("guildData")
guildNameStore:RemoveAsync(guildData.name)
end)
return success, problem
end
local function playerRequest_leaveMyGuild(player, confirmAbandon)
local guid = player.guildId.Value
local guildPlayerData = getGuildPlayerData(player, guid)
if guildPlayerData == nil then
return false, "Could not find your guild."
end
local guildData = getGuildData(player, guid)
if guildData == nil then
return false, "Guild data not found"
end
if guildPlayerData.rank == "leader" then
local memberCount = 0
for _, member in pairs(guildData.members) do
memberCount = memberCount + 1
end
if memberCount > 1 then
return false, "If you wish to abandon your guild, you must first exile all other members."
end
if confirmAbandon then
return abandonGuild(player, guid)
end
return false, "confirmAbandon"
end
local exiledUserId = player.userId
local guildMembers = guildData.members
guildMembers[tostring(exiledUserId)] = nil;
local notice = {Text = player.Name .. " has left the guild."; Font = "SourceSansBold"; Color = {r=161, g=132, b=194}}
local success, reason = modifyGuildDataValue(player, guid, "set", "members", guildMembers, true, notice)
if success then
-- alert other servers that the guild has been left
sendGuildMessage(guid, {
messageType = "member_data";
memberData = nil;
sender = player.Name;
senderId = player.userId;
notice = notice;
})
setPlayerGuildId(player, nil)
guildDataUpdated(guid, guildData)
return true, "Successfully left the guild."
else
return false, "Failed to leave: "..reason
end
end
local function playerRequest_changeUserIdRankValue(player, rankedUserId, newRankValue)
local guid = player.guildId.Value
local guildPlayerData = getGuildPlayerData(player, guid)
if not guildPlayerData then
return false, "Could not find your guild."
end
local guildData = getGuildData(player, guid)
if guildData == nil then
return false, "Guild data not found"
end
local rankedGuildPlayerData = guildData.members[tostring(rankedUserId)]
if rankedGuildPlayerData == nil then
return false, "Player is not in your guild."
end
local rankedUsername = rankedGuildPlayerData.name
local playerGuildRankValue = guildRankValues[guildPlayerData.rank]
local rankedGuildRankValue = guildRankValues[rankedGuildPlayerData.rank]
local isChangingLeaders = (playerGuildRankValue == 4) and (newRankValue == 4)
if (newRankValue >= playerGuildRankValue) and (not isChangingLeaders) then
return false, "You cannot rank someone to a rank that is not beneath you."
elseif rankedGuildRankValue >= playerGuildRankValue then
return false, "You cannot change the rank of someone who does not rank beneath you."
end
local newRank = "member"
for rankName, rankValue in pairs(guildRankValues) do
if newRankValue == rankValue then
newRank = rankName
end
end
local guildMembers = guildData.members
guildMembers[tostring(rankedUserId)].rank = newRank
local notice = {Text = rankedUsername .. "'s rank has been changed to "..newRank.." by "..player.Name.."."; Font = "SourceSansBold"; Color = {r=161, g=132, b=194}}
if isChangingLeaders then
guildMembers[tostring(player.UserId)].rank = 3
notice.Text = player.Name.." has stepped down as leader, appointing "..rankedUsername.." as the new leader!"
end
local success, reason = modifyGuildDataValue(player, guid, "set", "members", guildMembers, true, notice)
if success then
guildDataUpdated(guid, guildData)
-- tell other servers about the promotion
sendGuildMessage(guid, {
messageType = "member_data";
memberData = guildMembers[tostring(rankedUserId)];
sender = player.Name;
senderId = player.userId;
notice = notice;
})
return true, "Successfully ranked player."
else
return false, "Failed to rank: "..reason
end
end
local guildHallPlaceId = 4653017449
local hallLocationsByPlaceId = {
[2064647391] = "mushtown",
[2119298605] = "nilgarf",
[2470481225] = "warriorStronghold",
[3112029149] = "treeOfLife",
[2546689567] = "portFidelio",
}
local properNamesByHallLocation = {
["mushtown"] = "Mushtown",
["nilgarf"] = "Nilgarf",
["warriorStronghold"] = "Warrior Stronghold",
["treeOfLife"] = "Tree of Life",
["portFidelio"] = "Port Fidelio",
}
local function getHallLocationFromPlaceId()
return hallLocationsByPlaceId[game.PlaceId]
end
local function getPlaceIdFromHallLocation(hallLocationIn)
for placeId, hallLocation in pairs(hallLocationsByPlaceId) do
if hallLocation == hallLocationIn then
return placeId
end
end
return nil
end
local function playerRequest_getPlaceIdFromHallLocation(player, hallLocation)
return getPlaceIdFromHallLocation(hallLocation)
end
local function resetGuildHallServer()
-- TODO: broadcast a message which boots everyone from guild hall in event of update, move, etc.
end
local function changeGuildHallLocation(player)
local guildId = player:FindFirstChild("guildId")
if not guildId then
return false, "No guildId."
end
local guid = guildId.Value
local guildData = getGuildData(player, guid)
if not guildData then
return false, "No guildData."
end
local memberData = getGuildPlayerData(player, guid)
if not memberData then
return false, "No memberData."
end
if memberData.rank ~= "leader" then
return false, "Not leader."
end
local hallLocation = getHallLocationFromPlaceId()
if not hallLocation then
return false, "No guild hall at this location."
end
local notice
if guildData.hallLocation then
notice = {Text = "The Guild Hall has been moved from "..properNamesByHallLocation[guildData.hallLocation].." to "..properNamesByHallLocation[hallLocation].."!"}
else
notice = {Text = "The Guild Hall has been established at "..properNamesByHallLocation[hallLocation].."!"}
end
notice.Font = "SourceSansBold"
notice.Color = {r = 161, g = 132, b = 194}
local hallServerId = TeleportService:ReserveServer(guildHallPlaceId)
local serverIdSuccess, serverIdProblem = modifyGuildDataValue(player, guid, "set", "hallServerId", hallServerId, false)
if not serverIdSuccess then
return false, serverIdProblem
end
local success, reason = modifyGuildDataValue(player, guid, "set", "hallLocation", hallLocation, false, notice)
if success then
sendGuildMessage(guid, {
messageType = "data_update",
key = "hallLocation",
value = hallLocation,
sender = player.Name,
senderId = player.userId,
notice = notice,
})
resetGuildHallServer()
return true, ""
else
return false, reason
end
end
local function getGuildUpgradeCost(player)
local guildId = player:FindFirstChild("guildId")
if not guildId then
return nil, "No guildId."
end
local guid = guildId.Value
local guildData = getGuildData(player, guid)
if not guildData then
return nil, "No guildData."
end
local nextLevel = guildData.level + 1
if guildPurchases.level[nextLevel] then
return guildPurchases.level[nextLevel].cost, ""
else
return nil, "No next level."
end
end
local function upgradeGuild(player)
if (game.PlaceId ~= 2546689567) and (game.PlaceId ~= 2061558182) and (game.PlaceId ~= 3372071669) then
return false, "Not in Port Fidelio."
end
local guildId = player:FindFirstChild("guildId")
if not guildId then
return false, "No guildId."
end
local guid = guildId.Value
local guildData = getGuildData(player, guid)
if not guildData then
return false, "No guildData."
end
local memberData = getGuildPlayerData(player, guid)
if not memberData then
return false, "No memberData."
end
if memberData.rank ~= "leader" then
return false, "Not leader."
end
local upgradeCost, reason = getGuildUpgradeCost(player)
if not upgradeCost then
return false, reason
end
if guildData.bank < upgradeCost then
return false, "Not enough funds in guild bank."
end
-- customized logic here since I don't want to do two separate update calls
local guildDataStore = game:GetService("DataStoreService"):GetDataStore("guild", guid)
local dataSuccess, dataError = pcall(function()
guildDataStore:UpdateAsync("guildData", function(data)
if not data then return end
if not data.level then return end
if not data.bank then return end
if data.bank < upgradeCost then return end
data.bank = data.bank - upgradeCost
data.level = data.level + 1
guildDataUpdated(guid, data)
return data
end)
end)
if not dataSuccess then
network:invoke("reportError", player, "warning", "Failed to upgrade guild level: "..dataError)
return false, "DataStore error"
end
return true, ""
end
local function donateToGuild(player, amount)
if typeof(amount) ~= "number" then
return false, "Trying to donate not a number."
end
amount = math.floor(amount)
if amount < 0 then
return false, "Trying to donate a negative number."
end
local guildId = player:FindFirstChild("guildId")
if not guildId then
return false, "No guildId."
end
local guid = guildId.Value
local guildData = getGuildData(player, guid)
if not guildData then
return false, "No guildData."
end
local playerData = network:invoke("getPlayerData", player)
if not playerData then
return false, "No playerData."
end
if playerData.gold < amount then
return false, "Not enough gold."
end
playerData.nonSerializeData.incrementPlayerData("gold", -amount, "guild:donate")
local success, reason = modifyGuildDataValue(player, guid, "increment", "bank", amount, true)
if not success then
playerData.nonSerializeData.incrementPlayerData("gold", amount, "guild:donateFailed")
return false, reason
end
return true, ""
end
local function teleportPlayersToGuildHall(players, guid)
local leadPlayer = players[1]
local guildData = getGuildData(leadPlayer, guid)
if not guildData then
return false, "No guildData."
end
local hallServerId = guildData.hallServerId
if not hallServerId then
return false, "No hallServerId."
end
network:invoke("teleportPlayersToReserveServer", players, guildHallPlaceId, guid, nil, nil, hallServerId)
return true, ""
end
local function playerRequest_teleportToGuildHall(player)
local guildId = player:FindFirstChild("guildId")
if not guildId then
return false, "No guildId."
end
local guid = guildId.Value
if guid == "" then
return false, "No guild."
end
local partyData = network:invoke("getPartyDataByPlayer", player)
if partyData then
-- teleport the party
local players = {}
local leader
for _, memberInfo in pairs(partyData.members) do
local member = memberInfo.player
if memberInfo.isLeader then
leader = member
else
table.insert(players, member)
end
end
if player ~= leader then
return false, "Only the party leader may teleport everyone to a guild hall."
end
table.insert(players, 1, leader)
teleportPlayersToGuildHall(players, guid)
else
-- teleport the individual
teleportPlayersToGuildHall({player}, guid)
end
end
local function expelPlayerHelper(player, target)
network:fireAllClients("signal_alertChatMessage", {
Text = target.Name.." has been expelled by "..player.Name,
Font = Enum.Font.SourceSansBold,
Color = Color3.fromRGB(255, 127, 127),
})
network:invoke("teleportPlayer", target, game.ReplicatedStorage.lastLocationOverride.Value, "guildHall")
end
local function expelPlayer(player, target)
if (game.PlaceId ~= guildHallPlaceId) and (game.PlaceId ~= 2061558182) then
return false, "This command can only be used in the guild hall."
end
local guildId = game.ReplicatedStorage:FindFirstChild("guildId")
if guildId then
guildId = guildId.Value
else
if game.PlaceId == 2061558182 then
guildId = "73370A17-4486-4E7D-AD49-4597D93C52B0"
else
return false, "Couldn't find guildId."
end
end
local playerGuildId = player:FindFirstChild("guildId")
local targetGuildId = target:FindFirstChild("guildId")
if (not playerGuildId) then
return false, "You are not a member of a guild."
else
if playerGuildId.Value ~= guildId then
return false, "You are not a member of this guild."
end
if (not targetGuildId) then
expelPlayerHelper(player, target)
return true, ""
else
if targetGuildId.Value ~= guildId then
expelPlayerHelper(player, target)
return true, ""
else
local playerData = getGuildPlayerData(player, guildId)
local targetData = getGuildPlayerData(target, guildId)
if (not playerData) or (not targetData) or (not playerData.rank) or (not targetData.rank) then
return false, "There was an issue with player data."
end
local playerRank = getRankNumberFromRank(playerData.rank)
local targetRank = getRankNumberFromRank(targetData.rank)
if playerRank <= targetRank then
return false, "You must be a higher rank than your target in order to expel them."
else
expelPlayerHelper(player, target)
return true, ""
end
end
end
end
end
local function leaveGuildHall(player)
network:invoke("teleportPlayer", player, game.ReplicatedStorage.lastLocationOverride.Value, "guildHall")
end
function module.init(Modules)
network = Modules.network
network:connect("playerDataLoaded", "Event", onPlayerLoaded)
network:create("signal_guildDataUpdated", "RemoteEvent")
network:create("sendGuildMessage", "BindableFunction", "OnInvoke", sendGuildMessage)
network:create("sendGuildChat", "BindableFunction", "OnInvoke", sendGuildChat)
network:create("getGuildData", "BindableFunction", "OnInvoke", getGuildData)
network:create("playerRequest_getGuildData", "RemoteFunction", "OnServerInvoke", playerRequest_getGuildData)
network:create("getGuildMemberData", "BindableFunction", "OnInvoke", getGuildPlayerData)
network:create("playerRequest_getGuildMemberData", "RemoteFunction", "OnServerInvoke", playerRequest_getGuildMemberData)
network:create("playerRequest_createGuild", "RemoteFunction", "OnServerInvoke", playerRequest_createGuild)
network:create("serverPrompt_playerInvitedToServer", "RemoteFunction")
network:create("playerRequest_invitePlayerToGuild", "RemoteFunction", "OnServerInvoke", playerRequest_invitePlayerToGuild)
network:create("playerRequest_exileUserIdFromGuild","RemoteFunction","OnServerInvoke", playerRequest_exileUserIdFromGuild)
network:create("playerRequest_leaveMyGuild","RemoteFunction","OnServerInvoke",playerRequest_leaveMyGuild)
network:create("playerRequest_changeUserIdRankValue", "RemoteFunction", "OnServerInvoke", playerRequest_changeUserIdRankValue)
network:create("playerRequest_getHallLocationFromPlaceId", "RemoteFunction", "OnServerInvoke", getHallLocationFromPlaceId)
network:create("playerRequest_getPlaceIdFromHallLocation", "RemoteFunction", "OnServerInvoke", playerRequest_getPlaceIdFromHallLocation)
network:create("getPlaceIdFromHallLocation", "BindableFunction", "OnInvoke", getPlaceIdFromHallLocation)
network:create("playerRequest_changeGuildHallLocation", "RemoteFunction", "OnServerInvoke", changeGuildHallLocation)
network:create("playerRequest_getGuildUpgradeCost", "RemoteFunction", "OnServerInvoke", getGuildUpgradeCost)
network:create("playerRequest_upgradeGuild", "RemoteFunction", "OnServerInvoke", upgradeGuild)
network:create("playerRequest_donateToGuild", "RemoteFunction", "OnServerInvoke", donateToGuild)
network:create("teleportPlayersToGuildHall", "BindableFunction", "OnInvoke", teleportPlayersToGuildHall)
network:create("playerRequest_teleportToGuildHall", "RemoteFunction", "OnServerInvoke", playerRequest_teleportToGuildHall)
network:create("playerRequest_expelPlayer", "RemoteFunction", "OnServerInvoke", expelPlayer)
network:create("playerRequest_leaveGuildHall", "RemoteFunction", "OnServerInvoke", leaveGuildHall)
network:create("getRankNumberFromRank", "BindableFunction", "OnInvoke", getRankNumberFromRank)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_interact.lua
================================================
local module = {}
local CollectionService = game:GetService("CollectionService")
local network
local function attackInteractionSoundPlayed(player, part, soundName)
if not player then return end
if not player.Character then return end
if not player.Character.PrimaryPart then return end
local distance = (part.Position - player.Character.PrimaryPart.Position).Magnitude
local range = 4 + math.max(part.Size.X, part.Size.Y, part.Size.Z)
if distance < range then
network:fireAllClientsExcludingPlayer("attackInteractionSoundPlayed", player, player.Character.PrimaryPart.Position, soundName)
end
end
local function attackInteractionAttackableAttacked(player, part, hitPosition)
if not player then return end
if not player.Character then return end
if not player.Character.PrimaryPart then return end
if not CollectionService:HasTag(part, "attackable") then return end
local attackableModule = part:FindFirstChild("attackableScript")
if not attackableModule then return end
local attackable = require(attackableModule)
local distance = (part.Position - player.Character.PrimaryPart.Position).Magnitude
local range = 4 + math.max(part.Size.X, part.Size.Y, part.Size.Z)
if distance > range then return end
attackable.onAttackedServer(player)
network:fireAllClientsExcludingPlayer("attackInteractionAttackableAttacked", player, player, part, hitPosition)
end
function module.init(Modules)
network = Modules.network
network:create("attackInteractionAttackableAttacked", "RemoteEvent", "OnServerEvent", attackInteractionAttackableAttacked)
network:create("attackInteractionSoundPlayed", "RemoteEvent", "OnServerEvent", attackInteractionSoundPlayed)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_item.lua
================================================
-- manages items, duh
-- author: Polymorphic
-- editor: berezaa
local module = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local network
local placeSetup
local physics
local utilities
local configuration
local economy
local itemLookupContainer = ReplicatedStorage.itemData
local itemLookup = require(itemLookupContainer)
local itemsFolder
local assetFolder = ReplicatedStorage:FindFirstChild("assets")
local itemManfiests = assetFolder.items
local particleStorage = assetFolder.particles
local entityStorage = assetFolder.entities
local function getItemBaseDataFromItemsPart(itemPart)
local itemDataModule = itemLookupContainer:FindFirstChild(itemPart.Name)
if itemDataModule then
return require(itemDataModule)
end
end
local IGNORE_LIST = {}
-- TODO: make important settings like below in a module to be easily sync'd between server and client scripts
local _SERVER_ACQUISITION_RANGE = 10
local itemPickupDebounce = {}
local CONSUMABLE_COOLDOWN_TIME = 1
local playerConsumeCooldownTable = {}
local function processPlayerPickUpItem(player, itemPart, isPickUpFromPet)
if not itemPart or itemPart.Parent ~= itemsFolder then
return false, "item does not exist"
elseif itemPickupDebounce[itemPart] then
return false, "attempt to pick-up an item someone else is picking up"
elseif not itemPart:FindFirstChild("metadata") then
return false, "attempt to pick-up an invalid item"
elseif not utilities.playerCanPickUpItem(player, itemPart, isPickUpFromPet) then
return false, "can't pick-up this item"
end
local itemBaseData = getItemBaseDataFromItemsPart(itemPart)
if itemBaseData then
itemPickupDebounce[itemPart] = true
local metadata
local success, resultMessage, value
if not itemBaseData.autoConsume then
-- request the inventory service to add this item into the inventory
local success2, returnValue = utilities.safeJSONDecode(itemPart.metadata.Value)
if success2 then
metadata = returnValue
success, resultMessage = network:invoke("tradeItemsBetweenPlayerAndNPC", player, {}, 0, {returnValue}, 0, nil, {overrideItemsRecieved = true})
else
metadata = itemBaseData
success, resultMessage = false, "failed to decode metadata"
end
else
-- added physical item part to function for gold
metadata = itemBaseData
success, resultMessage, value = itemBaseData.activationEffect(player, itemPart)
end
-- let the client know if they picked it up
network:fireClient("notifyPlayerPickUpItem", player, metadata, success, value, nil, resultMessage)
if success then
-- fire quest trigger occured
network:fire("questTriggerOccurred", player, "item-collected", {id = itemBaseData.id; amount = 1})
-- remove player from owners
if itemPart:FindFirstChild("owners") then
if itemPart.owners:FindFirstChild(tostring(player.userId)) then
itemPart.owners[tostring(player.userId)]:Destroy()
else
for i, ownerTag in pairs(itemPart.owners:GetChildren()) do
if ownerTag.Value == player then
ownerTag:Destroy()
end
end
end
end
if not itemPart:FindFirstChild("pickupBlacklist") then
local pickupBlacklist = Instance.new("Folder")
pickupBlacklist.Name = "pickupBlacklist"
pickupBlacklist.Parent = itemPart
end
local blacklistTag = Instance.new("BoolValue")
blacklistTag.Name = tostring(player.userId)
blacklistTag.Value = true
blacklistTag.Parent = itemPart.pickupBlacklist
-- destroy the itemPart on the server, can no longer be picked up
if itemPart:FindFirstChild("singleOwnerPickup") or not itemPart:FindFirstChild("owners") or #itemPart.owners:GetChildren() == 0 then
itemPart:Destroy()
elseif itemPart:FindFirstChild("created") and (os.time() - itemPart.created.Value) >= configuration.getConfigurationValue("timeForAnyonePickupItem") then
itemPart:Destroy()
end
end
-- free the item up
itemPickupDebounce[itemPart] = nil
return success, resultMessage, value
end
end
local function playerRequest_pickUpItem(player, itemPart)
if player.Character == nil or player.Character.PrimaryPart == nil or player.Character.PrimaryPart:FindFirstChild("state") == nil or player.Character.PrimaryPart.state.Value == "dead" then
return false, "Invalid player or dead"
end
if not itemPart or not utilities.playerCanPickUpItem(player, itemPart) then
return false, "Unable to pick up"
end
if player.Character and player.Character.PrimaryPart and itemPart and typeof(itemPart) == "Instance" and itemPart:IsDescendantOf(itemsFolder) then
if utilities.magnitude(player.Character.PrimaryPart.Position - itemPart.Position) <= _SERVER_ACQUISITION_RANGE * 1.1 then
return processPlayerPickUpItem(player, itemPart, false)
else
return false, "Too far away"
end
end
return false, "Invalid"
end
local function prepareManifest(physItem)
if physItem:IsA("BasePart") then
-- damiens weld thingie
for _, Child in pairs(physItem:GetChildren()) do
if Child:IsA("BasePart") then
local motor6d = Instance.new("Motor6D")
motor6d.Part0 = physItem
motor6d.Part1 = Child
motor6d.C0 = CFrame.new()
motor6d.C1 = Child.CFrame:toObjectSpace(physItem.CFrame)
motor6d.Parent = Child
Child.CanCollide = false
Child.Anchored = false
end
end
elseif physItem:IsA("Model") then
local primaryPartForPhysItem = physItem.PrimaryPart
-- transform into the structure we know and love!
for _, child in pairs(physItem:GetChildren()) do
if child:IsA("BasePart") and child ~= primaryPartForPhysItem then
local motor6d = Instance.new("Motor6D")
motor6d.Part0 = primaryPartForPhysItem
motor6d.Part1 = child
motor6d.C0 = CFrame.new()
motor6d.C1 = child.CFrame:toObjectSpace(primaryPartForPhysItem.CFrame)
motor6d.Parent = child
child.CanCollide = false
child.Anchored = false
child.Parent = primaryPartForPhysItem
end
end
physItem = primaryPartForPhysItem
end
return physItem
end
local function generateItemManifest(itemDropData, physItem)
local itemBaseData = itemLookup[itemDropData.id]
local itemAsset = itemManfiests:FindFirstChild(itemBaseData.module.Name)
assert(itemAsset, "Item manifest not found for " .. itemBaseData.module.Name)
if physItem then
physItem = prepareManifest(physItem)
else
if itemBaseData.equipmentType == "arrow" then
-- arrows get special treatment
if itemDropData.stacks == 1 then
physItem = itemAsset.manifest:Clone()
physItem.CanCollide = false
physItem.Anchored = false
else
-- todo: how to do multiple quivers?
physItem = assetFolder.entities.ArrowUpperTorso2.quiver:Clone()
physItem.Anchored = false
physItem.CanCollide = false
local arrows = itemDropData.stacks or 0
local arrowParts = math.clamp(math.floor(arrows / configuration.getConfigurationValue("arrowsPerArrowPartVisualization")) + 1, 0, configuration.getConfigurationValue("maxArrowPartsVisualization"))
local degPerRot = 360 / configuration.getConfigurationValue("maxArrowPartsVisualization")
for ai = 1, arrowParts do
local arrow = itemAsset.manifest:Clone()
arrow.CanCollide = false
arrow.Anchored = false
arrow.Parent = physItem
local xRan, yRan = math.random() * 2 - 1, math.random() * 2 - 1
local arrowWeld = Instance.new("Motor6D", physItem)
arrowWeld.Name = "projectionWeld"
arrowWeld.Part0 = physItem
arrowWeld.Part1 = arrow
arrowWeld.C0 = physItem.Attachment.CFrame
arrowWeld.C1 = CFrame.Angles(xRan * math.rad(15), 0, yRan * math.rad(15))-- * CFrame.Angles(0.25 * math.rad(degPerRot * ai), 0, 0.25 * math.rad(degPerRot * ai))
end
end
elseif itemAsset:FindFirstChild("manifest", true) then
physItem = itemAsset:FindFirstChild("manifest", true):Clone()
physItem = prepareManifest(physItem)
elseif itemAsset:FindFirstChild("container") then
local primaryPart = itemAsset.container.PrimaryPart
physItem = primaryPart:Clone()
physItem:ClearAllChildren()
for _, vv in pairs(primaryPart:GetChildren()) do
if vv:IsA("BasePart") then
local part = vv:Clone()
local motor6d = Instance.new("Motor6D")
motor6d.Part0 = part
motor6d.Part1 = physItem
motor6d.C0 = CFrame.new()
motor6d.C1 = primaryPart.CFrame:toObjectSpace(vv.CFrame)
motor6d.Parent = part
part.Anchored = false
part.CanCollide = false
part.Parent = physItem
end
end
for _, v in pairs(itemAsset.container:GetChildren()) do
if v ~= primaryPart then
local part = v:Clone()
local motor6d = Instance.new("Motor6D")
motor6d.Part0 = physItem
motor6d.Part1 = part
motor6d.C0 = CFrame.new()
motor6d.C1 = v.CFrame:toObjectSpace(primaryPart.CFrame)
motor6d.Parent = physItem
part.Anchored = false
part.CanCollide = false
for _, vv in pairs(part:GetChildren()) do
if vv:IsA("BasePart") then
local motor6d = Instance.new("Motor6D")
motor6d.Part0 = part
motor6d.Part1 = vv
motor6d.C0 = CFrame.new()
motor6d.C1 = vv.CFrame:toObjectSpace(part.CFrame)
motor6d.Parent = vv
vv.CanCollide = false
vv.Anchored = false
end
end
part.Parent = physItem
end
end
else
error("attempt to drop item with invalid drop format")
end
end
physItem.Name = itemBaseData.module.Name
physItem.Anchored = false
physItem.CanCollide = false
if itemDropData.id == 1 then
if itemDropData.value >= 1000 then
physItem.Color = Color3.fromRGB(160,160,160)
end
end
local itemMask = assetFolder.entities.itemMask:Clone()
itemMask.Name = "HumanoidRootPart"
itemMask.RootPriority = 100
itemMask.CustomPhysicalProperties = PhysicalProperties.new(5, 1, 0.7)
itemMask.Parent = physItem
itemMask.Size = Vector3.new(physItem.Size.X * 1.1, physItem.Size.Y * 1.1, physItem.Size.Z * 1.1)
local weld = Instance.new("Motor6D")
weld.Part0 = physItem
weld.Part1 = itemMask
weld.Parent = physItem
weld.Name = "MASK_MOTOR"
local dye = itemDropData.dye
if dye then
physItem.Color = Color3.new(physItem.Color.r * dye.r/255, physItem.Color.g * dye.g/255, physItem.Color.b * dye.b/255)
for _, v in pairs(physItem:GetDescendants()) do
if v:IsA("BasePart") then
if dye then
v.Color = Color3.new(v.Color.r * dye.r/255, v.Color.g * dye.g/255, v.Color.b * dye.b/255)
end
end
end
end
local size = (math.sqrt(physItem.Size.X * physItem.Size.Y) + math.sqrt(physItem.Size.Z, physItem.Size.Y))
if not itemDropData.isNotDropping then
local itemInfo = itemBaseData
if (itemInfo.rarity and itemInfo.rarity == "Rare") or (itemInfo.category and itemInfo.category == "equipment") then
itemInfo.soulboundDrop = true
--[[
local attach = script.rareItem.Attachment:Clone()
attach.Parent = physItem
]]
for _, child in pairs(entityStorage.rareItem:GetChildren()) do
child:Clone().Parent = physItem
end
else
local rays = particleStorage.Rays:Clone()
local attach = Instance.new("Attachment")
attach.Axis = Vector3.new(1,0,0)
attach.SecondaryAxis = Vector3.new(0,1,0)
rays.Parent = attach
attach.Parent = physItem
rays.Size = NumberSequence.new(size * 1.3)
local sparkles = particleStorage.Sparkles:Clone()
sparkles.Parent = attach
end
local light = Instance.new("PointLight")
light.Brightness = 1.5
light.Range = 8
light.Parent = itemMask
local attachmentTarget
if physItem:IsA("BasePart") then
attachmentTarget = physItem
elseif physItem:IsA("Model") and (physItem.PrimaryPart or physItem:FindFirstChild("HumanoidRootPart")) then
local primaryPart = physItem.PrimaryPart or physItem:FindFirstChild("HumanoidRootPart")
if primaryPart then
attachmentTarget = primaryPart
end
end
local topAttachment = Instance.new("Attachment", physItem)
topAttachment.Position = Vector3.new(0, itemMask.Size.Y / 2, 0)
local bottomAttachment = Instance.new("Attachment", physItem)
bottomAttachment.Position = Vector3.new(0, -itemMask.Size.Y / 2, 0)
local trail = assetFolder.misc.Trail:Clone()
trail.Attachment0 = topAttachment
trail.Attachment1 = bottomAttachment
trail.Enabled = true
trail.Parent = physItem
end
physItem.Anchored = false
return physItem
end
local function spawnItemOnGround(lootDrop, dropPosition, owners, physItem)
if itemLookup[lootDrop.id] then
-- todo: refactor
physItem = generateItemManifest(lootDrop, physItem)
local hitPart, hitPosition
local tries = 0
local success, val = utilities.safeJSONEncode(lootDrop or {})
local metadataTag = Instance.new("StringValue", physItem)
metadataTag.Name = "metadata"
metadataTag.Value = val
physics:setWholeCollisionGroup(physItem, "items")
while not hitPart and tries < 5 do
tries = tries + 1
local ray = Ray.new(
(CFrame.new(dropPosition)
* CFrame.Angles(0, math.rad(math.random(1, 360)), 0)
* CFrame.Angles(0, 0, math.rad(20))
* CFrame.new(0, 0, -math.random() * 3)).p,
Vector3.new(0, -5, 0)
)
hitPart, hitPosition = workspace:FindPartOnRayWithIgnoreList(ray, IGNORE_LIST)
end
if not hitPosition then
hitPosition = dropPosition
end
physItem.CFrame = CFrame.new(hitPosition) + Vector3.new(0,0.5,0)
physItem.HumanoidRootPart.CFrame = CFrame.new(hitPosition) + Vector3.new(0,0.5,0)
if owners and #owners > 0 then
local ownersFolder = Instance.new("Folder")
ownersFolder.Name = "owners"
for _, owner in pairs(owners) do
if owner and owner.Parent == game.Players then
local ownerTag = Instance.new("ObjectValue")
ownerTag.Name = tostring(owner.userId)
ownerTag.Value = owner
ownerTag.Parent = ownersFolder
end
end
ownersFolder.Parent = physItem
end
local creationTimeTag = Instance.new("IntValue")
creationTimeTag.Name = "created"
creationTimeTag.Value = os.time()
creationTimeTag.Parent = physItem
if itemLookup[lootDrop.id].petsIgnore then
local petsIgnoreTag = Instance.new("BoolValue")
petsIgnoreTag.Name = "petsIgnore"
petsIgnoreTag.Value = true
petsIgnoreTag.Parent = physItem
end
-- 4 minute despawn
game.Debris:AddItem(physItem, 4 * 60)
-- apply value to numbered items like gold
if lootDrop.value then
local valueTag = Instance.new("IntValue")
valueTag.Name = "itemValue"
valueTag.Value = lootDrop.value
valueTag.Parent = physItem
end
if lootDrop.source then
local sourceTag = Instance.new("StringValue")
sourceTag.Name = "itemSource"
sourceTag.Value = lootDrop.source
sourceTag.Parent = physItem
end
physItem.Parent = itemsFolder
return physItem
end
end
local function onActivateItemRequestReceived(player, category, inventorySlotPosition, _itemId, playerInput)
print('recieve funny acitavtion :3')
local playerData = network:invoke("getPlayerData", player)
local inventorySlotData = network:invoke("getPlayerInventorySlotDataByInventorySlotPosition",
player,
category,
inventorySlotPosition
)
if playerData and inventorySlotData and (not _itemId or inventorySlotData.id == _itemId) then
local itemBaseData = itemLookup[inventorySlotData.id]
if itemBaseData.category == "consumable" or itemBaseData.activationEffect ~= nil then
if network:invoke("getIsManifestStunned", player.Character and player.Character.PrimaryPart) then
return false, "User is stunned."
end
local stats = playerData.nonSerializeData.statistics_final
local cooldown = CONSUMABLE_COOLDOWN_TIME * math.clamp(1 - stats.consumeTimeReduction, 0, 1)
if tick() - (playerConsumeCooldownTable[player] or 0) >= cooldown then
playerConsumeCooldownTable[player] = tick()
-- item is consumable -- get rid of a stack of the item then activate it
local worked, status = itemBaseData.activationEffect(player, playerInput)
if worked then
local source = "item:"..itemBaseData.module.Name
if itemBaseData.category ~= "miscellaneous" then
network:invoke("tradeItemsBetweenPlayerAndNPC",
player,
{{id = inventorySlotData.id; position = inventorySlotData.position; stacks = 1}},
0,
{},
0,
source
)
end
else
network:fireClient("signal_alertChatMessage",
player,
{
Text = "Failed to use item: "..status or "no error.";
Font = Enum.Font.SourceSans;
Color = Color3.fromRGB(216, 161, 107)
}
)
end
return worked, status
end
return false, "consume on cooldown"
end
return false, "Item is not activatable."
end
return false, "Failed to activate item."
end
-- todo: sanity check to make sure player is near shop!
local function onPlayerRequest_buyItemFromShop(player, inventorySlotData, stacksBeingRequested, inventoryModule)
if not inventoryModule then warn("Failed to supply inventoryModule") return false end
if not inventoryModule.Parent:IsA("BasePart") then warn("inventoryModule.Parent is not BasePart") return false end
local playerData = network:invoke("getPlayerData", player)
if not playerData then return false end
-- validate the inventory of the shopkeeper to what is trying to be purchased
local costInfo = inventorySlotData.costInfo
local confirmedItemInfo
local itemCost
local shopkeeperInventory = require(inventoryModule) do
local isMatched = false
for _, shopkeeperItemInfo in pairs(shopkeeperInventory) do
local v
local shopCostData
if typeof(shopkeeperItemInfo) == "string" then
v = shopkeeperItemInfo
elseif typeof(shopkeeperItemInfo) == "table" then
v = shopkeeperItemInfo.itemName
shopCostData = shopkeeperItemInfo
end
if itemLookup[v] == itemLookup[inventorySlotData.id] then
if costInfo and costInfo.costType then
if shopCostData and costInfo.costType == shopCostData.costInfo.costType then
itemCost = shopkeeperItemInfo.cost
confirmedItemInfo = shopkeeperItemInfo
isMatched = true
end
elseif not (shopCostData and shopCostData.costInfo and shopCostData.costInfo.costType) then
isMatched = true
end
end
end
if not isMatched then
return false, "could not find item in shop inventory!"
end
end
if typeof(inventorySlotData) ~= "table" or not inventorySlotData.id then return false end
local itemBaseData = itemLookup[inventorySlotData.id]
local clamp_stacksBeingRequested = math.clamp(math.floor(stacksBeingRequested or 1), 1, (itemBaseData.stackSize or 99) * 20)
if player and inventorySlotData and typeof(inventorySlotData) == "table" and stacksBeingRequested and type(stacksBeingRequested) == "number" and stacksBeingRequested >= 1 and stacksBeingRequested == clamp_stacksBeingRequested then
stacksBeingRequested = clamp_stacksBeingRequested
-- kill switch in case we decide we need one
if not itemBaseData or itemBaseData.cantBuy then
return false
end
-- edit: jesus don't put "stacks" in the amount removed.
local source = "shop:"..itemBaseData.module.Name
local success, reason
local confirmedCostInfo
local itemBeingBought = {id = inventorySlotData.id}
if confirmedItemInfo then
confirmedCostInfo = confirmedItemInfo.costInfo
if confirmedItemInfo.attributes then
for attribute, value in pairs(confirmedItemInfo.attributes) do
if not itemBeingBought[attribute] then
itemBeingBought[attribute] = value
end
end
end
end
if confirmedItemInfo and confirmedCostInfo and confirmedCostInfo.costType and itemCost then
if confirmedCostInfo.costType == "item" then
local stacksBeingBought = math.clamp(stacksBeingRequested, 1, itemBaseData.stackSize or 99)
itemBeingBought.stacks = stacksBeingBought
success, reason = network:invoke("tradeItemsBetweenPlayerAndNPC",
player,
{{id = confirmedCostInfo.costId; stacks = itemCost * stacksBeingRequested}},
0,
{itemBeingBought},
0,
source
)
return success, reason
elseif confirmedCostInfo.costType == "ethyr" then
local globalData = playerData.globalData
local stacksBeingBought = math.clamp(stacksBeingRequested, 1, itemBaseData.stackSize or 99)
if globalData.ethyr and globalData.ethyr >= itemCost * stacksBeingBought then
itemBeingBought.stacks = stacksBeingBought
success, reason = network:invoke("tradeItemsBetweenPlayerAndNPC", player, {}, 0, {itemBeingBought}, 0, source)
if success then
globalData.ethyr = globalData.ethyr - itemCost * stacksBeingRequested
playerData.nonSerializeData.setPlayerData("globalData", globalData)
spawn(function()
network:invoke("reportCurrency", player, "ethyr", - itemCost * stacksBeingRequested, source)
end)
end
end
return success, reason
end
end
local coinCostReduction = 1 - math.clamp(playerData.nonSerializeData.statistics_final.merchantCostReduction, 0, 1)
itemBeingBought.stacks = stacksBeingRequested
success, reason = network:invoke("tradeItemsBetweenPlayerAndNPC",
player,
{},
math.clamp((itemCost or itemBaseData.buyValue or 1) * coinCostReduction, 1, math.huge) * stacksBeingRequested,
{itemBeingBought},
0,
source
)
return success, reason
end
return false, "Failed to purchase item."
end
-- only will sell up to the stack size of an item
local function onPlayerRequest_sellItem(player, unsafeInventorySlotData, stacksToRemove)
if not player then return false end
local playerData = network:invoke("getPlayerData", player)
if not playerData then return false end
local inventorySlotData = nil
for _, slotData in pairs(playerData.inventory) do
if
slotData.position == unsafeInventorySlotData.position and
slotData.id == unsafeInventorySlotData.id
then
inventorySlotData = slotData
break
end
end
if inventorySlotData and typeof(inventorySlotData) == "table" and stacksToRemove == stacksToRemove and stacksToRemove and type(stacksToRemove) == "number" then
stacksToRemove = math.floor(math.clamp(stacksToRemove or 1, 1, 999))
local itemBaseData = itemLookup[inventorySlotData.id]
-- kill switch in case we decide we need one
if not itemBaseData or itemBaseData.cantSell then
return false
elseif itemBaseData and not itemBaseData.canStack then
stacksToRemove = 1
end
-- edit: jesus don't put "stacks" in the amount removed.
local source = "shop:"..itemBaseData.module.Name
local sellValue = economy.getSellValue(itemBaseData, inventorySlotData)
local success = network:invoke("tradeItemsBetweenPlayerAndNPC",
player,
{{
id = inventorySlotData.id;
position = inventorySlotData.position;
stacks = stacksToRemove
}},
0,
{},
(sellValue) * stacksToRemove,
source
)
if success and inventorySlotData.id == 138 then
-- xero's tablet. fail treasure hunt quest
network:fire("playerFailedQuest", player, 10)
end
return success
end
return false, "Failed to sell item."
end
local function applyPotionStatusEffectToEntityManifest_server(entityManifest, healthToRestore, manaToRestore, sourceType, sourceId)
print("POTPOTPOTPOTPOTPOTPOTPOTPOTPOTPOTPOT")
print(entityManifest, healthToRestore,manaToRestore,sourceType, sourceId)
if healthToRestore and healthToRestore > 0 then
utilities.healEntity(entityManifest, entityManifest, healthToRestore)
utilities.playSound("item_heal", entityManifest)
end
if manaToRestore and manaToRestore > 0 then
entityManifest.mana.Value = math.min(entityManifest.mana.Value + manaToRestore, entityManifest.maxMana.Value)
utilities.playSound("item_mana", entityManifest)
end
return true
end
local function onPlayerAdded(player)
playerConsumeCooldownTable[player] = 0
end
local function onPlayerRemoving(player)
playerConsumeCooldownTable[player] = nil
end
local function playerRequest_dropItem(player, inventorySlotData)
local playerData = network:invoke("getPlayerData", player)
if player:FindFirstChild("DataSaveFailed") then
network:fireClient("alertPlayerNotification", player, {
text = "Cannot drop items during DataStore outage.";
textColor3 = Color3.fromRGB(255, 57, 60)
})
return false, "This feature is temporarily disabled"
end
if player:FindFirstChild("DataLoaded") == nil then
return false
end
if not configuration.getConfigurationValue("isTradingEnabled", player) then
return false
end
if playerData and player and player.Character and player.Character.PrimaryPart then
local isInInventory, pos = false, nil
for i, trueInventorySlotData in pairs(playerData.inventory) do
if trueInventorySlotData.id == inventorySlotData.id and trueInventorySlotData.position == inventorySlotData.position then
isInInventory = true
pos = i
end
end
if isInInventory then
local trueMetadata = table.remove(playerData.inventory, pos)
local itemBaseData = itemLookup[trueMetadata.id]
local drop
if not (trueMetadata.soulbound or itemBaseData.soulbound) then
drop = network:invoke("spawnItemOnGround",
trueMetadata,
player.Character.PrimaryPart.Position + player.Character.PrimaryPart.CFrame.lookVector * 5,
nil
)
end
if drop then
local playerDropSource = Instance.new("NumberValue")
playerDropSource.Name = "playerDropSource"
playerDropSource.Value = player.userId
playerDropSource.Parent = drop
else
table.insert(playerData.inventory, trueMetadata)
end
playerData.nonSerializeData.setPlayerData("inventory", playerData.inventory)
end
end
return false, "invalid player data"
end
function module.init(Modules)
network = Modules.network
placeSetup = Modules.placeSetup
physics = Modules.physics
utilities = Modules.utilities
configuration = Modules.configuration
economy = Modules.economy
IGNORE_LIST = {placeSetup.getPlaceFoldersFolder()}
itemsFolder = placeSetup.getPlaceFolder("items")
game.Players.PlayerAdded:connect(onPlayerAdded)
for _, player in pairs(game.Players:GetPlayers()) do
onPlayerAdded(player)
end
game.Players.PlayerRemoving:connect(onPlayerRemoving)
network:create("itemsRecieved", "RemoteEvent")
network:create("applyPotionStatusEffectToEntityManifest_server", "BindableFunction", "OnInvoke", applyPotionStatusEffectToEntityManifest_server)
network:create("generateItemManifest_server", "BindableFunction", "OnInvoke", generateItemManifest)
network:create("activateItemRequest", "RemoteFunction", "OnServerInvoke", onActivateItemRequestReceived)
network:create("spawnItemOnGround", "BindableFunction", "OnInvoke", spawnItemOnGround)
network:create("playerRequest_buyItemFromShop", "RemoteFunction", "OnServerInvoke", onPlayerRequest_buyItemFromShop)
network:create("playerRequest_sellItemToShop", "RemoteFunction", "OnServerInvoke", onPlayerRequest_sellItem)
network:create("pickUpItemRequest", "RemoteFunction", "OnServerInvoke", playerRequest_pickUpItem)
network:create("playerRequest_pickUpItem", "RemoteFunction", "OnServerInvoke", playerRequest_pickUpItem)
network:create("pickUpItemForPlayer_server", "BindableFunction", "OnInvoke", processPlayerPickUpItem)
network:create("playerRequest_dropItem", "RemoteFunction", "OnServerInvoke", playerRequest_dropItem)
network:create("notifyPlayerPickUpItem", "RemoteEvent")
-- todo: snip snip
network:create("onMonsterDeath", "BindableEvent", "Event", function() end)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_messagingService.lua
================================================
local module = {}
local network
local messagingConnections = {}
local RunService = game:GetService("RunService")
local function reportError(player, Type, Error)
if not RunService:IsRunMode() then
network:invoke("reportError", player, Type, Error)
end
end
local function onPlayerAdded(player)
local iniSuccess, iniErr = pcall(function()
local referrals = Instance.new("IntValue")
referrals.Name = "referrals"
local data = network:invoke("getPlayerData", player)
if data and data.globalData and data.globalData.referrals then
referrals.Value = data.globalData.referrals
end
referrals.Parent = player
local success, err
repeat
success, err = pcall(function()
if messagingConnections[player.Name] then
messagingConnections[player.Name]:Disconnect()
messagingConnections[player.Name] = nil
end
messagingConnections[player.Name] = game:GetService("MessagingService"):SubscribeAsync("user-"..tostring(player.userId), function(message)
local msgSuccess, msgError = pcall(function()
-- REFERRAL!!!
local data = message.Data
local referredUserId = data.referredUserId
local referredUsername = data.referredUsername
if referredUserId then
local playerData = network:invoke("getPlayerData", player)
if playerData then
local globalData = playerData.globalData
if globalData then
if globalData.referredUserIds then
for i,existingReferral in pairs(globalData.referredUserIds) do
if existingReferral == referredUserId then
local Error = "A user ("..player.Name..") attempted to double refer."
network:invoke("reportError", player, "debug", Error)
player:Kick("Attempt to refer an already-referred user")
return false
end
end
end
local msuccess, merr = pcall(function()
game:GetService("MessagingService"):PublishAsync("acceptedReferrals", data.referredUserId)
end)
if msuccess then
network:fireAllClients("signal_alertChatMessage", {Text = "✉️ " .. player.Name .. " just referred ".. (referredUsername or "???") .. "!"; Font = Enum.Font.SourceSansBold; Color = Color3.fromRGB(23, 234, 118)} )
local rewards = {
-- megaphones
{id = 166; stacks = 1};
}
spawn(function()
game.BadgeService:AwardBadge(player.userId,2124469284)
end)
local success = network:invoke("tradeItemsBetweenPlayerAndNPC", player, {}, 0, rewards, 0, "gift:referral", {})
if not success then
if player.Character and player.Character.PrimaryPart then
local gift = network:invoke("spawnItemOnGround", {id = 166}, player.Character.PrimaryPart.Position + player.Character.PrimaryPart.CFrame.lookVector * 2, {player})
end
end
network:invoke("reportAnalyticsEvent",player,"referral:accepted")
globalData.referrals = (globalData.referrals or 0) + 1
globalData.referredUserIds = globalData.referredUserIds or {}
table.insert(globalData.referredUserIds, referredUserId)
playerData.nonSerializeData.setPlayerData("globalData", globalData)
referrals.Value = globalData.referrals
network:fireClient("alertPlayerNotification", player,{
text = "You referred "..(referredUsername or "???").." to Vesteria!";
textColor3 = Color3.new(0,0,0);
backgroundColor3 = Color3.fromRGB(23, 234, 118);
backgroundTransparency = 0;
textStrokeTransparency = 1;
font = Enum.Font.SourceSansBold;
}, 6, "ethyr1" )
end
end
end
end
end)
if not msgSuccess then
reportError(player, "error", "messagingService subscription error: "..msgError)
warn("Subscription error:",msgError)
end
end)
end)
if not success then
reportError(player, "error", "messagingService subscription failed: "..err)
end
wait(15)
until success or player.Parent ~= game.Players
end)
if not iniSuccess then
reportError(player, "error", "Error setting up messagingService: "..iniErr)
end
end
local function onPlayerRemoving(player)
local connections = 0
local removedConnections = 0
for playerName, connection in pairs(messagingConnections) do
connections = connections + 1
if connection and (playerName == player.Name or game.Players:FindFirstChild(playerName) == nil) then
connection:Disconnect()
messagingConnections[playerName] = nil
removedConnections = removedConnections + 1
end
end
end
function module.init(Modules)
network = Modules.network
game.Players.PlayerRemoving:connect(onPlayerRemoving)
network:connect("playerDataLoaded", "Event", onPlayerAdded)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_monster.lua
================================================
--[[
TODO
- ensure attackSpeed and attacking in general properly works
- prevent monsters from stacking on top of each other
> this probably will stem from another issue where the monsters
are thrown up sometimes when they cross terrain, likely due to
roblox physics attempting to prevent the monsters from phasing into
the terrain around it.
-test
--]]
--[[ NOTICE!!!
> a bit of terminology so its not as confusing as it seems
'monster' refers to the in-script class that contains all the monster's
data
'manifest' or 'monster manifest' refers to the server representation
'monster manifest render', 'client entity', 'manifest render' refers
to what the client sees
'spawnRegion' is the individual parts that make up a whole region
'spawnRegionCollection' is a collection of 'spawnRegion'
--]]
-- manages monsters, duh
-- author: Polymorphic
local module = {}
local monsterClass = {}
-- 'true' makes it so when you get too close
-- it will start moving towards you to attack
-- 'false' makes it so players must attack first
monsterClass.isAggressive = true
-- how close before the monster will aggro
-- onto players (aggressionType must be 'aggressive')
monsterClass.aggressionRange = 35
-- how close the monster will try to get near you
-- before attacking
monsterClass.attackRange = 10
-- time in seconds between each attack
monsterClass.attackSpeed = 10
-- if you're within this amount of studs of the monster, itll detect you regardless of if you're in sight
monsterClass.detectionFromOutOfVisionRange = monsterClass.attackRange * 1.5
-- the amount of radians the monster can see from the front part
-- (this is HALF the full vision length, so double this number to get the real cone)
monsterClass.visionAngle = math.rad(75)
-- once aggro'd, the monster must maintain
-- direct sight of you within this range
-- to continue tracking you, else it'll follow you
-- up until the last position it saw of you.
monsterClass.sightRange = 200
-- 'projectile' makes it so when the monster is
-- within attackRange it will start to shoot projectiles
-- at the player
-- 'physical' makes it so when the monster is within attackRange
-- it will do a physical attack
monsterClass.attackType = "physical"
-- default level of monsters
monsterClass.level = 1
-- the furthest distance the monster will
-- follow the player from where it was before the player ran into it
monsterClass.playerFollowDistance = 50
-- how fast does it walk towards you
monsterClass.baseSpeed = 10
-- TODO: make this stuff work (likely involve creating an internal
-- walkspeed class which is the current speed of the monster
-- and update that based on if its bursting, should be easy)
monsterClass.burstSpeed = 9 --
monsterClass.burstDuration = 0 --
monsterClass.burstCooldown = 0 --
local LAST_MONSTER_UPDATE_CYCLE = tick()
local LAST_MONSTER_UPDATE_CYCLE_END = tick()
local httpService = game:GetService("HttpService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local network
local utilities
local physics
local placeSetup
local projectile
local configuration
local events
local monsterLookup = require(ReplicatedStorage.monsterLookup)
local defaultMonsterState = require(ReplicatedStorage.defaultMonsterState)
-- set the metatable up so that values not set personally
-- to each class are redirected to the
monsterClass.__index = monsterClass
local MONSTER_DEATH_TIME = 5
-- how much it scales down the monster's hitbox from the model size
-- todo: make this a monster stat, some monster might have odd dilutions
local MONSTER_HITBOX_DILUTION = 0.96
local MONSTER_SPAWN_CYCLE_TIME = 10
local MONSTER_COLLECTION = {}
local itemLookupContainer = ReplicatedStorage.itemData
local itemLookup = require(itemLookupContainer)
local runService = game:GetService("RunService")
local collectionService = game:GetService("CollectionService")
local spawnRegionCollectionsFolder
local entityManifestCollectionFolder
local entityRenderCollectionFolder
local itemsFolder
local entitiesFolder
local foilage
local MONSTER_RAYCAST_IGNORE_LIST = {}
------------------------
-- INTERNAL FUNCTIONS --
------------------------
local function _isTargetEntityInVisionCone(monster)
local targetEntity = monster.targetEntity or monster.closestEntity
if not targetEntity then return false, "no HRP" end
-- point a cframe in the direction of the part, and get the lookVector
local rLookVector = CFrame.new(monster.manifest.Position, targetEntity.Position).lookVector
-- find out the degree difference in where the part is facing
-- to the vector that points directly to the target part from the main part
local res = math.acos(rLookVector:Dot(monster.manifest.CFrame.lookVector))
-- return true if vectors are 30 degrees off each other at maximum
-- (for either side, giving a cone of 60 degrees)
return res <= monster.visionAngle
end
-- return if the monster's targetPlayer is in it's line of sight
function monsterClass:isTargetEntityInLineOfSight(overrideSightRange, useVisionCone, predictiveTargetEntity)
if self.targetEntity and (self.targetEntityLockType or 0) >= 1 then return true end
local targetEntity = self.targetEntity or self.closestEntity do
if predictiveTargetEntity then
targetEntity = predictiveTargetEntity
end
end
local function wasHit(targetEntity)
if not targetEntity then return false, "no target HRP" end
if useVisionCone and not _isTargetEntityInVisionCone(self) and utilities.magnitude(targetEntity.Position - self.manifest.Position) > self.detectionFromOutOfVisionRange then
return false, "vision cone fail"
end
if targetEntity:FindFirstChild("isStealthed") then return false, "player stealthed" end
local monsterPosition = self.manifest.Position
local dir = targetEntity.Position - monsterPosition
local ray = Ray.new(
monsterPosition,
dir.unit * math.min(dir.magnitude, overrideSightRange and overrideSightRange or self.sightRange)
)
local hitPart, hitPosition = projectile.raycast(
ray,
{spawnRegionCollectionsFolder, entityManifestCollectionFolder, entityRenderCollectionFolder, itemsFolder, entitiesFolder, foilage}
)
-- return if the hitPart is nil (nothing obstructing) or if the hitPart is
-- a descendant of the target character (this will include hats and tools)
return
hitPart == nil
or hitPart == targetEntity, tostring(hitPart) .. "was hit"
end
if targetEntity then
if wasHit(targetEntity) then
self.closestEntity = targetEntity
self.targetEntity = targetEntity
return true
end
end
local aggressionRange = self.aggressionRange
for i, targetEntity in pairs(self.nearbyTargets) do
if utilities.magnitude(targetEntity.Position - self.manifest.Position) <= aggressionRange then
if wasHit(targetEntity) then
self.closestEntity = targetEntity
self.targetEntity = targetEntity
return true
end
end
end
end
-- this functions tells the monsters if the target entity is still good to follow around
function monsterClass:isTargetEntityValid()
end
local function purgeNumericKeys(t)
local keysToPurge = {}
for key, value in pairs(t) do
if typeof(key) ~= "string" then
table.insert(keysToPurge, key)
end
if typeof(value) == "table" then
purgeNumericKeys(value)
end
end
for _, key in pairs(keysToPurge) do
local value = t[key]
t[key] = nil
t[tostring(key)] = value
end
end
local function _getMonsterByManifest(monsterManifest, specificProperty)
for i, monster in pairs(MONSTER_COLLECTION) do
if monster.manifest == monsterManifest then
if specificProperty then
return monster[specificProperty]
else
return monster
end
end
end
return nil
end
local function _getMonsterCountInSpawnRegionCollection(spawnRegionCollection)
local count = 0
for i, monster in pairs(MONSTER_COLLECTION) do
if monster.__SPAWN_REGION_COLLECTION == spawnRegionCollection then
count = count + 1
end
end
return count
end
local function _getSpawnRegionFromSpawnRegionCollection(spawnRegionCollection)
local weightTable = {}
for i, spawnRegion in pairs(spawnRegionCollection:GetChildren()) do
if spawnRegion:IsA("BasePart") then
table.insert(weightTable, {
spawnRegion = spawnRegion;
-- weight based on the volume of the spawnRegion
-- ignoring the Y axis, that could be extremely variable
selectionWeight = math.floor(spawnRegion.Size.X * spawnRegion.Size.Z + 0.5);
})
end
end
return utilities.selectFromWeightTable(weightTable)
end
local spawnPositionGenerator = Random.new()
local function _getPositionFromSpawnRegion(spawnRegion)
local hitPart, hitPosition
for i=1,5 do
local ray = Ray.new(
(spawnRegion.CFrame * CFrame.new(
0.9 * spawnPositionGenerator:NextInteger(-spawnRegion.Size.X / 2, spawnRegion.Size.X / 2),
spawnRegion.Size.Y / 2,
0.9 * spawnPositionGenerator:NextInteger(-spawnRegion.Size.Z / 2, spawnRegion.Size.Z / 2)
)).p,
Vector3.new(0, -spawnRegion.Size.Y, 0)
)
hitPart, hitPosition = workspace:FindPartOnRayWithIgnoreList(ray, MONSTER_RAYCAST_IGNORE_LIST)
if hitPart then
break
end
end
return hitPosition
end
-- Used to figure out how players are spread out around the monster
-- Basically just the magnitude of the average of a bunch of directional unit vectors
-- Includes other useful data like a vector aimed at the point of highest density
-- Used for attack logic that depends on how players are spread out
local function getEntityDensity(monster)
local directionVector = Vector3.new()
local avgDistance = 0
local monsterPos = monster.manifest.Position
local entities = monster.nearbyTargets
for _, entity in pairs (entities) do
local entityPos = entity.position
local monsterToPlayerCf = CFrame.new(monsterPos, Vector3.new(entityPos.X, monsterPos.Y, entityPos.Z))
directionVector = directionVector + monsterToPlayerCf.LookVector
avgDistance = avgDistance + (monsterPos - entityPos).Magnitude
end
directionVector = directionVector / #entities
-- As density approaches 0, players are more evenly spread out
-- As density approaches 1, players are more concentrated in one area
return {
density = directionVector.Magnitude,
direction = directionVector,
distance = avgDistance
}
end
---------------------
-- CLASS FUNCTIONS --
---------------------
function monsterClass:getRoamPositionInSpawnRegion()
return _getPositionFromSpawnRegion(self.__SPAWN_REGION)
end
--[[
dropItem(
dropInformation = {
lootDropData = {id = 1}; -- can include dye, attribute, etc
dropPosition = Vector3.new();
itemOwners = {players}
},
physItem = nil,
lootMulti = 1
)
]]--
local randoGen = Random.new()
function monsterClass:dropItem(dropInformation, physItem, lootMulti)
physItem = physItem or nil
lootMulti = lootMulti or 1
local item = network:invoke(
"spawnItemOnGround",
dropInformation.lootDropData,
dropInformation.dropPosition,
dropInformation.itemOwners,
physItem
)
if item == nil then return false end
-- monster idol
if dropInformation.lootDropData.id == 181 then
local monsterNameTag = Instance.new("StringValue")
monsterNameTag.Name = "monsterName"
monsterNameTag.Value = self.module.Name
monsterNameTag.Parent = item
end
local velo = Vector3.new((randoGen:NextNumber() - 0.5) * 24, (2 + randoGen:NextNumber()) * 30, (randoGen:NextNumber() - 0.5) * 24)
velo = velo * (1 + ((lootMulti - 1) / 27))
if item:IsA("BasePart") then
item.Velocity = velo
elseif item:IsA("Model") and (item.PrimaryPart or item:FindFirstChild("HumanoidRootPart")) then
local primaryPart = item.PrimaryPart or item:FindFirstChild("HumanoidRootPart")
if primaryPart then
primaryPart.Velocity = velo
end
end
return true, item
end
-- internally sets targetPlayer (player monster is following)
-- targetEntityLockType
-- 0 = regular behaviour
-- 1 = soft lock, damage will allow targetentity to swap
-- 2 = semi-soft lock, only damage will allow TargetEntity to be set and manually setting it. will never just roam, will always default to defaulttargetentity
-- 3 = hard lock, nothing but manually setting target entity will allow targetEntity to be set
function monsterClass:setTargetEntity(targetEntity, resetDueToDeath, targetEntitySetSource, targetEntityLockType)
if resetDueToDeath then
self.closestEntity = nil
end
self.targetEntityLockType = self.targetEntityLockType or 0
if self.targetEntityLockType >= 3 and self.targetEntityLockType > (targetEntityLockType or 0) then
return false
end
if self.targetEntityLockType <= 2 and targetEntityLockType and targetEntityLockType >= 2 then
self.defaultTargetEntity = targetEntity
end
if not targetEntity and self.defaultTargetEntity then
self.targetEntity = self.defaultTargetEntity
else
self.targetEntity = targetEntity
end
self.targetEntitySetSource = targetEntitySetSource or nil
if targetEntityLockType then
self.targetEntityLockType = targetEntityLockType
end
-- update objectValue for target
self.manifest.targetEntity.Value = targetEntity
end
-- set the current state of the monster
function monsterClass:setState(newState, newStateData)
if self.state == "dead" then return end
if self.state ~= newState then
local success, newStateDataJSON = utilities.safeJSONEncode(newStateData or {})
self.manifest.stateData.Value = success and newStateDataJSON or "[]"
self.manifest.state.Value = newState
self.state = newState
end
if newState == "dead" then
network:fire("onMonsterDeath", self.manifest)
self.manifest.Anchored = true
self.manifest.CanCollide = false
if self.stateMachine and self.stateMachine.onTransition then
self.stateMachine.onTransition:Destroy()
end
-- step dead once
if self.stateMachine.states.dead and self.stateMachine.states.dead.step then
self.stateMachine.states.dead.step(self)
end
for i, monster in pairs(MONSTER_COLLECTION) do
if monster == self then
-- remove from update queue
table.remove(MONSTER_COLLECTION, i)
for i, event in pairs(monster.__EVENTS) do
event:disconnect()
end
monster.__EVENTS = nil
delay(MONSTER_DEATH_TIME, function()
self.stateMachine.states = nil
wait(30)
-- destroy manifest after death timer.
self.manifest:Destroy()
-- get rid of states
-- clear table.
for i, v in pairs(self) do
self[i] = nil
end
end)
break
end
end
end
end
-- return mass of the manifest
function monsterClass:getMass(monster)
return self.manifest:GetMass()
end
function monsterClass:resetPathfinding()
if self.pathfindingTrigger == "roaming" then
self.__LAST_ROAM_TIME = tick()
end
self.isProcessingPath = false
self.pathfindingTrigger = nil
self.path = nil
self.currentNode = 1
end
-------------------------
-- STATE INSTANTIATION --
-------------------------
local stateMachineFactory = {}
local stateMachine = {}
stateMachine.__index = stateMachine
local function isMonsterStunned(monster)
local manifest = monster.manifest
local guid = utilities.getEntityGUIDByEntityManifest(manifest)
if not guid then return false end
local statuses = network:invoke("getStatusEffectsOnEntityManifestByEntityGUID", guid)
for _, status in pairs(statuses) do
if status.statusEffectType == "stunned" then
return true
end
end
return false
end
local function updateMonsterStateMachine(monster)
-- this should be better, I need more information about how the state machine works
if isMonsterStunned(monster) then
monster.manifest.BodyVelocity.Velocity = Vector3.new()
return
end
local stateMachine = monster.stateMachine
-- dead
if (not stateMachine) or (not stateMachine.states) then
return
end
local currentStateData = stateMachine.states[stateMachine.currentState]
local canSwitchState = (not currentStateData.lockTimeForPreventStateTransition or tick() - currentStateData.__START_TIME > currentStateData.lockTimeForPreventStateTransition)
-- potential stuckage
local nextState, stateData = currentStateData.step(monster, canSwitchState)
if nextState and canSwitchState then
local nextStateData = stateMachine.states[nextState]
if nextStateData and (not currentStateData.lockTimeForLowerTransition or nextStateData.transitionLevel >= currentStateData.transitionLevel or tick() - currentStateData.__START_TIME >= currentStateData.lockTimeForLowerTransition) then
stateMachine.onTransition:Fire(stateMachine.currentState, nextState, stateData)
if currentStateData.verify then
if monster.targetEntity and monster.targetEntity:FindFirstChild("entityType") and monster.targetEntity.entityType.Value == "monster" then
currentStateData.verify(monster)
end
end
nextStateData.__START_TIME = tick()
stateMachine.previousState = stateMachine.currentState
stateMachine.currentState = nextState
end
end
end
function stateMachine:forceStateChange(newState)
if self.states[newState] then
self.onTransition:Fire(self.currentState, newState)
self.states[newState].__START_TIME = tick()
self.previousState = self.currentState
self.currentState = newState
end
end
function stateMachineFactory.create(monster, startingState, states)
local newStateMachine = {}
newStateMachine.states = states and utilities.copyTable(states) or {}
newStateMachine.previousState = "initializing"
newStateMachine.currentState = startingState
newStateMachine.onTransition = Instance.new("BindableEvent")
local defaultMonsterStateMachine = utilities.copyTable(require(ReplicatedStorage.defaultMonsterState))
setmetatable(newStateMachine.states, {
__index = function(_, index)
return defaultMonsterStateMachine.states[index]
end
})
startingState = startingState or "idling"
-- initialize the first state
newStateMachine.states[startingState].__START_TIME = tick()
return setmetatable(newStateMachine, stateMachine)
end
-------------------------
-- CLASS INSTANTIATION --
-------------------------
local baseHitbox do
baseHitbox = Instance.new("Part")
baseHitbox.TopSurface = Enum.SurfaceType.Smooth
baseHitbox.BottomSurface = Enum.SurfaceType.Smooth
baseHitbox.Shape = Enum.PartType.Ball
baseHitbox.Transparency = 1
baseHitbox.CanCollide = true
baseHitbox.Anchored = false
local bodyVelocity = Instance.new("BodyVelocity", baseHitbox)
bodyVelocity.MaxForce = Vector3.new(100000, 0, 100000)
bodyVelocity.Velocity = Vector3.new(0, 0, 0)
-- todo: keep bodyGyro so hitbox is always upright, ie cant
-- be toppled over.
local bodyGyro = Instance.new("BodyGyro", baseHitbox)
bodyGyro.MaxTorque = Vector3.new(1e5, 1e5, 1e5)
bodyGyro.P = 7000
bodyGyro.D = 500
local bodyForce = Instance.new("BodyForce", baseHitbox)
-- todo: convert all these ValueBase classes to be stored within a folder
-- to look cleaner
local stateValue = Instance.new("StringValue", baseHitbox)
stateValue.Name = "state"
stateValue.Value = "sleeping"
local targetEntity = Instance.new("ObjectValue", baseHitbox)
targetEntity.Name = "targetEntity"
targetEntity.Value = nil
local stateData = Instance.new("StringValue", baseHitbox)
stateData.Name = "stateData"
stateData.Value = "[]"
local entityType = Instance.new("StringValue", baseHitbox)
entityType.Name = "entityType"
entityType.Value = "monster"
local entityId = Instance.new("StringValue", baseHitbox)
entityId.Name = "entityId"
entityId.Value = ""
local statusEffects = Instance.new("StringValue", baseHitbox)
statusEffects.Name = "statusEffectsV2"
statusEffects.Value = "{}"
local stanceValue = Instance.new("StringValue", baseHitbox)
stanceValue.Name = "stance"
local maxHealthValue = Instance.new("NumberValue", baseHitbox)
maxHealthValue.Name = "maxHealth"
maxHealthValue.Value = 100
local healthValue = Instance.new("NumberValue", baseHitbox)
healthValue.Name = "health"
healthValue.Value = 100
local levelValue = Instance.new("IntValue", baseHitbox)
levelValue.Name = "level"
levelValue.Value = 1
collectionService:AddTag(baseHitbox, "monster")
end
local blacklistedSpawnRegion = {}
local function setIsSpawnRegionCollectionDisabled(spawnRegionCollection, isDisabled)
blacklistedSpawnRegion[spawnRegionCollection.Name] = isDisabled
end
local function getSpawnRegionsUnderpopulated()
local spawnRegionsCollection_underpopulated = {}
for i, spawnRegionCollection in pairs(spawnRegionCollectionsFolder:GetChildren()) do
if spawnRegionCollection.Name ~= "Pets" and not blacklistedSpawnRegion[spawnRegionCollection.Name] then
local monsterType, monsterSpawnAmount = string.match(spawnRegionCollection.Name, "(.+)-(%d+)")
local isUnderpopulated = false
--local playerDensityFactor = 0.25 + (#game.Players:GetPlayers() / (game.Players.MaxPlayers * 0.75))
--playerDensityFactor = math.min(playerDensityFactor, 1.25)
local playerDensityFactor = 1
if runService:IsRunMode() or runService:IsStudio() then
playerDensityFactor = 0.75
end
-- night time!
if game.Lighting.ClockTime < 5.9 or game.Lighting.ClockTime > 18.6 then
playerDensityFactor = playerDensityFactor * 1.5
end
if monsterType and monsterSpawnAmount then
monsterSpawnAmount = math.ceil(tonumber(monsterSpawnAmount) * playerDensityFactor)
if _getMonsterCountInSpawnRegionCollection(spawnRegionCollection) < monsterSpawnAmount then
isUnderpopulated = true
end
end
if isUnderpopulated then
table.insert(spawnRegionsCollection_underpopulated, {monsterNameToSpawn = monsterType; spawnRegionCollection = spawnRegionCollection})
end
end
end
return spawnRegionsCollection_underpopulated
end
-- fetches the real clientEntity (not a clone!)
local function getRealClientEntity(monsterName)
if monsterLookup[monsterName] then
return monsterLookup[monsterName].entity:Clone()
end
end
local extentsCache = {}
-- generates the server entity of the monster
local function getManifestFromClientEntity(realMonsterClientEntity, baseStats, monsterName)
if realMonsterClientEntity and baseStats then
-- generate hitbox
local hitBox = baseHitbox:Clone()
hitBox.Name = monsterName
local extent = extentsCache[monsterName]
if extent == nil then
extent = realMonsterClientEntity:GetExtentsSize()
extentsCache[monsterName] = extent
end
hitBox.Size = Vector3.new(extent.Y, extent.Y, extent.Y) * (baseStats.hitboxDilution or MONSTER_HITBOX_DILUTION)
return hitBox
else
warn("realMonsterClientEntity", realMonsterClientEntity, "baseStats", baseStats)
end
end
-- returns the baseStats of the monster
-- based on the moduleScript of that name
local function getMonsterBaseStats(monsterName)
return monsterLookup[monsterName]
end
local rand = Random.new(os.time())
local function isNightTime(guard)
-- DANGER!
if game.Lighting.ClockTime < 5.9 or game.Lighting.ClockTime > 18.6 then
return true
end
if not guard and not configuration.getConfigurationValue("doUseNightTimeGiantSpawn") then
-- its always nighttime in derry
return true
elseif guard and not configuration.getConfigurationValue(guard) then
-- you'll float too!
return true
end
return game.Lighting.ClockTime > 18.3 or game.Lighting.ClockTime < 6
end
-- generate new monster class to interface with the
-- control script
local function newMonster(monsterName, spawnLocation, spawnRegionCollection, spawnRegion, _additionalStats, postInitCallback)
if not monsterName then return end
local baseStats = getMonsterBaseStats(monsterName)
local clientEntity = getRealClientEntity(monsterName)
local manifest = getManifestFromClientEntity(clientEntity, baseStats, monsterName)
-- return if manifest is nil (no model for this thing)
if not manifest then warn("no manifest for " .. tostring(monsterName)) return end
-- default baseStats to internal defaults
if not baseStats then baseStats = {} end
if not clientEntity then return false end
-- set name of manifest
manifest.Name = monsterName
manifest.entityId.Value = monsterName
local newMonster = {}
newMonster.monsterName = monsterName
-- internal variables --
newMonster.__LAST_UPDATE = tick()
newMonster.__LAST_ATTACK_TIME = 0
newMonster.__LAST_GRAVITY_RAYCAST_UPDATE = 0
newMonster.__LAST_POSITION_SEEN = nil
newMonster.__LAST_MOVE_DIRECTION = nil
newMonster.__SPAWN_REGION_COLLECTION = spawnRegionCollection
newMonster.__SPAWN_REGION = spawnRegion
newMonster.__LAST_ROAM_TIME = 0
newMonster.__IS_WAITING_FOR_PATH_FINDING = false
newMonster.__EVENTS = {}
newMonster.__MONSTER_EVENTS = baseStats.monsterEvents or {}
newMonster.__STATE_OVERRIDES = baseStats.stateOverrides or {}
-- state variables --
newMonster.currentNode = 1
newMonster.specialsUsed = 0
newMonster.isProcessingPath = false
newMonster.origin = nil
newMonster.state = "sleeping"
newMonster.roamingTargetPosition = nil
newMonster.targetEntity = nil
newMonster.maxHealth = baseStats.maxHealth
newMonster.health = baseStats.maxHealth
newMonster.level = baseStats.level
-- identity variables
newMonster.manifest = manifest
newMonster.clientEntity = getRealClientEntity(monsterName)
-- apply monster baseStats
for baseStat, baseStatValue in pairs(baseStats) do
newMonster[baseStat] = baseStatValue
newMonster["_" .. baseStat] = baseStatValue
end
if (not _additionalStats or not _additionalStats.variation) and isNightTime("doSpawnNightTimeVariants") then
-- todo: merge this draft too
end
if _additionalStats then
local variation = _additionalStats.variation
local variationStats = variation and baseStats.variations[variation]
-- signal to the game what kind of variation this is
newMonster.variation = variation
if variationStats then
for stat, statValue in pairs(variationStats) do
newMonster[stat] = statValue
end
end
for additionalStat, additionalStateValue in pairs(_additionalStats) do
newMonster[additionalStat] = additionalStateValue
end
end
local statesLookup = monsterLookup[monsterName].statesData
local monsterStateMachine = stateMachineFactory.create(newMonster, statesLookup.default, statesLookup.states)
newMonster.stateMachine = monsterStateMachine
local spawnCFrame
if spawnLocation then
if typeof(spawnLocation) == "CFrame" then
spawnCFrame = spawnLocation + Vector3.new(0, newMonster.manifest.Size.Y, 0)
elseif typeof(spawnLocation) == "Vector3" then
spawnCFrame = CFrame.new(spawnLocation) + Vector3.new(0, newMonster.manifest.Size.Y, 0)
elseif typeof(spawnLocation) == "Instance" and spawnLocation:IsA("BasePart") then
spawnCFrame = spawnLocation.CFrame + Vector3.new(0, newMonster.manifest.Size.Y, 0)
end
spawnCFrame = spawnCFrame
else
warn("spawnLocation was nil")
end
spawnCFrame = spawnCFrame * CFrame.Angles(0, math.rad(math.random(1,360)), 0)
if spawnCFrame then
newMonster.manifest.CFrame = spawnCFrame
newMonster.origin = spawnCFrame
else
warn("invalid spawnLocation given")
end
local transitionEvent = monsterStateMachine.onTransition.Event:connect(function(old, new, newStateData)
if manifest:FindFirstChild("ParticleEmitter") then
end
if monsterStateMachine.states[new].execute_server then
spawn(function()
monsterStateMachine.states[new].execute_server(newMonster)
end)
end
newMonster:setState(new, newStateData)
if baseStats.monsterEvents and baseStats.monsterEvents.onStateChanged then
baseStats.monsterEvents.onStateChanged(newMonster, old, new)
end
end)
table.insert(newMonster.__EVENTS, transitionEvent)
-- set the metatable
setmetatable(newMonster, monsterClass)
-- step it up
updateMonsterStateMachine(newMonster)
-- spawn the monster based on the data type of the
-- spawnLocation given
-- attempt to correct for the monsters constantly being thrusted upward
newMonster.healthMulti = newMonster.healthMulti or 1
newMonster.bonusXPMulti = newMonster.bonusXPMulti or 1
newMonster.damageMulti = newMonster.damageMulti or 1
newMonster.goldMulti = newMonster.goldMulti or 1
newMonster.bonusLootMulti = newMonster.bonusLootMulti or 1
newMonster.attackRange = newMonster.attackRange or 1
-- night time!
local nighttime
if game.Lighting.ClockTime < 5.9 or game.Lighting.ClockTime > 18.6 then
if not ( _additionalStats and _additionalStats.level) then
newMonster.nightboosted = true
newMonster.level = newMonster.level + 1
newMonster.healthMulti = newMonster.healthMulti * 1.25
newMonster.damageMulti = newMonster.damageMulti * 1.25
newMonster.bonusXPMulti = newMonster.bonusXPMulti * 1.25
newMonster.bonusLootMulti = newMonster.bonusLootMulti * 1.25
newMonster.goldMulti = newMonster.goldMulti * 1.25
newMonster.aggressionRange = newMonster.aggressionRange * 2
newMonster.attackSpeed = newMonster.attackSpeed * 0.8
newMonster.playerFollowDistance = newMonster.playerFollowDistance * 2
newMonster.baseSpeed = newMonster.baseSpeed * 1.1
newMonster.attackRange = newMonster.attackRange * 1.1
newMonster.detectionFromOutOfVisionRange = newMonster.detectionFromOutOfVisionRange * 2
newMonster.visionAngle = newMonster.visionAngle * 1.25
end
end
if newMonster.gigaGiant then
if not ( _additionalStats and _additionalStats.level) then
newMonster.level = newMonster.level + 3
end
newMonster.scale = 5
newMonster.IS_MONSTER_ENRAGED = true
local monsterManifest = newMonster.manifest
monsterManifest.Size = monsterManifest.Size * 5
local scaleTag = Instance.new("NumberValue")
scaleTag.Name = "monsterScale"
scaleTag.Value = 5
scaleTag.Parent = monsterManifest
newMonster.healthMulti = newMonster.healthMulti * 1000
newMonster.bonusLootMulti = 30
newMonster.bonusXPMulti = newMonster.bonusXPMulti * 500
newMonster.goldMulti = (newMonster.goldMulti or 1) * 3.5
monsterManifest.maxHealth.Value = newMonster.maxHealth
monsterManifest.health.Value = newMonster.health
newMonster.damageMulti = (newMonster.damageMulti or 1) * 5
newMonster.attackRange = (newMonster.attackRange or 0) * 5.1
elseif newMonster.superGiant or ((not newMonster.dontScale and not newMonster.boss) and (isNightTime() and rand:NextInteger(1,7500) == 13)) then
newMonster.scale = 3
newMonster.superGiant = true
newMonster.IS_MONSTER_ENRAGED = true
if not ( _additionalStats and _additionalStats.level) then
newMonster.level = newMonster.level + 2
end
local monsterManifest = newMonster.manifest
monsterManifest.Size = monsterManifest.Size * 3
local scaleTag = Instance.new("NumberValue")
scaleTag.Name = "monsterScale"
scaleTag.Value = 3
scaleTag.Parent = monsterManifest
newMonster.healthMulti = newMonster.healthMulti * 250
newMonster.bonusLootMulti = 20
newMonster.bonusXPMulti = newMonster.bonusXPMulti * 125
newMonster.goldMulti = (newMonster.goldMulti or 1) * 3
monsterManifest.maxHealth.Value = newMonster.maxHealth
monsterManifest.health.Value = newMonster.health
newMonster.damageMulti = (newMonster.damageMulti or 1) * 2.5
newMonster.attackRange = (newMonster.attackRange or 0) * 3
elseif newMonster.giant or ((not newMonster.dontScale and not newMonster.boss) and (isNightTime() and rand:NextInteger(1, 750) == 13)) then -- 1, 1000, 777
newMonster.giant = true
newMonster.IS_MONSTER_ENRAGED = true
if not ( _additionalStats and _additionalStats.level) then
newMonster.level = newMonster.level + 1
end
newMonster.scale = 2
local monsterManifest = newMonster.manifest
monsterManifest.Size = monsterManifest.Size * 2
local scaleTag = Instance.new("NumberValue")
scaleTag.Name = "monsterScale"
scaleTag.Value = 2
scaleTag.Parent = monsterManifest
newMonster.healthMulti = newMonster.healthMulti * 70
newMonster.bonusLootMulti = 10
newMonster.bonusXPMulti = newMonster.bonusXPMulti * 35
newMonster.goldMulti = (newMonster.goldMulti or 1) * 2.5
monsterManifest.maxHealth.Value = newMonster.maxHealth
monsterManifest.health.Value = newMonster.health
newMonster.damageMulti = (newMonster.damageMulti or 1) * 1.75
newMonster.attackRange = (newMonster.attackRange or 0) * 2
elseif newMonster.scale then
local scale = newMonster.scale
local monsterManifest = newMonster.manifest
monsterManifest.Size = monsterManifest.Size * scale
local scaleTag = Instance.new("NumberValue")
scaleTag.Name = "monsterScale"
scaleTag.Value = scale
scaleTag.Parent = monsterManifest
newMonster.attackRange = (newMonster.attackRange or 0) * scale
else
local scale = 1 + rand:NextInteger(-5,5)/100
newMonster.scale = scale
local monsterManifest = newMonster.manifest
monsterManifest.Size = monsterManifest.Size * scale
local scaleTag = Instance.new("NumberValue")
scaleTag.Name = "monsterScale"
scaleTag.Value = scale
scaleTag.Parent = monsterManifest
newMonster.attackRange = (newMonster.attackRange or 0) * scale
end
if newMonster.giant or newMonster.superGiant or newMonster.gigaGiant or newMonster.boss or newMonster.resilient then
local resilientTag = Instance.new("BoolValue")
resilientTag.Name = "resilient"
resilientTag.Value = true
resilientTag.Parent = newMonster.manifest
end
if postInitCallback then
postInitCallback(newMonster)
end
newMonster.maxHealth = newMonster.maxHealth * (newMonster.healthMulti or 1);
newMonster.health = newMonster.maxHealth * (newMonster.healthMulti or 1);
newMonster.damage = newMonster.damage * (newMonster.damageMulti or 1);
manifest.BodyForce.Force = Vector3.new(0, newMonster.floats and 0 or -196.2 * manifest:getMass(), 0)
manifest.BodyVelocity.MaxForce = Vector3.new(100 * manifest:getMass(), newMonster.flies and 100 * manifest:getMass() or 0, 100 * manifest:getMass()) * 100 * (baseStats.velocityMaxForceMultiplier or 1)
manifest.CustomPhysicalProperties = PhysicalProperties.new(newMonster.density or 5, newMonster.friction or 0.4, newMonster.elasticity or 0.2)
if newMonster.specialName then
local nameTag = Instance.new("StringValue")
nameTag.Name = "specialName"
nameTag.Value = newMonster.specialName
nameTag.Parent = manifest
end
if newMonster.notGiant then
local notGiantTag = Instance.new("BoolValue")
notGiantTag.Name = "notGiant"
notGiantTag.Value = true
notGiantTag.Parent = manifest
end
if newMonster.alwaysRendered then
local alwaysRenderedTag = Instance.new("BoolValue")
alwaysRenderedTag.Name = "alwaysRendered"
alwaysRenderedTag.Value = true
alwaysRenderedTag.Parent = manifest
end
if newMonster.isPassive then
local isPassiveTag = Instance.new("BoolValue")
isPassiveTag.Name = "isPassive"
isPassiveTag.Value = true
isPassiveTag.Parent = manifest
end
if newMonster.hideLevel then
local hideLevelTag = Instance.new("BoolValue")
hideLevelTag.Name = "hideLevel"
hideLevelTag.Value = true
hideLevelTag.Parent = manifest
end
if newMonster.isDamageImmune then
local isDamageImmuneTag = Instance.new("BoolValue")
isDamageImmuneTag.Name = "isDamageImmune"
isDamageImmuneTag.Value = true
isDamageImmuneTag.Parent = manifest
end
if newMonster.dye then
local monsterColorVariantTag = Instance.new("Color3Value")
monsterColorVariantTag.Name = "colorVariant"
monsterColorVariantTag.Value = Color3.fromRGB(newMonster.dye.r, newMonster.dye.g, newMonster.dye.b)
monsterColorVariantTag.Parent = manifest
end
if newMonster.isTargetImmune then
local isTargetImmune = Instance.new("BoolValue")
isTargetImmune.Name = "isTargetImmune"
isTargetImmune.Value = true
isTargetImmune.Parent = manifest
end
manifest.maxHealth.Value = newMonster.maxHealth
manifest.health.Value = newMonster.health
manifest.level.Value = newMonster.level
local entityGUIDTag = Instance.new("StringValue")
entityGUIDTag.Name = "entityGUID"
entityGUIDTag.Value = httpService:GenerateGUID(false)
entityGUIDTag.Parent = manifest
manifest.Parent = entityManifestCollectionFolder
manifest:SetNetworkOwner(nil)
if (newMonster.scale >= 1.4) and (not newMonster.notGiant) then
game.CollectionService:AddTag(manifest, "giantEnemy")
end
if baseStats.boss then
manifest.BodyForce.Force = Vector3.new()
local hitPart, hitPos = projectile.raycastForProjectile(
Ray.new(
spawnCFrame.p,
Vector3.new(0, -300, 0)
), {workspace.placeFolders}
)
--[[
local downForce = Instance.new("BodyPosition")
downForce.Name = "bossDownForce"
downForce.Position = Vector3.new(0, hitPos.Y + newMonster.manifest.Size.Y / 2 + 0.5, 0)
downForce.MaxForce = Vector3.new(0, math.huge, 0)
downForce.Parent = manifest
]]
end
-- give ownership to the server if we're using accuratemonsterhitbox
-- we don't want it falling through the ground :smile:
if baseStats.useAccurateMonsterHitbox then
manifest:SetNetworkOwner(nil)
end
-- handle monster variant stuff --
events:fireEventLocal("monsterEntitySpawning", newMonster)
-- register this new monster internally
-- to be updated by the scheduler
table.insert(MONSTER_COLLECTION, newMonster)
return newMonster
end
local function newPet(player, id, petEquipmentData)
if not id then return end
if not itemLookup[id] then return end
local baseStats = itemLookup[id]
local clientEntity = itemLookup[id].entity
local manifest = getManifestFromClientEntity(clientEntity, baseStats, clientEntity.Name)
-- return if manifest is nil (no model for this thing)
if not manifest then warn("no manifest for pet " .. tostring(baseStats.name)) return end
-- default baseStats to internal defaults
if not baseStats then baseStats = {} end
if not clientEntity then return false end
-- name manifest
manifest.Name = baseStats.name
manifest.entityId.Value = id
local newMonster = {}
newMonster.monsterName = baseStats.name
-- internal variables --
newMonster.__LAST_UPDATE = tick()
newMonster.__EVENTS = {}
-- state variables --
newMonster.owner = player
newMonster.isMonsterPet = true
-- identity variables
newMonster.manifest = manifest
newMonster.clientEntity = clientEntity
manifest.BodyForce.Force = Vector3.new(0, -196.2 * manifest:getMass(), 0)
manifest.BodyVelocity.MaxForce = Vector3.new(100 * manifest:getMass(), 0, 100 * manifest:getMass()) * 100 * (baseStats.velocityMaxForceMultiplier or 1)
local statesLookup = baseStats.statesData
local monsterStateMachine = stateMachineFactory.create(newMonster, statesLookup.default, statesLookup.states)
newMonster.stateMachine = monsterStateMachine
manifest.CustomPhysicalProperties = PhysicalProperties.new(newMonster.density or 5, newMonster.friction or 0.4, newMonster.elasticity or 0.2)
local transitionEvent = monsterStateMachine.onTransition.Event:connect(function(old, new, newStateData)
newMonster:setState(new, newStateData)
end)
table.insert(newMonster.__EVENTS, transitionEvent)
-- set the metatable
setmetatable(newMonster, monsterClass)
-- tag it as a pet so clients know..
local monsterPetTag = Instance.new("IntValue")
monsterPetTag.Name = "pet"
monsterPetTag.Value = id
monsterPetTag.Parent = manifest
manifest.entityType.Value = "pet"
physics:setWholeCollisionGroup(manifest, "passthrough")
if petEquipmentData and petEquipmentData.customName then
local monsterNicknameTag = Instance.new("StringValue")
monsterNicknameTag.Name = "nickname"
monsterNicknameTag.Value = petEquipmentData.customName
monsterNicknameTag.Parent = manifest.entityId
end
if petEquipmentData and petEquipmentData.dye then
local monsterColorVariantTag = Instance.new("Color3Value")
monsterColorVariantTag.Name = "colorVariant"
monsterColorVariantTag.Value = Color3.fromRGB(petEquipmentData.dye.r, petEquipmentData.dye.g, petEquipmentData.dye.b)
monsterColorVariantTag.Parent = manifest
end
-- step it up
updateMonsterStateMachine(newMonster)
local entityGUIDTag = Instance.new("StringValue")
entityGUIDTag.Name = "entityGUID"
entityGUIDTag.Value = httpService:GenerateGUID(false)
entityGUIDTag.Parent = manifest
-- parent and set ownership to server
manifest.Parent = entityManifestCollectionFolder
manifest:SetNetworkOwner(nil)
-- register this new monster internally
-- to be updated by the scheduler
table.insert(MONSTER_COLLECTION, newMonster)
return newMonster
end
-- NOTE: This spawns a monster while respecting spawnRegion's
-- spawn limits.
-- TODO: finish this
local function spawnMonster(monsterName, spawnRegionCollection, _spawnPosition, additionalData, postInitCallback)
-- make sure we can spawn the monster here...
if spawnRegionCollection then
local spawnRegionSelection = _getSpawnRegionFromSpawnRegionCollection(spawnRegionCollection)
if spawnRegionSelection then
local spawnRegion = spawnRegionSelection.spawnRegion
additionalData = additionalData or {}
for _,valueObject in pairs(spawnRegionCollection:GetChildren()) do
if valueObject:IsA("ValueBase") then
if valueObject:FindFirstChild("JSON") then
additionalData[valueObject.Name] = game.HttpService:JSONDecode(valueObject.Value)
else
additionalData[valueObject.Name] = valueObject.Value
end
end
end
local spawnPosition = _spawnPosition or _getPositionFromSpawnRegion(spawnRegion)
local newMonster = newMonster(monsterName, spawnPosition, spawnRegionCollection, spawnRegion, additionalData, postInitCallback)
----------------- handle monster stuff
return newMonster
end
elseif _spawnPosition then
local newMonster = newMonster(monsterName, _spawnPosition, nil, nil, additionalData, postInitCallback)
return newMonster
end
end
local function spawnMonsterPet(player, id, ...)
return newPet(player, id, ...)
end
local function onSpawnMonster(monsterName, spawnPosition, spawnRegionCollection, additionalData, postInitCallback)
return spawnMonster(monsterName, spawnRegionCollection, spawnPosition, additionalData, postInitCallback)
end
local function onSpawnMonsterPet(player, id, ...)
local monsterPet = spawnMonsterPet(player, id, ...)
return monsterPet.manifest
end
local monsterIdolModelCache = Instance.new("Folder")
monsterIdolModelCache.Name = "monsterIdolModelCache"
monsterIdolModelCache.Parent = game.ReplicatedStorage
------------------------
-- MAIN PROCESS LOGIC --
------------------------
local function onMonsterRemoved(monsterManifest)
for i, monster in pairs(MONSTER_COLLECTION) do
if monster.manifest == monsterManifest then
table.remove(MONSTER_COLLECTION, i)
if monster.__EVENTS then
for i, event in pairs(monster.__EVENTS) do
event:disconnect()
end
end
if monster.stateMachine and monster.stateMachine.onTransition then
monster.stateMachine.onTransition:Destroy()
end
monster.__EVENTS = nil
-- get rid of states
monster.stateMachine.states = nil
-- clear table.
for i, v in pairs(monster) do
monster[i] = nil
end
break
end
end
end
local rand = Random.new(os.time())
local monsterStateRegistry = {}
local function int__getMonsterStateInformation(monster)
return {
["previous-state"] = monster.stateMachine.previousState;
["current-state"] = monster.stateMachine.currentState;
["name"] = monster.monsterName;
["target-player"] = monster.targetPlayer and monster.targetPlayer.Name;
["closest-player"] = monster.closestPlayer and monster.closestPlayer.Name;
["last-updated"] = math.floor((tick() - monster.__LAST_UPDATE)*1000*100)/100;
}
end
local monsterDebugStateData = {}
monsterDebugStateData.phase = "init"
monsterDebugStateData.monster = ""
monsterDebugStateData.stateBefore = ""
local function onDumpMonsterManagerDebugInformation()
local timeSinceLastMonsterUpdateCycle = math.floor((tick() - LAST_MONSTER_UPDATE_CYCLE)*1000*100)/100
local timeSinceLastMOnsterUpdateCycleEnd = math.floor((tick() - LAST_MONSTER_UPDATE_CYCLE_END)*1000*100)/100
local numberMonsterInMemory = #MONSTER_COLLECTION
warn("MONSTER-MANAGER-DEBUG-DUMP")
warn("MONSTERS IN MEMORY:", numberMonsterInMemory)
warn("TIME SINCE LAST UPDATE CYCLE:", timeSinceLastMonsterUpdateCycle,"ms")
warn("TIME SINCE LAST UPDATE CYCLE:", timeSinceLastMOnsterUpdateCycleEnd,"ms")
warn("CURRENT STATE DATA --", "phase:", monsterDebugStateData.phase, "|", "monster:", monsterDebugStateData.monster, "|", "stateBefore:", monsterDebugStateData.stateBefore)
warn("MONSTER SPECIFIC INFORMATION")
for i, v in pairs(MONSTER_COLLECTION) do
local monsterDebugData = int__getMonsterStateInformation(v)
warn(
monsterDebugData["name"],
"|", "cState:", monsterDebugData["current-state"],
"|", "target:", v.targetPlayer, v.targetEntity, v.targetEntity.Parent,
"|", "last updated:", monsterDebugData["last-updated"], "ms"
)
end
end
local function onRegisterToMonsterState(player, monsterManifest)
local monster = _getMonsterByManifest(monsterManifest)
if monster then
if monsterStateRegistry[player] then
monsterStateRegistry[player]:disconnect()
end
monsterStateRegistry[player] = monster.stateMachine.onTransition.Event:connect(function(old, new)
network:fireClient("monsterStateChanged", player, int__getMonsterStateInformation(monster))
if new == "dead" then
if monsterStateRegistry[player] then
-- monsterStateRegistry[player]:disconnect()
end
end
end)
return true, int__getMonsterStateInformation(monster)
end
return false, nil
end
local function triggerMonsterRewardsSequence(monster, playerWhoKilled, damageData)
if monster.deathRewardsApplied then
return
end
monster.deathRewardsApplied = true
----------------
----------------
----------------
----------------
if playerWhoKilled then
network:fire("playerKilledMonster", playerWhoKilled, monster.manifest, damageData.sourceType, damageData.sourceId)
end
local killer
if playerWhoKilled then
killer = playerWhoKilled.Character and playerWhoKilled.Character.PrimaryPart
end
network:fire("entityKillingBlow", monster.manifest, killer, damageData)
monster.deathRewardsApplied = true
local XPMulti = 1
local lootMulti = 1
local goldMulti = 1
local totalDamageDealt = 0
local damageDealers = 0
for player, damageDealt in pairs(monster.damageTable) do
if player and damageDealt > 1 then
local char = player.Character and player.Character.PrimaryPart
if char and char:FindFirstChild("state") and char.state.Value ~= "dead" then
totalDamageDealt = totalDamageDealt + damageDealt
damageDealers = damageDealers + 1
end
end
end
local playerInvolvementMulti = damageDealers ^ (1/3)
XPMulti = XPMulti * playerInvolvementMulti
goldMulti = goldMulti * playerInvolvementMulti
lootMulti = lootMulti * playerInvolvementMulti
local totalEXP = monster.EXP * XPMulti
local owners = {}
local EXPTable = {}
-- award EXP
for player, damageDealt in pairs(monster.damageTable) do
if player and player.Parent and damageDealt > 0 then
local playerData = network:invoke("getPlayerData", player)
if playerData then
if totalEXP > 0 then
local xp = (totalEXP / damageDealers) * (playerData.nonSerializeData.statistics_final.wisdom or 1)
-- party bonus
local partyData = network:invoke("getPartyDataByPlayer", player)
if partyData and partyData.members then
if #partyData.members >= 6 then
xp = xp * 1.2
elseif #partyData.members > 1 then
xp = xp * 1.1
end
end
EXPTable[player.Name] = xp
xp = math.ceil(xp)
playerData.nonSerializeData.incrementPlayerData("exp",xp)
end
local monsterReference = {}
for i,v in pairs(monster) do
if typeof(i) == "string" and typeof(v) ~= "table" then
monsterReference[i] = v
end
end
network:fire(
"questTriggerOccurred",
player,
"monster-killed",
{["monsterName"] = monster.monsterName; ["monster"] = monsterReference}
)
if monster.module.Name == "Spider Queen" then
spawn(function()
game.BadgeService:AwardBadge(player.userId, 2124454074)
end)
end
table.insert(owners, player)
end
end
end
network:fireAllClients("signal_exp", EXPTable, monster.manifest)
-- drop item loot
-- super hacky todo: dont do this xD
local lootDrops = utilities.copyTable(monster.lootDrops)
local dropPosition = monster.manifest.Position
local monsterName = monster.manifest.Name
if monster.additionalLootDrops then
for _, lootDrop in pairs(monster.additionalLootDrops) do
table.insert(lootDrops, lootDrop)
end
end
if not monster.dropsDisabled then
spawn(function()
for n = 1, math.ceil(lootMulti) do
for _, lootDrop in pairs(lootDrops) do
if n <= math.floor(lootMulti) or rand:NextNumber() < (lootMulti - math.floor(lootMulti)) then
local lootSpawnChance = lootDrop.spawnChance or 0.001
while lootSpawnChance > rand:NextNumber() do
lootSpawnChance = lootSpawnChance - 1
local itemInfo
if lootDrop.itemName then
local real = game.ReplicatedStorage.itemData:FindFirstChild(lootDrop.itemName)
if real then
itemInfo = require(real)
lootDrop.id = itemInfo.id
end
end
if itemInfo == nil then
itemInfo = itemLookup[lootDrop.id]
end
local itemOwners = {}
-- ber: equal loot drop
for _, owner in pairs(owners) do
table.insert(itemOwners, owner)
end
local isUnique = itemInfo and (itemInfo.rarity and (itemInfo.rarity == "Rare" or itemInfo.rarity == "Legendary")) or (itemInfo.category and itemInfo.category == "equipment")
-- ber: equal chance for unique drops
if isUnique then
itemOwners = {itemOwners[rand:NextInteger(1, #itemOwners)]}
end
--assign value for gold
if lootDrop.id == 1 then
local baseMulti = (goldMulti or 1)
lootDrop.value = math.ceil(baseMulti * monster.gold)
lootDrop.source = "monster:"..monsterName:lower()
end
-- remove itemName and other non-important values from being saved
local lootDropClone = utilities.copyTable(lootDrop)
lootDropClone.itemName = nil
lootDropClone.spawnChance = nil
local dropInformation = {
itemOwners = itemOwners;
dropPosition = dropPosition;
lootDropData = lootDropClone;
}
if lootDrop.dropCircumstance then
dropInformation = lootDrop.dropCircumstance(dropInformation, {network = network})
end
local physItem
if lootDrop.id == 181 then
local monsterIdolModel = monsterIdolModelCache:FindFirstChild(monster.module.Name)
if monsterIdolModel == nil then
if game.ReplicatedStorage.monsterLookup:FindFirstChild(monster.module.Name):FindFirstChild("displayEntity") then
monsterIdolModel = game.ReplicatedStorage.monsterLookup:FindFirstChild(monster.module.Name).displayEntity:Clone()
else
monsterIdolModel = game.ReplicatedStorage.monsterLookup:FindFirstChild(monster.module.Name).entity:Clone()
end
local extents = monsterIdolModel:GetExtentsSize()
local min = math.min(extents.X, extents.Y, extents.Z)
local max = math.max(extents.X, extents.Y, extents.Z)
local reductionFactor = 4/(min+max)
utilities.scale(monsterIdolModel, reductionFactor)
local primary = monsterIdolModel.PrimaryPart
primary.Name = "manifest"
for i,v in pairs(monsterIdolModel:GetChildren()) do
if v:IsA("BasePart") then
v.Material = Enum.Material.Glass
v.Reflectance = v.Reflectance + 0.2
v.Transparency = 0
v.Color = Color3.new(0,0,0)
if v ~= primary then
v.Parent = primary
v.Massless = true
end
end
end
local oldModel = monsterIdolModel
primary.Size = Vector3.new(2.2,2.2,2.2)
primary.Name = monster.module.Name
primary.Parent = monsterIdolModelCache
monsterIdolModel = primary
oldModel:Destroy()
end
physItem = monsterIdolModel:Clone()
end
if #dropInformation.itemOwners > 0 then
-- drop the item!
local success = monster:dropItem(dropInformation, physItem, lootMulti)
assert(success, "failed to drop monster loot")
end
end
end
end
end
wait()
end)
end
end
-- give api function to drop rewards for a monster
-- ie, special way to grant exp/rewards without death of monster
function monsterClass:dropRewards(playerWhoKilled, damageData)
if self.deathRewardsApplied then
return false
end
triggerMonsterRewardsSequence(self, playerWhoKilled, damageData)
return true
end
local function onMonsterDamageRequestReceived(player, monsterManifest, damageData)
local monster
for _, monsterToCheck in pairs(MONSTER_COLLECTION) do
if monsterToCheck.manifest == monsterManifest then
monster = monsterToCheck
break
end
end
if monster then
if monster.health > 0 then
local monsterData = monsterLookup[monster.monsterName]
local statesData = monsterData.statesData
-- process status effects that may affect damage here
-- unfortunately we don't have as robust a system for mobs
-- as we do for players, but this will have to suffice for now
-- Davidii wrote this
do
local manifest = monster.manifest
local guid = utilities.getEntityGUIDByEntityManifest(manifest)
if guid then
local statuses = network:invoke("getStatusEffectsOnEntityManifestByEntityGUID", guid)
for _, status in pairs(statuses) do
for stat, value in pairs(status.statusEffectModifier.modifierData or {}) do
if stat == "damageTakenMulti" then
damageData.damage = damageData.damage * (1 + value)
end
end
end
end
end
-- allow monsters to modify damage done to them
if statesData.processDamageRequestToMonster then
damageData = statesData.processDamageRequestToMonster(monster, damageData) or damageData
end
-- player is hitting monster, or monster is hitting monster?
events:fireEventLocal("entityWillDealDamage", player, monsterManifest, damageData)
local newHealth = math.clamp(monster.health - damageData.damage, -99999999, monster.maxHealth)
local actualDamage = monster.health - newHealth
local actualDamageForLootCalculation = monster.health - math.clamp(newHealth, 0, monster.maxHealth)
monster.health = newHealth
monster.manifest.health.Value = monster.health
monster.manifest.maxHealth.Value = monster.maxHealth
monster.damageTable = monster.damageTable or {}
if monster.__MONSTER_EVENTS.onMonsterDamaged then
monster.__MONSTER_EVENTS.onMonsterDamaged(monster, damageData.damage, player)
end
if player then
if damageData.sourceType == "ability" then
if not monster.playersWithAbilityUse then
monster.playersWithAbilityUse = {}
end
monster.playersWithAbilityUse[player.userId] = true
end
if statesData.onDamageReceived then
statesData.onDamageReceived(monster, "player", player, damageData.damage)
end
if damageData.damage > 0 then
-- make monsters switch targets if attacked by someone closer
if player.Character and player.Character.PrimaryPart then
if monster.targetEntity and monster.targetEntity ~= player.Character.PrimaryPart then
local monsterPosition = monster.entity:IsA("BasePart") and monster.entity.Position or monster.entity:IsA("Model") and monster.entity.PrimaryPart.Position
local targetPosition = monster.targetEntity:IsA("BasePart") and monster.targetEntity.Position or monster.targetEntity:IsA("Model") and monster.targetEntity.PrimaryPart.Position
if (player.Character.PrimaryPart.Position - monsterPosition).magnitude < (targetPosition - monsterPosition).magnitude or rand:NextNumber() <= 0.3 then
monster.targetEntity = player.Character.PrimaryPart
monster.entityMonsterWasAttackedBy = player.Character.PrimaryPart
end
end
end
monster.damageTable[player] = (monster.damageTable[player] or 0) + actualDamageForLootCalculation
if not monster.targetEntity and player.Character.PrimaryPart then
if statesData.states["attacked-by-player"] and statesData.states["idling"] and statesData.states["following"] then
monster.entityMonsterWasAttackedBy = player.Character.PrimaryPart
monster.stateMachine:forceStateChange("attacked-by-player")
end
end
end
end
end
network:fireAllClients("signal_damage", monster.manifest, damageData)
if monster.health <= 0 then
-- trigger the rewards
if not monster.deathRewardsApplied then
triggerMonsterRewardsSequence(monster, player, damageData)
end
-- monster died. kill it.
monster.stateMachine:forceStateChange("dead")
else
if monster.stateMachine.states.hurt then
monster.stateMachine:forceStateChange("hurt")
end
end
return true, monster.health <= 0
end
return false, false
end
local function updateMonsterLogic()
LAST_MONSTER_UPDATE_CYCLE = tick()
monsterDebugStateData.phase = "start"
monsterDebugStateData.monster = ""
monsterDebugStateData.stateBefore = ""
local lastMonsterUpdated = nil
local preState = nil
local currTime = tick()
for i, monster in pairs(MONSTER_COLLECTION) do
local success, err = pcall(function()
if monster.manifest and monster.manifest.Parent == entityManifestCollectionFolder and monster.state ~= "dead" then
-- update the brain
lastMonsterUpdated = monster
if not monster.isMonsterPet then
-- fetch enemies within range
local entities do
local monsterData = monsterLookup[monster.monsterName]
local statesData = monsterData.statesData
if statesData.getClosestEntities then
entities = statesData.getClosestEntities(monster)
else
entities = defaultMonsterState.getClosestEntities(monster)
end
end
local nearbyTargets = {}
local closestEntity
local nearestDist = 999
-- find nearby targets
for i, entityManifest in pairs(entities) do
if (not entityManifest:FindFirstChild("isStealthed")) and (not entityManifest:FindFirstChild("isTargetImmune")) then
if entityManifest.state.Value ~= "dead" and entityManifest.state.Value ~= "sitting" and entityManifest.entityType.Value ~= "pet" then
local dist = (entityManifest.Position - monster.manifest.Position).magnitude
if dist < monster.sightRange then
nearbyTargets[#nearbyTargets + 1] = entityManifest
if dist <= nearestDist then
closestEntity = entityManifest
nearestDist = dist
end
end
end
end
end
if monster.closestEntity and (monster.closestEntity.Parent == nil or monster.closestEntity.state.Value == "dead") then
monster.closestEntity = nil
end
--legacy
monster.closestEntity = (monster.closestEntity == monster.targetEntity and monster.closestEntity) or closestEntity
monster.nearbyTargets = nearbyTargets
-- ITS TIME FOR G-G-G-GALAXY BRAIN MMMMMONSTERRRRR AI ! ! !
monster.entityDensityData = getEntityDensity(monster)
-- targetEntity
if monster.targetEntity and (monster.targetEntity.Parent == nil or monster.targetEntity.state.Value == "dead") then
monster:setTargetEntity(nil, nil)
end
-- despawn night mobs
if nearestDist >= 100 and monster.nightBoosted and not (game.Lighting.ClockTime < 5.9 or game.Lighting.ClockTime > 18.6) then
if monster and monster.manifest and monster.manifest.Parent then
monster.manifest:Destroy()
end
end
end
monsterDebugStateData.phase = "before-stateUpdate"
monster.__LAST_UPDATE = currTime
preState = monster.stateMachine.currentState
updateMonsterStateMachine(monster)
monsterDebugStateData.phase = "after-stateUpdate"
end
end)
if not success then
warn("manager_monster::" .. tostring(lastMonsterUpdated.monsterName) .. "::" .. tostring(lastMonsterUpdated.stateMachine.currentState) .. "::", err)
warn("killing entity")
if monster and monster.manifest and monster.manifest.Parent then
monster.manifest:Destroy()
end
end
end
monsterDebugStateData.phase = "end"
monsterDebugStateData.monster = ""
monsterDebugStateData.stateBefore = ""
LAST_MONSTER_UPDATE_CYCLE_END = tick()
end
local function onPlayerCharacterDied(player)
if not player.Character or not player.Character.PrimaryPart then return end
for i, monster in pairs(MONSTER_COLLECTION) do
if monster.targetEntity == player.Character.PrimaryPart then
monster:setTargetEntity(nil, nil, true)
updateMonsterStateMachine(monster)
end
end
end
local function onPlayerRemoving(player)
playerLastMonsterKill[player] = nil
end
local function monsterHasAttribute(monster, attribute)
if attribute == "enraged" then
return not not monster.IS_MONSTER_ENRAGED
end
return monster.variation == attribute
end
local function main()
-- register for variants --
events:registerForEvent("entityDiedTakingDamage", function(sourcePlayer, entityHitboxDamaged, damageData)
if not configuration.getConfigurationValue("doSpawnNightTimeVariants") then return end
local sourceEntityHitbox = utilities.getEntityManifestByEntityGUID(damageData.sourceEntityGUID)
if sourceEntityHitbox then
local monsterData = _getMonsterByManifest(sourceEntityHitbox)
if monsterData then
end
end
end)
events:registerForEvent("monsterEntitySpawning", function(monster)
if not configuration.getConfigurationValue("doSpawnNightTimeVariants") then return end
if monsterHasAttribute(monster, "stalker") then
monster.autoStealthTimer = tick()
local wasApplied, reason = network:invoke(
"applyStatusEffectToEntityManifest",
monster.manifest,
"stealth",
{
duration = 9999,
},
monster.manifest,
"autoStealth_stealth",
0
)
end
end)
events:registerForEvent("entityWillDealDamage", function(sourcePlayer, entityHitboxDamaged, damageData)
if not configuration.getConfigurationValue("doSpawnNightTimeVariants") then return end
local sourceEntityHitbox = utilities.getEntityManifestByEntityGUID(damageData.sourceEntityGUID)
if sourceEntityHitbox then
local monsterDealingDamageData = _getMonsterByManifest(sourceEntityHitbox)
-- note: not sure if this exists, make sure to check!
local monsterTakingDamageData = entityHitboxDamaged and _getMonsterByManifest(entityHitboxDamaged)
if monsterDealingDamageData then
if monsterHasAttribute(monsterDealingDamageData, "cursed") then
if math.random() < (1 / 3) then
local wasApplied, reason = require(game.ReplicatedStorage.modules.network):invoke(
"applyStatusEffectToEntityManifest",
entityHitboxDamaged,
"poison",
{duration = 3; healthLost = 0.2 * damageData.damage},
sourceEntityHitbox,
"monster-variant",
"poison"
)
end
elseif monsterHasAttribute(monsterDealingDamageData, "stalker") or (monsterTakingDamageData and monsterHasAttribute(monsterTakingDamageData, "stalker")) then
-- this will revoke stealth regardless of if the monster is being damaged or dealing damage
local monsterDataToRevokeStealthFrom = monsterHasAttribute(monsterDealingDamageData, "stalker") and monsterDealingDamageData or monsterTakingDamageData
if network:invoke("doesEntityManifestHaveStatusEffectBySourceType", monsterDataToRevokeStealthFrom.manifest, "autoStealth_stealth") then
network:invoke("removeStatusEffectFromEntityManifestBySourceType", monsterDataToRevokeStealthFrom.manifest, "autoStealth_stealth")
monsterDataToRevokeStealthFrom.autoStealthTimer = tick()
delay(5, function()
if tick() - monsterDataToRevokeStealthFrom.autoStealthTimer >= 5 and monsterDataToRevokeStealthFrom.manifest.Parent and not network:invoke("doesEntityManifestHaveStatusEffectBySourceType", monsterDataToRevokeStealthFrom.manifest, "autoStealth_stealth") then
local wasApplied, reason = network:invoke(
"applyStatusEffectToEntityManifest",
monsterDataToRevokeStealthFrom.manifest,
"stealth",
{
duration = 9999,
},
monsterDataToRevokeStealthFrom.manifest,
"autoStealth_stealth",
0
)
end
end)
end
end
end
if monsterTakingDamageData and monsterHasAttribute(monsterTakingDamageData, "short-fused") then
local wasApplied, reason = require(game.ReplicatedStorage.modules.network):invoke(
"applyStatusEffectToEntityManifest",
entityHitboxDamaged,
"explode",
{duration = 1},
sourceEntityHitbox,
"monster-variant",
"explosion"
)
end
if monsterTakingDamageData and monsterHasAttribute(monsterTakingDamageData, "enraged") then
if not monsterTakingDamageData.ENRAGED_AGGRO_TIMER then
monsterTakingDamageData.ENRAGED_AGGRO_TIMER = 0
end
if tick() - monsterTakingDamageData.ENRAGED_AGGRO_TIMER > 5 then
monsterTakingDamageData.ENRAGED_AGGRO_TIMER = tick()
for i, oMonsterData in pairs(MONSTER_COLLECTION) do
if oMonsterData.manifest and (oMonsterData.manifest.Position - monsterTakingDamageData.manifest.Position).magnitude < 100 then
oMonsterData:setTargetEntity(sourceEntityHitbox)
end
end
end
end
end
end)
-- end register for variants --
game.Players.PlayerRemoving:connect(onPlayerRemoving)
entityManifestCollectionFolder.ChildRemoved:connect(onMonsterRemoved)
network:create("setIsSpawnRegionCollectionDisabled_server", "BindableFunction", "OnInvoke", setIsSpawnRegionCollectionDisabled)
network:create("monsterDamageRequest_server", "BindableFunction", "OnInvoke", onMonsterDamageRequestReceived)
network:create("getMonsterDataByMonsterManifest_server", "BindableFunction", "OnInvoke", function(monsterManifest, specificProperty)
if specificProperty then
return _getMonsterByManifest(monsterManifest, specificProperty)
else
local monster = _getMonsterByManifest(monsterManifest)
if not monster then
return nil
end
monster = utilities.copyTable(monster)
purgeNumericKeys(monster)
return monster
end
end)
network:create("registerToMonsterState", "RemoteFunction", "OnServerInvoke", onRegisterToMonsterState)
network:create("monsterStateChanged", "RemoteEvent")
network:create("playerKilledMonster", "BindableEvent")
network:create("spawnMonsterPet", "BindableFunction", "OnInvoke", onSpawnMonsterPet)
network:create("dumpMonsterManagerDebugInformation", "BindableFunction", "OnInvoke", onDumpMonsterManagerDebugInformation)
network:connect("playerCharacterDied", "Event", onPlayerCharacterDied)
network:create("getMonsterEntityDataTag", "BindableFunction", "OnInvoke", function(monsterEntity, nameTag)
local monsterData do
for i, monster in pairs(MONSTER_COLLECTION) do
if monster.manifest == monsterEntity then
monsterData = monster
end
end
end
if monsterData then
return monsterData[nameTag]
end
end)
network:create("setMonsterEntityDataTag", "BindableFunction", "OnInvoke", function(monsterEntity, nameTag, value)
local monsterData do
for i, monster in pairs(MONSTER_COLLECTION) do
if monster.manifest == monsterEntity then
monsterData = monster
end
end
end
if monsterData then
monsterData[nameTag] = value
end
end)
network:create("setMonsterTargetEntity", "BindableFunction", "OnInvoke", function(monsterEntity, targetEntity, targetEntitySetSource, targetEntityLockType)
local monsterData do
for i, monster in pairs(MONSTER_COLLECTION) do
if monster.manifest == monsterEntity then
monsterData = monster
end
end
end
if monsterData and targetEntity then
monsterData:setTargetEntity(targetEntity, nil, targetEntitySetSource, targetEntityLockType)
end
end)
local function isNight()
return game.Lighting.ClockTime < 5.9 or game.Lighting.ClockTime > 18.6
end
spawn(function() while wait(isNight() and MONSTER_SPAWN_CYCLE_TIME * 0.5 or MONSTER_SPAWN_CYCLE_TIME) do
for i, monsterSpawnInformation in pairs(getSpawnRegionsUnderpopulated()) do
spawnMonster(monsterSpawnInformation.monsterNameToSpawn, monsterSpawnInformation.spawnRegionCollection)
end
end end)
while wait(1/5) do
updateMonsterLogic()
end
end
function module.init(Modules)
network = Modules.network
utilities = Modules.utilities
physics = Modules.physics
placeSetup = Modules.placeSetup
projectile = Modules.projectile
configuration = Modules.configuration
events = Modules.events
spawnRegionCollectionsFolder = placeSetup.getPlaceFolder("spawnRegionCollections")
entityManifestCollectionFolder = placeSetup.getPlaceFolder("entityManifestCollection")
entityRenderCollectionFolder = placeSetup.getPlaceFolder("entityRenderCollection")
itemsFolder = placeSetup.getPlaceFolder("items")
entitiesFolder = placeSetup.getPlaceFolder("entities")
foilage = placeSetup.getPlaceFolder("foilage")
MONSTER_RAYCAST_IGNORE_LIST = {
spawnRegionCollectionsFolder,
entityManifestCollectionFolder,
entityRenderCollectionFolder,
itemsFolder,
entitiesFolder,
foilage
}
physics:setWholeCollisionGroup(baseHitbox, "monsters")
network:create("signal_damage", "RemoteEvent")
network:create("spawnMonster", "BindableFunction", "OnInvoke", onSpawnMonster)
spawn(main)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_network.lua
================================================
-- Routes all client->server remote event and function calls through a secure channel
-- Author: berezaa
local module = {}
module.priority = 2
local network
local RunService = game:GetService("RunService")
local signal = Instance.new("RemoteEvent")
signal.Name = "signal"
signal.Parent = game.ReplicatedStorage
local playerRequest = Instance.new("RemoteFunction")
playerRequest.Name = "playerRequest"
playerRequest.Parent = game.ReplicatedStorage
local function getServerNetworkLog(player)
if player:FindFirstChild("developer") then
return network.log
end
end
function module.init(Modules)
network = Modules.network
signal.OnServerEvent:connect(function(player, eventName, ...)
local event = game.ServerStorage.serverNetwork:FindFirstChild(eventName)
if event then
return event:Fire(player, ...)
else
if network then
if RunService:IsStudio() then
error("MISSING REMOTE EVENT HIT! WOULD BAN IN PROD. Name: " .. eventName)
else
warn(player.Name, "kicked for bad remote event request:", eventName)
network:invoke("banPlayer", player, 60 * 60 * 24, "Compromised client", "network")
end
end
end
end)
playerRequest.OnServerInvoke = function(player, requestName, ...)
local request = game.ServerStorage.serverNetwork:FindFirstChild(requestName)
if request then
return request:Invoke(player, ...)
else
if network then
if RunService:IsStudio() then
error("MISSING REMOTE FUNCTION HIT! WOULD BAN IN PROD. Name: " .. requestName)
else
warn(player.Name, "kicked for bad remote function request:", requestName)
network:invoke("banPlayer", player, 60 * 60 * 24, "Compromised client", "network")
end
end
end
end
network:create("getServerNetworkLog", "RemoteFunction", "OnServerInvoke", getServerNetworkLog)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_orb.lua
================================================
local module = {}
local CollectionService = game:GetService("CollectionService")
--
local network
local assetsFolder = game.ReplicatedStorage:WaitForChild("assets")
local placeIdList do
-- main game
if game.GameId == 833209132 then
placeIdList = {
2119298605, -- nilgarf
}
-- free to play
elseif game.GameId == 712031239 then
placeIdList = {
4787415375, -- crabby den
4042431927, -- enchanted forest
4041618739, -- great crossroads
4042399045, -- lost corridor
4041616995, -- mushroom forest
4041642879, -- mushroom grotto
4041449372, -- mushtown
4042577479, -- nilgarf
4042595899, -- nilgarf sewers
4042356215, -- port fidelio
4042533453, -- redwood pass
4042327457, -- scallop shores
4784800626, -- seaside path
4787417227, -- shiprock bottom
4786263828, -- spider queen's lair
4784798551, -- the clearing
4042381342, -- colosseum
4042493740, -- tree of life
4042553675, -- warrior stronghold
}
end
end
local orbSpawn do
local orbSpawnParts = CollectionService:GetTagged("orbSpawn")
if #orbSpawnParts == 1 then
orbSpawn = Instance.new("Vector3Value")
orbSpawn.Name = "orbSpawn"
orbSpawn.Value = orbSpawnParts[1].Position
orbSpawn.Parent = game:GetService("ReplicatedStorage")
orbSpawnParts[1]:Destroy()
else
warn("Orb manager requires exactly 1 orb spawn location")
end
end
local orb = assetsFolder.entities.orb
local checkForOrbSpawn = true
local function onPlayerAdded(player)
if orb.Parent == workspace then
network:fireClient("effects_requestEffect", player, "orbAnnoucement")
end
end
game.Players.PlayerAdded:Connect(onPlayerAdded)
local function spawnOrb()
orb:SetPrimaryPartCFrame(CFrame.new(orbSpawn.Value))
orb.Parent = workspace
orb.PrimaryPart.loop:Play()
network:fireAllClients("effects_requestEffect", "orbArrival", {orb = orb})
end
local function despawnOrb()
network:fireAllClients("effects_requestEffect", "orbDeparture", {orb = orb})
delay(5, function()
orb.Parent = script
end)
end
local function getCurrentDay()
local vesterianDay = game:GetService("ReplicatedStorage"):FindFirstChild("vesterianDay")
if not vesterianDay then
return 0
end
return math.floor(vesterianDay.Value + 0.5)
end
local function update()
local clockTime = game.Lighting.ClockTime
local isNight = (clockTime < 5.9) or (clockTime > 18.6) -- keep synchronized with day night cycle
if isNight then
if checkForOrbSpawn then
checkForOrbSpawn = false
local day = getCurrentDay()
local rand = Random.new(day)
local placeId = placeIdList[rand:NextInteger(1, #placeIdList)]
if game.PlaceId == placeId then
spawnOrb()
end
print("manager_orb chose "..placeId.." as the spawn location. This place is "..game.PlaceId..".")
end
else
if not checkForOrbSpawn then
checkForOrbSpawn = true
if orb.Parent ~= script then
despawnOrb()
end
end
end
end
-- defunct
local function playerRequest_enchantAbility(player, orb, requestData)
local playerData = playerDataContainer[player]
if playerData then
if typeof(orb) == "Instance" and orb:FindFirstChild("itemEnchanted") and orb:IsDescendantOf(workspace) then
if player.Character and player.Character.PrimaryPart and (player.Character.PrimaryPart.Position - orb.Position).magnitude <= 100 then
local abilitySlotData
for i, abilityData in pairs(playerData.abilities) do
if abilityData.id == requestData.id then
abilitySlotData = abilityData
break
end
end
local abilityBaseData = abilityLookup[abilitySlotData.id](playerData)
if abilitySlotData and abilityBaseData then
local metadata = abilityBaseData.metadata
if metadata then
local AP = playerData.level - 1
local availablePoints = AP - getPlayerDataSpentAP(playerData)
if requestData.request == "upgrade" then
if metadata.upgradeCost and metadata.maxRank and availablePoints >= metadata.upgradeCost then
if abilitySlotData.rank > 0 and abilitySlotData.rank < metadata.maxRank then
abilitySlotData.rank = abilitySlotData.rank + 1
playerData.nonSerializeData.playerDataChanged:Fire("abilities")
orb.itemEnchanted:Play()
if orb:FindFirstChild("steady") then
orb.steady:Emit(50)
end
return true, "upgrade applied"
end
end
elseif requestData.request == "variant" then
if abilitySlotData.variant == nil or metadata.variants[abilitySlotData.variant].default then
local variantData = metadata.variants[requestData.variant]
if variantData and variantData.cost and availablePoints >= variantData.cost then
if variantData.requirement and variantData.requirement(playerData) then
abilitySlotData.variant = requestData.variant
playerData.nonSerializeData.playerDataChanged:Fire("abilities")
orb.itemEnchanted:Play()
if orb:FindFirstChild("steady") then
orb.steady:Emit(50)
end
return true, "variant applied"
end
return false, "requirements not fufilled"
end
return false, "not enough points"
end
return false, "ability already has variant"
end
return false, "invalid request"
end
return false, "unsupported ability"
end
return false, "no data for ability"
end
return false, "too far from orb"
end
return false, "invalid orb"
end
end
local function startUpdating()
while wait(1) do
update()
end
end
function module.init(Modules)
network = Modules.network
network:create("playerRequest_enchantAbility", "RemoteFunction", "OnServerInvoke", playerRequest_enchantAbility)
spawn(startUpdating)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_party.lua
================================================
-- author: Polymorphic
local module = {}
local HttpService = game:GetService("HttpService")
local network
local utilities
local MAX_PARTY_MEMBERS = 6
--[[
partyMemberData {}
bool isLeader
bool isPowerUser
player player
partyData {}
{partyMemberData} members
partyDataContainer {partyData}
--]]
local partyDataContainer = {}
local pendingPartyInvites = {}
local invitationCD = {}
local function getPartyDataByPlayer(player)
for _, partyData in pairs(partyDataContainer) do
for _, partyMemberData in pairs(partyData.members) do
if partyMemberData.player == player then
return partyData, partyMemberData
end
end
end
return nil, nil
end
local function playerRequest_getMyPartyData(player)
return getPartyDataByPlayer(player)
end
local function getPartyMembersInMyParty(player)
local partyData = getPartyDataByPlayer(player)
if partyData then
return partyData.members
end
return nil
end
local function propogatePartyDataChangedToPartyMembers(partyData)
-- let players know if a new party is created
if partyData.isNew then
if partyData.removeIsNewNextPropogate then
partyData.isNew = false
partyData.removeIsNewNextPropogate = false
else
partyData.removeIsNewNextPropogate = true
end
end
for i, partyPlayerData in pairs(partyData.members) do
network:fireClient("signal_myPartyDataChanged", partyPlayerData.player, partyData)
end
end
-- makes `playerInvited` join the party of `playerInviting`
local function invitePlayerToMyParty(playerInviting, playerInvited)
local partyData_playerInviting, partyMemberData_playerInviting = getPartyDataByPlayer(playerInviting)
local partyData_playerInvited, ________________________ = getPartyDataByPlayer(playerInvited)
if playerInviting ~= playerInvited and not partyData_playerInvited then
if not invitationCD[playerInviting] or (tick() - invitationCD[playerInviting] > 3) then
invitationCD[playerInviting] = tick()
local guid = HttpService:GenerateGUID(false)
pendingPartyInvites[guid] = {
partyData = partyData_playerInviting;
playerInviting = playerInviting;
player = playerInvited;
}
network:fireClient("signal_playerInvitedToParty", playerInvited, playerInviting, guid)
return true, "Invitation sent."
else
return false, "Sending invites too fast!"
end
end
return false, "Invalid player."
end
local function isPartyDataValid(partyData)
for _, _partyData in pairs(partyDataContainer) do
if partyData == _partyData then
return true
end
end
return false
end
local function playerRequest_acceptMyPartyInvitation(player, guid)
local partyData = getPartyDataByPlayer(player)
if not partyData then
local partyInvitationData = pendingPartyInvites[guid]
if partyInvitationData and partyInvitationData.player == player then
if isPartyDataValid(partyInvitationData.partyData) or partyInvitationData.partyData == nil then
-- do this instead of reading the party data of the invitation because they mightve invited multiple
-- people and then had the party made.
local invitingPlayerPartyData = getPartyDataByPlayer(partyInvitationData.playerInviting)
if partyInvitationData.playerInviting and partyInvitationData.playerInviting.Parent then
if invitingPlayerPartyData then
pendingPartyInvites[guid] = nil
if #invitingPlayerPartyData.members < MAX_PARTY_MEMBERS then
-- invalidate the guid
local partyMemberData = {}
partyMemberData.isLeader = false
partyMemberData.isPowerUser = false
partyMemberData.player = player
table.insert(invitingPlayerPartyData.members, partyMemberData)
invitingPlayerPartyData.newestMember = partyMemberData
local invitingPlayer = partyInvitationData.playerInviting
invitingPlayerPartyData.status = {text = player.Name .. " joined the party. (Invited by " .. invitingPlayer.Name .. ")" }
-- update players
propogatePartyDataChangedToPartyMembers(invitingPlayerPartyData)
return true, "Joined party"
else
return false, "Party is full"
end
else
local partyData = {}
partyData.guid = HttpService:GenerateGUID(false)
partyData.members = {}
local partyMemberData_player = {}
partyMemberData_player.isLeader = false
partyMemberData_player.isPowerUser = false
partyMemberData_player.player = player
table.insert(partyData.members, partyMemberData_player)
partyData.newestMember = partyMemberData_player
local partyMemberData_playerInviting = {}
partyMemberData_playerInviting.isLeader = true
partyMemberData_playerInviting.isPowerUser = true
partyMemberData_playerInviting.player = partyInvitationData.playerInviting
table.insert(partyData.members, partyMemberData_playerInviting)
table.insert(partyDataContainer, partyData)
partyData.isNew = true
partyData.status = {text = player.Name .. " joined the party. (Invited by " .. partyInvitationData.playerInviting.Name .. ")" }
-- update players
propogatePartyDataChangedToPartyMembers(partyData)
return true, "Joined party"
end
else
return false, "Invalid leader."
end
end
end
end
return nil, "eof"
end
local function int__checkIfCleanUpPartyData(givenPartyData)
for i, partyData in pairs(partyDataContainer) do
if partyData == givenPartyData then
if #partyData.members <= 1 then
if #partyData.members == 1 then
network:fireClient("signal_myPartyDataChanged", partyData.members[1].player, nil)
partyData.members[1].player = nil
partyData.members[1] = nil
partyData.members = {}
end
table.remove(partyDataContainer, i)
end
end
end
end
local function playerRequest_leaveParty(player, playerToKickOut)
local partyData_player, partyMemberData_player = getPartyDataByPlayer(player)
if partyData_player then
if playerToKickOut and partyMemberData_player and partyMemberData_player.isPowerUser then
local partyData_playerToKickOut, partyMemberData_playerToKickOut = getPartyDataByPlayer(playerToKickOut)
if partyData_playerToKickOut == partyData_player and not partyMemberData_playerToKickOut.isLeader then
for i, partyMemberData in pairs(partyData_player.members) do
if partyMemberData.player == playerToKickOut then
table.remove(partyData_player.members, i)
-- reset newest member
partyData_player.newestMember = nil
if player == playerToKickOut then
partyData_player.status = {text = player.Name .. " left the party." }
else
partyData_player.status = {text = playerToKickOut.Name .. " was kicked from the party." }
end
-- update players
propogatePartyDataChangedToPartyMembers(partyData_player)
network:fireClient("signal_myPartyDataChanged", playerToKickOut, nil)
int__checkIfCleanUpPartyData(partyData_player)
return true
end
end
end
elseif not playerToKickOut then
for i, partyMemberData in pairs(partyData_player.members) do
if partyMemberData.player == player then
table.remove(partyData_player.members, i)
-- reset newest member
partyData_player.newestMember = nil
partyData_player.status = {text = player.Name .. " left the party." }
-- update players
propogatePartyDataChangedToPartyMembers(partyData_player)
network:fireClient("signal_myPartyDataChanged", player, nil)
int__checkIfCleanUpPartyData(partyData_player)
return true
end
end
else
end
end
return false
end
local function playerRequest_startGroupTeleport(player, teleportPart)
local partyData_player, partyMemberData_player = getPartyDataByPlayer(player)
if partyData_player and partyMemberData_player.isLeader and teleportPart and teleportPart:FindFirstChild("teleportDestination") then
if partyData_player.teleportState == "none" or partyData_player.teleportState == nil then
partyData_player.teleportState = "pending"
partyData_player.teleportDestination = teleportPart.teleportDestination.Value
partyData_player.status = nil
partyData_player.teleportPosition = teleportPart.Position
partyData_player.teleportPart = teleportPart
propogatePartyDataChangedToPartyMembers(partyData_player)
spawn(function()
while partyData_player.teleportState == "pending" and player.Parent do
local canTeleport = true
for i, partyMemberData in pairs(partyData_player.members) do
if not partyMemberData.player.Character or not partyMemberData.player.Character.PrimaryPart or utilities.magnitude(partyMemberData.player.Character.PrimaryPart.Position - teleportPart.Position) > 20 then
canTeleport = false
end
end
if canTeleport then
partyData_player.teleportState = "teleporting"
break
else
wait(1 / 2)
end
end
if partyData_player.teleportState == "teleporting" then
propogatePartyDataChangedToPartyMembers(partyData_player)
local startTime = tick()
local isWaitingForTeleportReady = true
while tick() - startTime < 15 and isWaitingForTeleportReady do
isWaitingForTeleportReady = false
for i, partyMemberData in pairs(partyData_player.members) do
if not partyMemberData.isReadyToTeleport then
isWaitingForTeleportReady = true
end
end
if isWaitingForTeleportReady then
wait(1 / 4)
end
end
local playersToTeleport = {}
local partyLeaderUserId = 0
for i, partyMemberData in pairs(partyData_player.members) do
if partyMemberData.isReadyToTeleport then
table.insert(playersToTeleport, partyMemberData.player)
if partyMemberData.isLeader then
partyLeaderUserId = partyMemberData.player.userId
end
end
end
-- drop the teleport if anyone in the party is experiencing a DataStore outage
local teleportFail = false
local failedPlayers = {}
for i,player in pairs(playersToTeleport) do
if player:FindFirstChild("DataSaveFailed") then
teleportFail = true
table.insert(failedPlayers, player)
end
end
if teleportFail then
local errorMsg = "Teleport failed: "
for i,player in pairs(failedPlayers) do
errorMsg = errorMsg .. player.Name .. (failedPlayers[i+1] and ", " or "")
end
errorMsg = errorMsg .. " " .. (#failedPlayers == 1 and "is" or "are") .. " experiencing a DataStore outage."
partyData_player.status = {text = errorMsg; textColor3 = Color3.fromRGB(255, 57, 60)}
partyData_player.teleportState = "none"
propogatePartyDataChangedToPartyMembers(partyData_player)
return false, "Datastore outage."
end
if #playersToTeleport > 0 and partyLeaderUserId then
local success, message = network:invoke("teleportParty", playersToTeleport, teleportPart.teleportDestination.Value, partyLeaderUserId)
return success, message
else
warn("manager_party::no leader or no one ready")
end
end
end)
return true
end
end
return false
end
local function getPartyDataByGUID(guid)
for _, partyData in pairs(partyDataContainer) do
if partyData.guid == guid then
return partyData
end
end
return nil
end
local function getPartyLeaderForPartyData(partyData)
for _, partyMemberData in pairs(partyData.members) do
if partyMemberData.isLeader then
return partyMemberData
end
end
return nil
end
-- todo: only accept from JoinData.members
local function playerRequest_acceptMyPartyInvitationByTeleportation(player, guid, partyLeaderUserId)
if not guid or not partyLeaderUserId then return end
local partyData_guid = getPartyDataByGUID(guid)
if not partyData_guid then
local joinData = player:GetJoinData()
local partyData = {}
partyData.guid = guid
partyData.isTeleportParty = true
partyData.partyLeaderUserId = partyLeaderUserId
partyData.members = {}
local partyMemberData = {}
partyMemberData.isLeader = player.userId == partyLeaderUserId
partyMemberData.isPowerUser = true
partyMemberData.player = player
table.insert(partyData.members, partyMemberData)
table.insert(partyDataContainer, partyData)
propogatePartyDataChangedToPartyMembers(partyData)
elseif partyData_guid and partyData_guid.isTeleportParty then
local partyMemberData = {}
partyMemberData.isLeader = partyData_guid.partyLeaderUserId == player.userId
partyMemberData.isPowerUser = true
partyMemberData.player = player
table.insert(partyData_guid.members, partyMemberData)
propogatePartyDataChangedToPartyMembers(partyData_guid)
end
end
local function playerRequest_cancelGroupTeleport(player)
local partyData_player, partyMemberData_player = getPartyDataByPlayer(player)
if partyData_player and partyData_player.teleportState == "pending" then
partyData_player.teleportState = "none"
propogatePartyDataChangedToPartyMembers(partyData_player)
end
end
local function signal_playerReadyToGroupTeleport(player)
local partyData_player, partyMemberData_player = getPartyDataByPlayer(player)
if partyData_player and partyMemberData_player then
partyMemberData_player.isReadyToTeleport = true
end
end
local function onPlayerAdded(player)
local joinData = player:GetJoinData()
end
local function onPlayerRemoving(player)
invitationCD[player] = nil
if player:FindFirstChild("teleporting") == nil then
local partyData_player, partyMemberData_player = getPartyDataByPlayer(player)
if partyData_player then
for i, partyMemberData in pairs(partyData_player.members) do
if partyMemberData.player == player then
table.remove(partyData_player.members, i)
-- reassign leader if leaver was leader
if partyMemberData.isLeader then
if partyData_player.members[1] then
partyData_player.members[1].isLeader = true
end
end
-- reset newest member
partyData_player.newestMember = nil
partyData_player.status = {text = player.Name .. " left the party." }
-- update players
propogatePartyDataChangedToPartyMembers(partyData_player)
int__checkIfCleanUpPartyData(partyData_player)
return true
end
end
end
end
end
function module.init(Modules)
network = Modules.network
utilities = Modules.utilities
game.Players.PlayerAdded:connect(onPlayerAdded)
for _, player in pairs(game.Players:GetPlayers()) do
onPlayerAdded(player)
end
game.Players.PlayerRemoving:connect(onPlayerRemoving)
network:create("resumePartyAfterTeleport", "BindableFunction", "OnInvoke", playerRequest_acceptMyPartyInvitationByTeleportation)
network:create("playerRequest_getPartyMembersInMyParty", "RemoteFunction", "OnServerInvoke", getPartyMembersInMyParty)
network:create("playerRequest_getMyPartyData", "RemoteFunction", "OnServerInvoke", playerRequest_getMyPartyData)
network:create("playerRequest_invitePlayerToMyParty", "RemoteFunction", "OnServerInvoke", invitePlayerToMyParty)
network:create("playerRequest_acceptMyPartyInvitation", "RemoteFunction", "OnServerInvoke", playerRequest_acceptMyPartyInvitation)
network:create("playerRequest_leaveParty", "RemoteFunction", "OnServerInvoke", playerRequest_leaveParty)
network:create("playerRequest_startGroupTeleport", "RemoteFunction", "OnServerInvoke", playerRequest_startGroupTeleport)
network:create("playerRequest_cancelGroupTeleport", "RemoteFunction", "OnServerInvoke", playerRequest_cancelGroupTeleport)
network:create("signal_playerReadyToGroupTeleport", "RemoteEvent", "OnServerEvent", signal_playerReadyToGroupTeleport)
network:create("getPartyDataByPlayer", "BindableFunction", "OnInvoke", getPartyDataByPlayer)
network:create("signal_playerInvitedToParty", "RemoteEvent")
network:create("signal_myPartyDataChanged", "RemoteEvent")
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_pet.lua
================================================
-- pets service, duh!
-- acts as the interfacing between manager_monster and manager_player
-- to keep track of pets and spawning/despawning them.
local module = {}
local network
local mapping
local playerPetDataCollection = {}
--[[
playerPetData {}
int id
instance manifest
]]--
local function despawnPlayerPet(player)
if playerPetDataCollection[player] then
playerPetDataCollection[player].manifest:Destroy()
playerPetDataCollection[player] = nil
end
end
local function spawnPlayerPet(player, petEquipmentData)
if not playerPetDataCollection[player] then
local petManifest = network:invoke("spawnMonsterPet", player, petEquipmentData.id, petEquipmentData)
local playerPetData = {}
playerPetData.id = petEquipmentData.id
playerPetData.manifest = petManifest
playerPetDataCollection[player] = playerPetData
end
end
local function getPlayerPetEquipment(player, equipment)
for i, equipmentData in pairs(equipment) do
if equipmentData.position == mapping.equipmentPosition.pet then
return equipmentData
end
end
return nil
end
local function int__processPlayerPetEquipmentData(player, petEquipmentData)
if petEquipmentData and not playerPetDataCollection[player] then
spawnPlayerPet(player, petEquipmentData)
elseif not petEquipmentData and playerPetDataCollection[player] then
despawnPlayerPet(player, petEquipmentData)
end
end
local function onPlayerEquipmentChanged_server(player, equipment)
local playerPetEquipmentData = getPlayerPetEquipment(player, equipment)
int__processPlayerPetEquipmentData(player, playerPetEquipmentData)
end
local function onPlayerAdded(player)
-- get the player's data, waiting until they either leave or
-- we successfully fetch their player data
local playerData do
while not playerData and player.Parent == game.Players do
playerData = network:invoke("getPlayerData", player)
wait(0.1)
end
end
if playerData then
onPlayerEquipmentChanged_server(player, playerData.equipment)
end
end
local function onPlayerRemoving(player)
if playerPetDataCollection[player] then
-- despawn pet here!
despawnPlayerPet(player)
end
playerPetDataCollection[player] = nil
end
function module.init(Modules)
network = Modules.network
mapping = Modules.mapping
-- todo: this is really icky, should just hook into player data loaded
spawn(function()
for _, player in pairs(game.Players:GetPlayers()) do
onPlayerAdded(player)
end
end)
game.Players.PlayerAdded:connect(onPlayerAdded)
game.Players.PlayerRemoving:connect(onPlayerRemoving)
network:connect("playerEquipmentChanged_server", "Event", onPlayerEquipmentChanged_server)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_player.lua
================================================
local module = {}
module.priority = 3
local playerDataContainer = {}
local playerPositionDataContainer = {}
local shuttingDown = false
local runService = game:GetService("RunService")
local CollectionService = game:GetService("CollectionService")
local HttpService = game:GetService("HttpService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local datastoreInterface
local network
local utilities
local physics
local levels
local mapping
local configuration
local placeSetup
local events
local detection
local projectile
local entityManifestCollectionFolder
local temporaryEquipmentFolder
local itemLookup = require(ReplicatedStorage.itemData)
local itemAttributes = require(ReplicatedStorage.itemAttributes)
local perkLookup = require(ReplicatedStorage.perkLookup)
local assetsFolder = ReplicatedStorage.assets
-- has to load here due to requirements on stuff above
local PLAYER_LEVEL_CAP = 49
local function getPlayerData(player)
return playerDataContainer[player]
end
local function getPlayerData_remote(callingPlayer, ...)
return getPlayerData(...)
end
-- GLOBAL DATA HOOKUP
local function getPlayerGlobalData(player)
local success, data, status = datastoreInterface:getPlayerGlobalSaveFileData(player)
if not success then
local reportingSuccess, reportingError = pcall(function()
network:invoke("reportError", player, "error", "getPlayerGlobalData failed: "..status)
network:invoke("reportAnalyticsEvent",player,"data:fail:save")
end)
if not reportingSuccess then
warn("Failed to report data error: "..reportingError)
end
end
return success, data, status
end
local function setPlayerGlobalData(player, GlobalData)
local playerId = player.userId
local success, status, version = datastoreInterface:updatePlayerGlobalSaveFileData(playerId, GlobalData)
if not success then
local reportingSuccess, reportingError = pcall(function()
network:invoke("reportError", player, "error", "setPlayerGlobalData failed: "..status)
network:invoke("reportAnalyticsEvent",player,"data:fail:save")
end)
if not reportingSuccess then
warn("Failed to report data error: "..reportingError)
end
end
return success, status, version
end
local function isPlayerOfClass(player, class)
class = class:lower()
if class == "adventurer" then
return true
end
local playerData = playerDataContainer[player]
if not playerData then
return false
end
if playerData.class:lower() == class then
return true
end
-- conflating the name of an ability book with a class seems spookarook
-- but here we go anyways, Davidii taking responsibility for this
return playerData.abilityBooks[class] ~= nil
end
local function getPlayerDefaultHomePlaceId(player)
if isPlayerOfClass(player, "warrior") then
return 2470481225 -- warrior stronghold
elseif isPlayerOfClass(player, "mage") then
return 3112029149 -- tree of life
elseif isPlayerOfClass(player, "hunter") then
return 2546689567
else
return 2064647391
end
end
local function onDeathGuiAccepted(player)
local playerData = playerDataContainer[player]
if not playerData then return end
local tagName = "acceptedDeathConsequences"
local tag = player:FindFirstChild(tagName)
if tag then return end
local source = "game:death"
playerData.nonSerializeData.incrementPlayerData("gold", -(playerData.gold*0.1), source)
local expForNextLevel = levels.getEXPToNextLevel(playerData.level)
local newExp = math.clamp(playerData.exp - expForNextLevel * 0.2, 0, expForNextLevel)
playerData.nonSerializeData.setPlayerData("exp", newExp)
local nearestCity = game.ReplicatedStorage:FindFirstChild("nearestCityId")
nearestCity = nearestCity and nearestCity.Value
local returnDestination = nearestCity or playerData.homePlaceId or getPlayerDefaultHomePlaceId(player)
returnDestination = utilities.placeIdForGame(returnDestination)
playerData.lastLocationDeathOverride = returnDestination
tag = Instance.new("BoolValue")
tag.Name = tagName
tag.Value = true
tag.Parent = player
-- set health and mana to 1
local character = player.Character
if character then
local manifest = character.PrimaryPart
if manifest then
local health = manifest:FindFirstChild("health")
local mana = manifest:FindFirstChild("mana")
if health and mana then
health.Value = 1
mana.Value = 1
end
end
end
if game.PlaceId == 2103419922 then
-- game:GetService("TeleportService"):Teleport(2103419922, player)
player:Kick("You are dead.")
return
end
network:invoke("teleportPlayer", player, playerData.lastLocationDeathOverride, "default", nil, "death")
end
local function onPlayerRemoving(player)
local playerId = player.userId
if not player:FindFirstChild("teleporting") then
if player:FindFirstChild("awaitingDeathGuiResponse") or player:FindFirstChild("acceptedDeathConsequences") or
(player:FindFirstChild("isPlayerSpawning") and player.isPlayerSpawning.Value) then
network:fireAllClients("signal_alertChatMessage", {
Text = player.Name .. " rage quit.";
Font = Enum.Font.SourceSansBold;
Color = Color3.fromRGB(45, 87, 255)
})
else
network:fireAllClients("signal_alertChatMessage", {
Text = player.Name .. " disconnected.";
Font = Enum.Font.SourceSansBold;
Color = Color3.fromRGB(45, 87, 255)
})
end
local tag = Instance.new("BoolValue")
tag.Name = "disconnected"
tag.Parent = player
end
playerPositionDataContainer[player] = nil
if playerDataContainer[player] then
-- onUnequipped this player's equipment to avoid memory leaks
for _, slotData in pairs(playerDataContainer[player].equipment) do
local itemData = itemLookup[slotData.id]
if itemData.perks then
for perkName, _ in pairs(itemData.perks) do
local perkData = perkLookup[perkName]
if perkData and perkData.onUnequipped then
local success, err = pcall(function()
perkData.onUnequipped(player, itemData, tostring(slotData.position))
end)
if not success then
warn(string.format("item %s unequip failed because: %s", itemData.name, err))
end
end
end
end
end
local deathTag = player:FindFirstChild("awaitingDeathGuiResponse")
if deathTag ~= nil then
deathTag:Destroy()
onDeathGuiAccepted(player)
end
if player:FindFirstChild("entityGUID") then
local statusEffects = network:invoke("playerRemovingPackageStatusEffects", player)
if statusEffects then
playerDataContainer[player].packagedStatusEffects = statusEffects
end
end
local char = player.Character
if char then
local manifest = char.PrimaryPart
if manifest then
local health = manifest:FindFirstChild("health")
local mana = manifest:FindFirstChild("mana")
if health and mana then
playerDataContainer[player]["condition"] = {
health = health.Value,
mana = mana.Value
}
end
end
end
local playerDataBackup = playerDataContainer[player]
playerDataContainer[player] = nil
-- attempt to retry up to 5 times on failure
for _ = 1, 5 do
local Success, Error, TimeStamp = datastoreInterface:updatePlayerSaveFileData(playerId, playerDataBackup)
if not Success then
warn(player.Name,"'s data failed to save.",Error)
local reportingSuccess, reportingError = pcall(function()
network:invoke("reportError", player, "error", "Failed to save player data (on exit!): "..Error)
network:invoke("reportAnalyticsEvent",player,"data:fail:save")
end)
if not reportingSuccess then
warn("Failed to report data error: "..reportingError)
end
local messagingSuccess, messagingError = pcall(function()
game:GetService("MessagingService"):PublishAsync("datawarning", {userId = playerId})
end)
if not messagingSuccess then
warn("Failed to send warning: "..messagingError)
end
else
if player:FindFirstChild("DataSaveFailed") then
player.DataSaveFailed:Destroy()
end
return TimeStamp
end
end
end
-- can't believe i have to do this.
if player.Character then
player.Character.Parent = nil
player.Character = nil
end
end
-- TODO: tie into teleport manager
local function saveDataForTeleport(player)
if not player:FindFirstChild("teleporting") then
local tag = Instance.new("BoolValue")
tag.Name = "teleporting"
tag.Parent = player
network:invoke("reportAnalyticsEvent",player,"teleport:attempt")
local TimeStamp = onPlayerRemoving(player)
if TimeStamp then
return TimeStamp
end
end
end
--[[
PLAYER APPEARANCE CHANGE
--]]
local function playerRequest_changeAccessories(player, desiredAccessories, dialogueSource)
local playerData = playerDataContainer[player]
if playerData then
if dialogueSource:IsA("ModuleScript") then
local dialogueData = require(dialogueSource)
if dialogueData and dialogueData.characterCustomization then
local details = dialogueData.characterCustomization
local cost = details.cost or 50000
if #desiredAccessories > 0 and playerData.gold >= cost then
local playerAccessoryData = utilities.copyTable(playerData.accessories)
for _, desiredAccessory in pairs(desiredAccessories) do
local category = game.ReplicatedStorage.accessoryLookup:FindFirstChild(desiredAccessory.accessory) or
game.ReplicatedStorage.accessoryLookup:FindFirstChild(string.gsub(desiredAccessory.accessory, "Id", ""))
if category and category:FindFirstChild(tostring(desiredAccessory.value)) then
playerAccessoryData[desiredAccessory.accessory] = desiredAccessory.value
else
return false, "Invalid accessory request"
end
end
playerData.nonSerializeData.setPlayerData("accessories", playerAccessoryData)
playerData.nonSerializeData.incrementPlayerData("gold", -50000)
return true
end
return false, "Not enough money or no accessory choice"
end
return false, "Invalid source"
end
return false, "No source provided"
end
return false, "Player data not found"
end
--[[
INVENTORY
--]]
local CATEGORIES = {"equipment"; "consumable"; "miscellaneous"}
local MAX_NUMBER_SLOTS_PER_CATEGORY = 20
local MAX_COUNT_PER_STACK = 99
local MAX_STORAGE_COUNT = 20
local function onGetPlayerEquipment(client, player)
if player and typeof(player) == "Instance" and player:IsA("Player") then
-- this is important, yield for it if the data isn't there.
if not playerDataContainer[player] then
while not playerDataContainer[player] do
wait(0.1)
end
end
return playerDataContainer[player].equipment
end
end
local function performDeathToRenderCharacter(player)
local previousCharacter = player.Character
local serverHitbox = previousCharacter.PrimaryPart
if serverHitbox.state.Value ~= "dead" then
player.isPlayerSpawning.Value = true
player.playerSpawnTime.Value = os.time()
serverHitbox.state.Value = "dead"
local killer
if serverHitbox:FindFirstChild("killingBlow") and serverHitbox.killingBlow:FindFirstChild("source") then
killer = serverHitbox.killingBlow.source.Value
end
network:fire("playerCharacterDied", player, previousCharacter, killer)
events:fireEventLocal("entityManifestDied", serverHitbox)
if serverHitbox.health.Value <= serverHitbox.maxHealth.Value * -3 then
utilities.playSound("DEATH", serverHitbox)
else
utilities.playSound("kill", serverHitbox)
end
local respawnTime = 7
if game.ReplicatedStorage:FindFirstChild("respawnTime") then
respawnTime = game.ReplicatedStorage.respawnTime.Value
end
local respawnType do
if ReplicatedStorage:FindFirstChild("safeZone") or (game.PlaceId == 2061558182) then
respawnType = "normal"
elseif ReplicatedStorage:FindFirstChild("overrideDeathBehavior") then
respawnType = "custom"
else
respawnType = "dangerous"
end
end
local tombstoneDuration = 300
if not ReplicatedStorage:FindFirstChild("safeZone") then
delay(3, function()
local tombstoneTag = player:FindFirstChild("tombstone")
if tombstoneTag == nil then
tombstoneTag = Instance.new("ObjectValue")
tombstoneTag.Name = "tombstone"
tombstoneTag.Parent = player
end
if tombstoneTag.Value then
tombstoneTag.Value:Destroy()
end
local tombstone = assetsFolder.entities.tombstone:Clone()
tombstone.CanCollide = false
local offset = Vector3.new(0, previousCharacter.PrimaryPart.Size.Y / 2, 0)
local targetPosition = (previousCharacter.PrimaryPart.CFrame - offset).Position
local rayDown = Ray.new(previousCharacter.PrimaryPart.Position, Vector3.new(0,-50,0))
local hitPart, hitPosition = workspace:FindPartOnRayWithIgnoreList(rayDown, {
workspace.placeFolders, workspace.CurrentCamera
}, false, true)
if hitPart and hitPosition then
targetPosition = hitPosition + Vector3.new(0, tombstone.Size.Y / 2.1, 0)
end
tombstone.CFrame = previousCharacter.PrimaryPart.CFrame - previousCharacter.PrimaryPart.Position + targetPosition
game.Debris:AddItem(tombstone, tombstoneDuration)
end)
end
if respawnType == "dangerous" then
network:fireClient("deathGuiRequested", player)
local tag = Instance.new("BoolValue")
tag.Name = "awaitingDeathGuiResponse"
tag.Value = true
tag.Parent = player
local playerStillDead = true
local timer = 70
while timer > 0 do
timer = timer - wait(1)
-- if player is alive, stop this killswitch
local character = player.Character
if character then
local manifest = character.PrimaryPart
if manifest then
local state = manifest:FindFirstChild("state")
if state and state.Value ~= "dead" then
playerStillDead = false
break
end
end
end
end
if player and player.Parent then
tag:Destroy()
if playerStillDead then
onDeathGuiAccepted(player)
end
end
end
if respawnType == "normal" then
delay(respawnTime, function()
if player then
local character = player.Character
if character then
-- skip out on respawning if they're not dead
local manifest = character.PrimaryPart
if manifest then
local state = manifest:FindFirstChild("state")
if state and state.Value ~= "dead" then
return
end
end
-- otherwise we should go on
player.Character:Destroy()
end
player:LoadCharacter()
end
end)
end
end
end
local function onCharacterHealthChanged(player, healthValue)
if healthValue <= 0 then
performDeathToRenderCharacter(player)
end
end
local function applyMaxHealth(player, dontHeal)
local playerData = playerDataContainer[player]
if playerData and player.Character and player.Character.PrimaryPart then
local stats = playerData.nonSerializeData.statistics_final
local oldMaxHealth = player.Character.PrimaryPart.maxHealth.Value
local oldMaxMana = player.Character.PrimaryPart.maxMana.Value
--[[
player.Character.PrimaryPart.maxMana.Value = 15 + (3 * playerData.level) + (5 * stats.int)
player.Character.PrimaryPart.maxHealth.Value = 75 + (25 * playerData.level) + (10 * stats.vit)
]]
player.Character.PrimaryPart.maxMana.Value = stats.maxMana
player.Character.PrimaryPart.maxHealth.Value = stats.maxHealth
local newHealth = player.Character.PrimaryPart.maxHealth.Value
local newMana = player.Character.PrimaryPart.maxMana.Value
if dontHeal then
newHealth = player.Character.PrimaryPart.health.Value + (newHealth - oldMaxHealth)
newMana = player.Character.PrimaryPart.mana.Value + (newMana - oldMaxMana)
end
player.Character.PrimaryPart.maxStamina.Value = stats.stamina or 0
player.Character.PrimaryPart.health.Value = math.clamp(newHealth, 0, player.Character.PrimaryPart.maxHealth.Value)
player.Character.PrimaryPart.mana.Value = math.clamp(newMana, 0, player.Character.PrimaryPart.maxMana.Value)
end
end
local function onPlayerRequest_returnToMainMenu(player)
game:GetService("TeleportService"):Teleport(2376885433, player, {
teleportReason = "Heading back to the main menu..."
}, game.ReplicatedStorage.returnToLobby)
end
local function onPlayerRequest_respawnMyCharacter(player)
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart:FindFirstChild("health") then
if player.Character.PrimaryPart.health.Value > 0 and player.Character.PrimaryPart.state.Value ~= "dead" then
if not game.ReplicatedStorage:FindFirstChild("safeZone") then
local isNight = game.Lighting.ClockTime < 5.9 or game.Lighting.ClockTime > 18.6
local messages = {
"got themselves into a sticky situation";
"is not having a good time right now";
"pondered the meaning of life a little too hard";
"wants to go home right now";
"tripped over a pebble";
"forgot how to breathe";
"is just taking a long nap... right guys?";
"ceased to exist";
"ignored the warning";
"used the stones to destroy... uh... themselves.";
"didn't wash their hands for 20 seconds";
"evaporated into thin air";
"suddenly stopped being alive";
"pressed the wrong button";
isNight and "stared directly at the moon" or "stared directly into the sun"
}
local message = messages[math.random(1,#messages)]
local text = "☠ " .. player.Name .. " " .. message .. " ☠"
network:fireAllClients("signal_alertChatMessage", {
Text = text,
Font = Enum.Font.SourceSansBold,
Color = Color3.fromRGB(255, 130, 100),
})
end
player.Character.PrimaryPart.health.Value = 0
end
else
if not game.ReplicatedStorage:FindFirstChild("noRespawns") then
player:LoadCharacter()
end
end
end
local statsToProcessWithFormula = {"dex"; "int"; "str"; "vit"}
local function generatecompletePlayerStats(player, isInitializing, playerData)
if not isInitializing and (not player or not playerDataContainer[player]) then return nil end
playerData = playerData or playerDataContainer[player]
local baseStats = {}
local baseStatsAdditive = {}
local baseStatsMultiplicative = {}
local totalStatsMultiplicative = {}
local equipmentDefense = 0
local equipmentDamage = 0
local function incorporateModifierData(modifierData)
for _, modifier in pairs(modifierData) do
for stat, statValue in pairs(modifier) do
if stat == "defense" then
equipmentDefense = equipmentDefense + statValue
elseif stat == "damage" or stat == "baseDamage" then
equipmentDamage = equipmentDamage + statValue
else
baseStats[stat] = (baseStats[stat] or 0) + statValue
end
end
end
--[[
for ii, stat in pairs(statsToProcessWithFormula) do
if modifierData[stat] then
baseStats[stat] = baseStats[stat] + modifierData[stat]
elseif modifierData[stat .. "_baseAdditive"] then
baseStatsAdditive[stat] = baseStatsAdditive[stat] + modifierData[stat .. "_baseAdditive"]
elseif modifierData[stat .. "_baseMultiplicative"] then
baseStatsMultiplicative[stat] = baseStatsMultiplicative[stat] + modifierData[stat .. "_baseMultiplicative"]
elseif modifierData[stat .. "_totalAdditive"] then
totalStatsAdditive[stat] = totalStatsAdditive[stat] + modifierData[stat .. "_totalAdditive"]
elseif modifierData[stat .. "_totalMultiplicative"] then
totalStatsMultiplicative[stat] = totalStatsMultiplicative[stat] + modifierData[stat .. "_totalMultiplicative"]
end
end
if modifierData.defense then
equipmentDefense = equipmentDefense + modifierData.defense
end
if modifierData.baseDamage or modifierData.damage then
equipmentDamage = equipmentDamage + (modifierData.baseDamage or modifierData.damage)
end
]]
end
-- set defaults for each stat
for _, stat in pairs(statsToProcessWithFormula) do
baseStats[stat] = 0
baseStatsAdditive[stat] = 0
baseStatsMultiplicative[stat] = 1
totalStatsMultiplicative[stat] = 1
end
-- apply the stats
local completePlayerStats = {}
for i, stat in pairs(statsToProcessWithFormula) do
-- fetch the base stat in the player data container file
baseStats[stat] = playerData.statistics[stat] or 0
baseStatsAdditive[stat] = baseStatsAdditive[stat] or 0
baseStatsMultiplicative[stat] = baseStatsMultiplicative[stat] or 1
totalStatsMultiplicative[stat] = totalStatsMultiplicative[stat] or 1
end
-- statusEffectsV2
local activeStatusEffects = network:invoke("getStatusEffectsOnEntityManifestByEntityGUID", player.entityGUID.Value)
for _, activeStatusEffectData in pairs(activeStatusEffects) do
if activeStatusEffectData.statusEffectModifier and activeStatusEffectData.statusEffectModifier.modifierData then
local modifierData = activeStatusEffectData.statusEffectModifier.modifierData
incorporateModifierData({modifierData})
end
end
local equipAttackSpeed = 3
-- calculate from what the player is currently wearing
local mainhandWeaponData, offhandWeaponData
for _, equipmentSlotData in pairs(playerData.equipment) do
local itemBaseData = itemLookup[equipmentSlotData.id]
-- add in base data
if itemBaseData then
if itemBaseData.modifierData then
incorporateModifierData(itemBaseData.modifierData)
end
local slot = equipmentSlotData.position
local isWeapon = false
if slot == 1 then
mainhandWeaponData = itemBaseData
isWeapon = true
if itemBaseData.attackSpeed then
equipAttackSpeed = itemBaseData.attackSpeed
end
-- only swords in the offhand count as weapons
elseif slot == 11 and itemBaseData.equipmentType == "sword" then
offhandWeaponData = itemBaseData
isWeapon = true
-- ignore bows and daggers in the offhand too
-- but don't save their data because we aren't
-- averaging the bows and daggers together
elseif slot == 11 and (itemBaseData.equipmentType == "bow" or itemBaseData.equipmentType == "dagger") then
isWeapon = true
-- ignore fishing rods? why are these considered weapons
elseif slot == 11 and itemBaseData.equipmentType == "fishing-rod" then
isWeapon = true
end
-- we'll calculate weapon damage later because dual wielding is wacky
if (not isWeapon) and (itemBaseData.baseDamage or itemBaseData.damage) then
equipmentDamage = equipmentDamage + (itemBaseData.baseDamage or itemBaseData.damage)
end
-- enchantments! :P
local statUpgrade = 0
if equipmentSlotData.enchantments then
for _, enchantmentData in pairs(equipmentSlotData.enchantments) do
local enchantmentBaseData = itemLookup[enchantmentData.id]
if enchantmentBaseData.enchantments then
local enchantmentState = enchantmentBaseData.enchantments[enchantmentData.state]
if enchantmentState then
if enchantmentState.modifierData then
incorporateModifierData({enchantmentBaseData.enchantments[enchantmentData.state].modifierData})
end
if enchantmentState.statUpgrade then
statUpgrade = statUpgrade + enchantmentState.statUpgrade
end
end
end
end
end
-- variable stat-enhancing enchants (mainly for hats!)
if statUpgrade > 0 and itemBaseData.statUpgrade then
local statUpgradeModifierData = {}
for stat, value in pairs(itemBaseData.statUpgrade) do
if value ~= 0 then
statUpgradeModifierData[stat] = value * statUpgrade
end
end
incorporateModifierData({statUpgradeModifierData})
end
-- attributes! :D
if equipmentSlotData.attribute then
local attributeData = itemAttributes[equipmentSlotData.attribute]
if attributeData and attributeData.modifier then
local attributeModifierData = attributeData.modifier(itemBaseData, equipmentSlotData)
if attributeModifierData then
incorporateModifierData({attributeModifierData})
end
end
end
end
if equipmentSlotData.modifierData then
incorporateModifierData(equipmentSlotData.modifierData)
end
end
-- calculate the damage of weapons because dual wielding is wacky
if mainhandWeaponData and offhandWeaponData then
local damage = (
(mainhandWeaponData.baseDamage or 0) + (mainhandWeaponData.damage or 0) +
(offhandWeaponData.baseDamage or 0) + (offhandWeaponData.damage or 0)
) / 2
equipmentDamage = equipmentDamage + damage
elseif mainhandWeaponData and (not offhandWeaponData) then
local damage = (mainhandWeaponData.baseDamage or 0) + (mainhandWeaponData.damage or 0)
equipmentDamage = equipmentDamage + damage
end
-- compile the stats
for stat, value in pairs(baseStats) do
completePlayerStats[stat] = value
end
-- post def-negation buffs
completePlayerStats.damageTakenMulti = 1 + (baseStats.damageTakenMulti or 0)
completePlayerStats.damageGivenMulti = 1 + (baseStats.damageGivenMulti or 0)
-- unique stats are calculated differently
completePlayerStats.equipmentDefense = equipmentDefense --+ playerData.level
completePlayerStats.defense = equipmentDefense --+ playerData.level
completePlayerStats.equipmentDamage = (equipmentDamage + (baseStats.equipmentDamage or 0)) --+ playerData.level
completePlayerStats.damage = completePlayerStats.equipmentDamage --+ playerData.level
completePlayerStats.physicalDamage = completePlayerStats.equipmentDamage --+ playerData.level
completePlayerStats.magicalDamage = completePlayerStats.equipmentDamage --+ playerData.level
completePlayerStats.physicalDefense = completePlayerStats.equipmentDefense --+ playerData.level
completePlayerStats.magicalDefense = completePlayerStats.equipmentDefense --+ playerData.level
completePlayerStats.stamina = 3 + (baseStats.stamina or 0)
completePlayerStats.staminaRecovery = 1 + (baseStats.staminaRecovery or 0)
-- previous: 75 + 25*lvl * 1/100*VIT
-- new: 50 + 25*lvl
local level = playerData.level
local vit = completePlayerStats.vit
local int = completePlayerStats.int
-- health
local HP = 15 + (5 * level)
HP = HP + (5 * vit)
if baseStats.maxHealth then
HP = HP + baseStats.maxHealth
end
completePlayerStats.maxHealth = math.ceil(HP)
-- mana
local MP = 3 + (2 * level)
MP = MP + 1 * int
if baseStats.maxMana then
MP = MP + baseStats.maxMana
end
completePlayerStats.maxMana = math.ceil(MP)
local manaRegenMulti = 1
if baseStats.manaRegen then
manaRegenMulti = manaRegenMulti + baseStats.manaRegen/100
end
completePlayerStats.manaRegen = (1.0 + 0.035 * playerData.level + 0.050 * completePlayerStats.int) * manaRegenMulti
local healthRegenMulti = 1
if baseStats.healthRegen then
healthRegenMulti = healthRegenMulti + baseStats.healthRegen/100
end
completePlayerStats.healthRegen = (0.5 + 0.240 * playerData.level + 0.100 * completePlayerStats.vit) * healthRegenMulti
completePlayerStats.jump = 75 + (baseStats.jump or 0)
completePlayerStats.consumeTimeReduction = 0 + (baseStats.consumeTimeReduction or 0)
completePlayerStats.attackSpeed = 0 + (equipAttackSpeed - 3)/3.5
completePlayerStats.woodcutting = math.max(baseStats.woodcutting or 1, 1)
completePlayerStats.mining = math.max(baseStats.mining or 1, 1)
completePlayerStats.criticalStrikeChance = ((0.5 / 100) / 3) * completePlayerStats.dex + (baseStats.criticalStrikeChance or 0)
completePlayerStats.blockChance = math.clamp(0.20 * (completePlayerStats.dex / (3 * playerData.level)), 0, 1) + (baseStats.blockChance or 0)
-- how much damage critical strikes do (decimal multiplier)
-- 2 means 200%, not just "2".
completePlayerStats.criticalStrikeDamage = 2 + (baseStats.criticalStrikeDamage or 0)
-- how much more gold you get (this is a multipler, so 1 is just 100% of the
-- regular value you'd get, anything higher is considered an increase)
completePlayerStats.greed = 1 + (baseStats.greed or 0)
-- how much more exp you get (this is a multipler, so 1 is just 100% of the
-- regular value you'd get, anything higher is considered an increase)
completePlayerStats.wisdom = 1 + (baseStats.wisdom or 0)
-- increases chance of soulbound items being given to you
completePlayerStats.luck = 1 + (baseStats.luck or 0)
-- flat movement speed of player
completePlayerStats.walkspeed = 18 + (baseStats.walkspeed or 0)
-- merchantCostReduction (1 = 100% reduction (1 gold minimum))
completePlayerStats.merchantCostReduction = 0
-- ability cool down reduction (1 = 100% reduction)
completePlayerStats.abilityCDR = 0
-- additive bonus (1 + attackRangeIncrease, therefore 1 = 200% increase)
completePlayerStats.attackRangeIncrease = 0
-- additive bonus (1 + consumableHealthIncrease, therefore 1 = 200% increase)
completePlayerStats.consumableHealthIncrease = 0
completePlayerStats.consumableManaIncrease = 0
-- apply multiplicative bonuses?
for stat, value in pairs(completePlayerStats) do
local multiplicative = baseStats[stat.."_totalMultiplicative"]
if multiplicative then
completePlayerStats[stat] = value * (1 + multiplicative)
end
end
-- apply perks here
local activePerks = {}
-- add perks from equipment
for i, equipmentSlotData in pairs(playerData.equipment) do
local itemBaseData = itemLookup[equipmentSlotData.id]
-- add in base data
if itemBaseData.perks then
for perkName, perkData in pairs(itemBaseData.perks) do
activePerks[perkName] = true
end
end
if equipmentSlotData.perks then
for perkName, perkData in pairs(equipmentSlotData.perks) do
activePerks[perkName] = true
end
end
end
-- run conditions and apply perks as needed
for perkName, perk in pairs(perkLookup) do
local condition = perk.condition and perk.condition(completePlayerStats)
-- figured this would be easier to do at the manager level
if perk.class and isPlayerOfClass(player, perk.class) then
condition = true
end
if condition or activePerks[perkName] then
activePerks[perkName] = true
if perk.apply then
perk.apply(completePlayerStats)
end
else
activePerks[perkName] = false
end
end
completePlayerStats.activePerks = activePerks
playerData.nonSerializeData.statistics_final = completePlayerStats
applyMaxHealth(player, true)
if player then
for i, stat in pairs(statsToProcessWithFormula) do
local valueObject = player:FindFirstChild(stat)
if valueObject == nil then
valueObject = Instance.new("IntValue")
valueObject.Name = stat
valueObject.Parent = player
end
valueObject.Value = completePlayerStats[stat]
end
if playerDataContainer[player] then
network:fireClient("playerStatisticsChanged", player, playerData.statistics, completePlayerStats)
playerData.nonSerializeData.playerDataChanged:Fire("nonSerializeData")
end
end
return completePlayerStats
end
local function isStartingValueRemoved(primaryPart, child)
if primaryPart.Parent == nil then
return false
end
return not not game.StarterPlayer.StarterCharacter.PrimaryPart:FindFirstChild(child.Name)
end
local RESPAWN_POINT_RADIUS = 32
local RESPAWN_POINT_RADIUS_SQ = RESPAWN_POINT_RADIUS ^ 2
local function checkForRespawnPoint(player)
local char = player.Character
if not char then return end
local root = char.PrimaryPart
if not root then return end
local respawnPoint = player:FindFirstChild("respawnPoint")
if not respawnPoint then return end
local spawnPoints = game.ReplicatedStorage.spawnPoints:GetChildren()
local bestSpawnPoint = nil
local bestDistanceSq = RESPAWN_POINT_RADIUS_SQ
local function checkSpawnPoint(spawnPoint)
if not spawnPoint:FindFirstChild("description") then return end
if spawnPoint:FindFirstChild("ignore") then return end
local delta = spawnPoint.Value.Position - root.Position
local distanceSq = delta.X * delta.X + delta.Y * delta.Y + delta.Z * delta.Z
if distanceSq <= bestDistanceSq then
bestSpawnPoint = spawnPoint
bestDistanceSq = distanceSq
end
end
for _, spawnPoint in pairs(spawnPoints) do
checkSpawnPoint(spawnPoint)
end
if bestSpawnPoint and (respawnPoint.Value ~= bestSpawnPoint) then
respawnPoint.Value = bestSpawnPoint
local spawnText = bestSpawnPoint:FindFirstChild("description") and bestSpawnPoint.description.Value
if spawnText then
local playerData = playerDataContainer[player]
local placeData = playerData.locations[tostring(game.PlaceId)]
placeData.spawns[bestSpawnPoint.Name] = {text = spawnText}
playerData.nonSerializeData.playerDataChanged:Fire("locations")
end
events:fireEventPlayer("playerRespawnPointChanged", player, respawnPoint.Value)
end
end
local function onCharacterAdded(player, character)
while character.Parent ~= entityManifestCollectionFolder do
wait(0.1)
character.Parent = entityManifestCollectionFolder
end
generatecompletePlayerStats(player, false, playerDataContainer[player])
if character:WaitForChild("hitbox", 10) then
character.PrimaryPart = character.hitbox
local VALUE_OBJECT_EXPLOITER_DELETION_CHECK do
local primaryPartForCheck = character.PrimaryPart
for i, obj in pairs(game.StarterPlayer.StarterCharacter.PrimaryPart:GetChildren()) do
if not character.PrimaryPart:FindFirstChild(obj.Name) then
player:Kick("Anti-exploit")
-- game:GetService("TeleportService"):Teleport(2376885433, player, nil, game.ReplicatedStorage.returnToLobby)
end
end
local function onChildRemoved(child)
if isStartingValueRemoved(primaryPartForCheck, child) then
player:Kick("Anti-exploit")
-- game:GetService("TeleportService"):Teleport(2376885433, player, nil, game.ReplicatedStorage.returnToLobby)
end
end
character.PrimaryPart.ChildRemoved:connect(onChildRemoved)
end
network:fire("playerCharacterLoaded", player, character)
local success = pcall(function() character.PrimaryPart:SetNetworkOwner(player) end) do
while not success do
wait(0.5)
success = pcall(function() character.PrimaryPart:SetNetworkOwner(player) end)
end
end
applyMaxHealth(player)
local playerData = playerDataContainer[player]
if playerData and playerData.condition then
local manifest = character.PrimaryPart
if not manifest then return end
local health = manifest.health
local mana = manifest.mana
if not (health and mana) then return end
health.Value = math.max(1, playerData.condition.health)
mana.Value = math.max(1, playerData.condition.mana)
playerData.condition = nil
end
onCharacterHealthChanged(player, player.Character.PrimaryPart.health.Value)
local alive = true
if character.PrimaryPart and player.Character.PrimaryPart.health.Value > 0 then
player.Character.PrimaryPart.health.Changed:connect(function(newHealth)
onCharacterHealthChanged(player, newHealth)
if newHealth <= 0 then
alive = false
end
end)
end
local health_regenPool = 0
local mana_regenPool = 0
local playerData = playerDataContainer[player]
spawn(function()
while true do
local delta = wait(1)
if configuration.getConfigurationValue("server_doRedirectMaxHealthDeletions") and character.PrimaryPart and not character.PrimaryPart:FindFirstChild("maxHealth") then
player:Kick("Anti-exploit")
-- game:GetService("TeleportService"):Teleport(2376885433, player, nil, game.ReplicatedStorage.returnToLobby)
end
checkForRespawnPoint(player)
if alive and character and character.PrimaryPart and player.Character == character and playerData and playerData.nonSerializeData then
local isInCombat = (tick() - (playerData.nonSerializeData.lastTimeInCombat or 0)) <= 4
local stats = playerData.nonSerializeData.statistics_final
local multiplier_mana = 1
local multiplier_health = 1
local state = player.Character.PrimaryPart:FindFirstChild("state")
if state and state.Value == "sitting" and player.Character.PrimaryPart.Anchored then
multiplier_mana = multiplier_mana * 2
multiplier_health = multiplier_health * 2
end
if isInCombat then
multiplier_health = multiplier_health * 0
multiplier_mana = multiplier_mana * 0.5
end
if state and state.Value == "dead" then
multiplier_health = 0
multiplier_mana = 0
end
local healthGainFromThisTick = stats.healthRegen * delta * multiplier_health * 0.5
local healthGain = health_regenPool + healthGainFromThisTick
local healthToGivePlayer = math.floor(healthGain)
if healthToGivePlayer > 0 then
character.PrimaryPart.health.Value = math.clamp(character.PrimaryPart.health.Value + healthToGivePlayer, 0, character.PrimaryPart.maxHealth.Value)
end
local manaGainFromThisTick = stats.manaRegen * delta * multiplier_mana * 0.5
local manaGain = mana_regenPool + manaGainFromThisTick
local manaToGivePlayer = math.floor(manaGain)
if manaToGivePlayer > 0 then
character.PrimaryPart.mana.Value = math.clamp(character.PrimaryPart.mana.Value + manaToGivePlayer, 0, character.PrimaryPart.maxMana.Value)
end
health_regenPool = healthGain - healthToGivePlayer
mana_regenPool = manaGain - manaToGivePlayer
else
break
end
end
end)
-- when we stand on death traps, be... uh, death trapped.
local manifest = character.PrimaryPart
local deathTrapNext = 0
local deathTrapTime = 1
local collisionWiggle = Vector3.new(1, 1, 1) * 0.2
local conn = nil
local lastDeathTrap
local function getDeathTrapKillMessage(part)
local text = "☠ " .. player.Name .. " was killed by "..part.Name.." ☠"
local message = part:FindFirstChild("message")
if message then
text = message.Value
text = string.gsub(text, "%[playerName]", player.Name)
text = string.gsub(text, "%[trapName]", part.Name)
text = "☠ "..text.." ☠"
end
return text
end
local function applyDeathTrap(part, now)
deathTrapNext = now + deathTrapTime
if part:FindFirstChild("cooldown") then
deathTrapNext = now + part.cooldown.Value
end
-- is this a status effect trap?
if part:FindFirstChild("statusEffect") then
local statusName = part.statusEffect.Value
local function getArgs(object)
local args = {}
for _, child in pairs(object:GetChildren()) do
if child:IsA("Folder") then
args[child.Name] = getArgs(child)
else
args[child.Name] = child.Value
end
end
return args
end
local args = getArgs(part.statusEffect)
network:invoke("applyStatusEffectToEntityManifest",
manifest,
statusName,
args,
manifest,
"trap",
0
)
-- drop a special tag so that the kill notification system knows what to say
-- in the case where we "kill ourselves" and it was actually due to a trap
local tag = Instance.new("StringValue")
tag.Name = "deathTrapKillMessage"
tag.Value = getDeathTrapKillMessage(part)
tag.Parent = player
game:GetService("Debris"):AddItem(tag, args.duration or 1)
end
-- how much damage?
local damage
if part:FindFirstChild("damage") then
damage = part.damage.Value
elseif part:FindFirstChild("percentDamage") then
local maxHealth = manifest:FindFirstChild("maxHealth")
if maxHealth then
damage = maxHealth.Value * part.percentDamage.Value
end
end
if damage then
local health = manifest:FindFirstChild("health")
if not health then return end
if health.Value <= 0 then return end
health.Value = math.max(0, health.Value - damage)
-- play a sound
local injurySound = Instance.new("Sound")
injurySound.SoundId = "rbxassetid://2065833626"
injurySound.MaxDistance = 1000
injurySound.Volume = 1.5
injurySound.EmitterSize = 5
injurySound.Parent = manifest
injurySound:Play()
game:GetService("Debris"):AddItem(injurySound, injurySound.TimeLength)
-- potentially show a message
if health.Value <= 0 then
network:fireAllClients("signal_alertChatMessage", {
Text = getDeathTrapKillMessage(part),
Font = Enum.Font.SourceSansBold,
Color = Color3.fromRGB(255, 130, 100),
})
end
end
-- any knockback?
if part:FindFirstChild("knockback") then
--"deathTrapKnockback"
local nearestPoint = detection.projection_Box(part.CFrame, part.Size, manifest.Position)
local delta = manifest.Position - nearestPoint
network:fireClient("deathTrapKnockback", player, delta.Unit * part.knockback.Value)
end
end
local function getIsTouchingDeathTrap(part)
-- fast distance check
local delta = manifest.Position - part.Position
local manhattan = math.max(math.abs(delta.X), math.abs(delta.Y), math.abs(delta.Z))
local range = math.max(part.Size.X, part.Size.Y, part.Size.Z)
if manhattan > range then return false end
-- slower collision check
-- we need to add a bit to the top and bottom of our character because the
-- hit box as it exists now doesn't fully cover the head and feet
local charCFrame = manifest.CFrame
local charSize = manifest.Size + collisionWiggle + Vector3.new(0, 2, 0)
local castPosition = detection.projection_Box(charCFrame, charSize, part.Position)
if not detection.boxcast_singleTarget(part.CFrame, part.Size, castPosition) then return false end
return true
end
local function checkDeathTrap(part, now)
if not getIsTouchingDeathTrap(part) then return end
if part:FindFirstChild("reference") then
part = part.reference.Value
if not part then return end
end
lastDeathTrap = part
if part:FindFirstChild("onTouched") then
part = part.onTouched
end
applyDeathTrap(part, now)
end
local function checkForOnTouchEnded(part)
if getIsTouchingDeathTrap(part) then return end
local part = lastDeathTrap
lastDeathTrap = nil
if part:FindFirstChild("onTouchEnded") then
applyDeathTrap(part.onTouchEnded, tick())
end
end
local function checkForDeathTraps()
if (not manifest.Parent) or (manifest.Parent ~= player.Character) then
conn:Disconnect()
end
if lastDeathTrap then
checkForOnTouchEnded(lastDeathTrap)
end
local now = tick()
if now < deathTrapNext then return end
local origin = manifest.Position - Vector3.new(0, manifest.Size.Y / 2, 0)
local direction = Vector3.new(0, -2, 0)
local parts = CollectionService:GetTagged("deathTrap")
for _, part in pairs(parts) do
local hitATrap = checkDeathTrap(part, now)
if hitATrap then
break
end
end
end
conn = runService.Heartbeat:Connect(checkForDeathTraps)
physics:setWholeCollisionGroup(character, "characters")
local entity = character.PrimaryPart
--[[
entity:WaitForChild("state")
while character and character.Parent do
if entity.state.Value == "sprinting" then
entity.stamina.Value = math.max(entity.stamina.Value - 0.2, 0)
elseif entity.stamina.Value < entity.maxStamina.Value then
entity.stamina.Value = math.min(entity.stamina.Value + 0.2, entity.maxStamina.Value)
end
wait(0.2)
end
]]
end
end
local function forceCharacterPosition(player, cf)
if player.Character and player.Character.PrimaryPart then
player.Character:SetPrimaryPartCFrame(cf)
end
end
local function onClientRequestFlushPropogationCache(client)
if not playerDataContainer[client] then
while not playerDataContainer[client] do
wait()
end
end
if playerDataContainer[client] then
local propogationCacheLookupTable = {}
for propogationNameTag, propogationValue in pairs(playerDataContainer[client]) do
--if propogationNameTag ~= "nonSerializeData" then
propogationCacheLookupTable[propogationNameTag] = propogationValue
--end
end
network:fireClient("clientFlushPropogationCache", client, propogationCacheLookupTable)
end
end
local function replicatePlayerCharacterAppearance(player, playerData)
playerData = playerData or playerDataContainer[player]
if playerData then
local characterAppearanceData = {}
characterAppearanceData.equipment = playerData.equipment
characterAppearanceData.accessories = playerData.accessories
characterAppearanceData.temporaryEquipment = playerData.nonSerializeData.temporaryEquipment
if player.Character and player.Character.PrimaryPart then
player.Character.PrimaryPart.appearance.Value = HttpService:JSONEncode(characterAppearanceData)
end
end
end
local function onPlayerRequest_equipTemporaryEquipment(player, temporaryEquipmentModel)
local playerData = playerDataContainer[player]
if playerData then
if temporaryEquipmentModel and temporaryEquipmentModel:IsDescendantOf(temporaryEquipmentFolder) then
if not playerData.nonSerializeData.temporaryEquipment[temporaryEquipmentModel.Name] then
playerData.nonSerializeData.temporaryEquipment[temporaryEquipmentModel.Name] = true
-- force update player appearance
replicatePlayerCharacterAppearance(player, playerData)
return true
end
end
end
return false
end
local function replicatePlayerCharacterActiveStatusEffects(player, playerData)
if player:FindFirstChild("entityGUID") then
network:invoke("updateStatusEffectsForEntityManifestByEntityGUID", player.entityGUID.Value)
end
end
local function getPropogationCacheLookupTable(client)
if game.PlaceId == 2376885433 or game.PlaceId == 3323943158 or game.PlaceId == 2015602902 then
return {}
end
if not playerDataContainer[client] then
while not playerDataContainer[client] do
wait()
end
end
local propogationCacheLookupTable = {}
for propogationNameTag, propogationValue in pairs(playerDataContainer[client]) do
--if propogationNameTag ~= "nonSerializeData" then
propogationCacheLookupTable[propogationNameTag] = propogationValue
--end
end
return propogationCacheLookupTable
end
local function onClientRequestPropogateCacheData(client, propogationNameTag)
if not playerDataContainer[client] then
while not playerDataContainer[client] do
wait(0.1)
end
end
if playerDataContainer[client] then
network:fireClient("propogateCacheDataRequest", client, propogationNameTag, playerDataContainer[client][propogationNameTag])
end
end
local function getAvailableSlot(slots)
for slotId, isSlotOpen in pairs(slots) do
if isSlotOpen then
return slotId
end
end
return nil
end
local function validateEquipmentSlots(playerEquipment)
-- iterate through playerEquipment and make sure its all good
while true do
local hasMadeChange = false
for i, equipmentSlotData in pairs(playerEquipment) do
local itemBaseData = itemLookup[equipmentSlotData.id]
if itemBaseData.equipmentSlot ~= equipmentSlotData.position then
hasMadeChange = true
table.remove(playerEquipment, i)
break
end
end
if not hasMadeChange then
break
end
end
end
-- validates and builds intermediate transfer data
-- second return is true IF AND ONLY IF the player is missing the
-- items stipulated
local function int__getInventoryTransferData_intermediateCollectionFromInventoryTransferDataCollection(player, inventoryTransferDataCollection)
local playerData = playerDataContainer[player]
if playerData then
local inventoryTransferData_intermediateCollection = {}
local wasInventoryTransferDataModified = false
local isDataMalformed = false
local successful_conversions = 0
local inventoryTransferDataCollection_list = utilities.copyTable(inventoryTransferDataCollection)
for i, inventoryTransferData in pairs(inventoryTransferDataCollection_list) do
local itemBaseData = itemLookup[inventoryTransferData.id]
if itemBaseData and (inventoryTransferData.stacks or 1) > 0 then
for trueInventorySlotPosition, inventorySlotData in pairs(playerData.inventory) do
if inventorySlotData.id == inventoryTransferData.id then
if itemBaseData.canStack then
if inventoryTransferData.stacks > 0 then
local stacksToRemove = math.clamp(inventoryTransferData.stacks, 1, inventorySlotData.stacks or 1)
local inventoryTransferData_intermediate = {}
inventoryTransferData_intermediate.id = inventoryTransferData.id
inventoryTransferData_intermediate.position = inventorySlotData.position
-- copy over the item metadata
for metadataName, metadataValue in pairs(inventorySlotData) do
if not inventoryTransferData_intermediate[metadataName] then
if type(metadataValue) == "table" then
inventoryTransferData_intermediate[metadataName] = utilities.copyTable(metadataValue)
else
inventoryTransferData_intermediate[metadataName] = metadataValue
end
end
end
inventoryTransferData_intermediate.stacks = stacksToRemove
-- flag this as taken!
inventoryTransferData.stacks = inventoryTransferData.stacks - stacksToRemove
table.insert(inventoryTransferData_intermediateCollection, inventoryTransferData_intermediate)
if inventoryTransferData.stacks == 0 then
break
end
end
else
if inventorySlotData.position == inventoryTransferData.position then
local inventoryTransferData_intermediate = {}
inventoryTransferData_intermediate.id = inventoryTransferData.id
inventoryTransferData_intermediate.position = inventoryTransferData.position
-- copy over the item metadata
for metadataName, metadataValue in pairs(inventorySlotData) do
if not inventoryTransferData_intermediate[metadataName] then
if type(metadataValue) == "table" then
inventoryTransferData_intermediate[metadataName] = utilities.copyTable(metadataValue)
else
inventoryTransferData_intermediate[metadataName] = metadataValue
end
end
end
table.insert(inventoryTransferData_intermediateCollection, inventoryTransferData_intermediate)
-- flag this as taken!
inventoryTransferData.stacks = 0
break
end
end
end
end
-- [{"position":2,"stacks":1,"id":3}, {"position":1,"stacks":1,"id":8}, {"position":1,"stacks":1,"id":3}]
if inventoryTransferData.stacks == 0 then
-- pop it from the table, its all good
-- table.remove(inventoryTransferDataCollection_list, i)
else
-- didn't get all the stacks for this item! deny it.
break
end
else
-- invalid item, either stacks <= 0 or id is invalid
break
end
end
local isGood = true
for i, v in pairs(inventoryTransferDataCollection_list) do
if (v.stacks or 1) > 0 then
isGood = false
end
end
-- if everything went well, #inventoryTransferDataCollection_list == 0
-- this function returns true in arg#2 if there was an error
return inventoryTransferData_intermediateCollection, not isGood--#inventoryTransferDataCollection_list > 0 --successful_conversions ~= #inventoryTransferDataCollection or isDataMalformed or wasInventoryTransferDataModified
end
return nil, true
end
-- CONTINUE NPC TRADING HERE!!!!!!
local function int__doesPlayerHaveInventorySpaceForTrade(player, inventoryTransferData_intermediateCollection_losing, inventoryTransferData_intermediateCollection_gaining)
-- reminder: respect category
local availableSlots = {}
local necessarySlots = {}
for i_category = 1, #CATEGORIES do
availableSlots[CATEGORIES[i_category]] = MAX_NUMBER_SLOTS_PER_CATEGORY
end
local playerData = playerDataContainer[player]
if playerData then
for trueInventorySlotDataPosition, inventorySlotData in pairs(playerData.inventory) do
local itemBaseData = itemLookup[inventorySlotData.id]
if itemBaseData then
availableSlots[itemBaseData.category] = availableSlots[itemBaseData.category] - 1
else
return false
end
end
end
for i, inventoryTransferData_intermediate in pairs(inventoryTransferData_intermediateCollection_losing) do
local itemBaseData = itemLookup[inventoryTransferData_intermediate.id]
if itemBaseData then
availableSlots[itemBaseData.category] = availableSlots[itemBaseData.category] + 1
else
return false
end
end
local representation_inventoryTransferData_intermediateCollection_gaining = utilities.copyTable(inventoryTransferData_intermediateCollection_gaining)
local playerInventoryClone = utilities.copyTable(playerData.inventory)
while true do
local madeChange = false
for i, inventoryTransferData_intermediate in pairs(representation_inventoryTransferData_intermediateCollection_gaining) do
local itemBaseData = itemLookup[inventoryTransferData_intermediate.id]
if not inventoryTransferData_intermediate.stacks then
inventoryTransferData_intermediate.stacks = 1
end
if itemBaseData then
if itemBaseData.canStack then
for _, playerInventorySlotData in pairs(playerInventoryClone) do
if playerInventorySlotData.id == inventoryTransferData_intermediate.id and playerInventorySlotData.stacks <= (itemBaseData.stackSize or MAX_COUNT_PER_STACK) then
local stacksToAdd = math.clamp(inventoryTransferData_intermediate.stacks, 0, (itemBaseData.stackSize or MAX_COUNT_PER_STACK) - playerInventorySlotData.stacks)
inventoryTransferData_intermediate.stacks = inventoryTransferData_intermediate.stacks - stacksToAdd
playerInventorySlotData.stacks = playerInventorySlotData.stacks + stacksToAdd
if inventoryTransferData_intermediate.stacks == 0 then
table.remove(representation_inventoryTransferData_intermediateCollection_gaining, i)
madeChange = true
break
end
end
end
end
end
if madeChange then
break
end
end
if not madeChange then
break
end
end
for i, inventoryTransferData_intermediate in pairs(representation_inventoryTransferData_intermediateCollection_gaining) do
local itemBaseData = itemLookup[inventoryTransferData_intermediate.id]
if itemBaseData then
if itemBaseData.canStack then
availableSlots[itemBaseData.category] = availableSlots[itemBaseData.category] - 1
else
availableSlots[itemBaseData.category] = availableSlots[itemBaseData.category] - inventoryTransferData_intermediate.stacks
end
necessarySlots[itemBaseData.category] = true
else
return false
end
end
-- check if player can do the trade based on their slots!
for category, _ in pairs(necessarySlots) do
-- NOTE: we are not doing <= 0 because if we do, it doesn't
-- acount for the scenario where we are at 24 slots available
-- and gaining 1 item. if we have all items full, and only gain 1
-- itll be -1 so it wont pass through.
if availableSlots[category] < 0 then
return false
end
end
return true
end
-- todo: merge into already present stacks!
local function grantPlayerItemsByInventoryTranferData_intermediateCollection(player, inventoryTransferDataCollection_g)
local playerData = playerDataContainer[player]
local inventoryTransferDataCollection = utilities.copyTable(inventoryTransferDataCollection_g)
if playerData then
local hasInventoryChanged = false
for i, inventoryTransferData in pairs(inventoryTransferDataCollection) do
local itemBaseData = itemLookup[inventoryTransferData.id]
-- set this for the lazy
if not inventoryTransferData.stacks then
inventoryTransferData.stacks = 1
end
if itemBaseData and itemBaseData.canStack then
for trueInventorySlotDataPosition, inventorySlotData in pairs(playerData.inventory) do
if inventorySlotData.id == inventoryTransferData.id then
if (inventorySlotData.stacks + inventoryTransferData.stacks) <= (itemBaseData.stackSize or MAX_COUNT_PER_STACK) then
inventorySlotData.stacks = inventorySlotData.stacks + inventoryTransferData.stacks
network:fire("questTriggerOccurred", player, "item-collected", {id = itemBaseData.id; amount = 1}) -- amount is irrelevant to the check
-- flag as finished item
inventoryTransferData.stacks = 0
-- flag as changed!
hasInventoryChanged = true
-- move to next item!
break
elseif inventorySlotData.stacks < (itemBaseData.stackSize or MAX_COUNT_PER_STACK) then
local diff = (itemBaseData.stackSize or MAX_COUNT_PER_STACK) - inventorySlotData.stacks
if inventoryTransferData.stacks >= diff then
inventorySlotData.stacks = inventorySlotData.stacks + diff
inventoryTransferData.stacks = inventoryTransferData.stacks - diff
hasInventoryChanged = true
if inventoryTransferData.stacks == 0 then
network:fire("questTriggerOccurred", player, "item-collected", {id = itemBaseData.id; amount = 1})
break
end
end
end
end
end
end
end
for _, inventoryTransferData in pairs(inventoryTransferDataCollection) do
if inventoryTransferData.stacks > 0 then
-- grant the item
local itemBaseData = itemLookup[inventoryTransferData.id]
if itemBaseData then
-- make sure the id is the integer id, not the string id
inventoryTransferData.id = itemBaseData.id
if itemBaseData.canStack then
local itemStackSize = itemBaseData.stackSize or 99
local stacksNeeded = inventoryTransferData.stacks
while stacksNeeded > 0 do
if stacksNeeded >= itemStackSize then
local itemToAdd = utilities.copyTable(inventoryTransferData)
itemToAdd.stacks = itemStackSize
stacksNeeded = stacksNeeded - itemStackSize
table.insert(playerData.inventory, itemToAdd)
else
local itemToAdd = utilities.copyTable(inventoryTransferData)
itemToAdd.stacks = stacksNeeded
stacksNeeded = 0
table.insert(playerData.inventory, itemToAdd)
end
end
inventoryTransferData.stacks = 0
network:fire("questTriggerOccurred", player, "item-collected", {id = itemBaseData.id; amount = 1})
-- flip flag
hasInventoryChanged = true
else
-- todo look to remove this for loop!
for i = 1, inventoryTransferData.stacks or 1 do
local newInventorySlotData = {
id = inventoryTransferData.id;
stacks = 1;
modifierData = inventoryTransferData.modifierData;
}
for i, v in pairs(inventoryTransferData) do
if not newInventorySlotData[i] then
if type(v) == "table" then
newInventorySlotData[i] = utilities.copyTable(v)
elseif i ~= "stacks" then
newInventorySlotData[i] = v
end
end
end
table.insert(playerData.inventory, newInventorySlotData)
network:fire("questTriggerOccurred", player, "item-collected", {id = itemBaseData.id; amount = 1})
-- flip flag
hasInventoryChanged = true
end
end
end
end
end
if hasInventoryChanged then
playerData.nonSerializeData.playerDataChanged:Fire("inventory")
end
return true
end
return false
end
-- reminder: inventoryTransferData and inventoryTransferData_intermediate can both be used. Since we don't care about modifierData
-- they're pretty much identical.
local function revokePlayerItemsByInventoryTransferDataCollection(player, inventoryTransferDataCollection_r)
local playerData = playerDataContainer[player]
local inventoryTransferDataCollection = utilities.copyTable(inventoryTransferDataCollection_r)
if playerData then
local hasInventoryChanged = false
for _, inventoryTransferData in pairs(inventoryTransferDataCollection) do
local itemBaseData = itemLookup[inventoryTransferData.id]
-- set this for the lazy
if not inventoryTransferData.stacks then
inventoryTransferData.stacks = 1
end
if itemBaseData and itemBaseData.canStack then
while inventoryTransferData.stacks > 0 do
local tookStacksThisCycle = false
for trueInventorySlotPosition, inventorySlotData in pairs(playerData.inventory) do
if inventorySlotData.id == inventoryTransferData.id then
-- drop the item
if inventorySlotData.stacks > inventoryTransferData.stacks then
inventorySlotData.stacks = inventorySlotData.stacks - inventoryTransferData.stacks
inventoryTransferData.stacks = 0
else
inventoryTransferData.stacks = inventoryTransferData.stacks - inventorySlotData.stacks
table.remove(playerData.inventory, trueInventorySlotPosition)
end
-- flip flag
hasInventoryChanged = true
tookStacksThisCycle = true
break
end
end
if not tookStacksThisCycle then
break
end
end
else
-- non-stackable item revoke, only one stack (entire item)
inventoryTransferData.stacks = 1
for trueInventorySlotPosition, inventorySlotData in pairs(playerData.inventory) do
if inventorySlotData.position == inventoryTransferData.position and inventorySlotData.id == inventoryTransferData.id then
-- revoke item
table.remove(playerData.inventory, trueInventorySlotPosition)
-- flag is taken
inventoryTransferData.stacks = 0
-- mark as changed
hasInventoryChanged = true
-- move to next item
break
end
end
end
end
if hasInventoryChanged then
playerData.nonSerializeData.playerDataChanged:Fire("inventory")
end
return true
end
return false
end
local function int__transferInventoryToStorage(player, inventorySlotData)
local playerData = playerDataContainer[player]
if playerData then
local inventoryTransferData_intermediateCollection_player, wasInventoryTransferDataModified_player = int__getInventoryTransferData_intermediateCollectionFromInventoryTransferDataCollection(player, {inventorySlotData})
if not wasInventoryTransferDataModified_player then
for i, inventoryTransferData in pairs(inventoryTransferData_intermediateCollection_player) do
local itemBaseData = itemLookup[inventoryTransferData.id]
if inventoryTransferData.questBound or (itemBaseData and itemBaseData.questBound) then
return false, "Item is quest bound"
end
end
revokePlayerItemsByInventoryTransferDataCollection(player, inventoryTransferData_intermediateCollection_player)
local copy_inventoryTransferData_intermediateCollection_player = utilities.copyTable(inventoryTransferData_intermediateCollection_player)
-- merge itemStorage stacks if possible
while true do
local hasChanged = false
for i, inventoryTransferData in pairs(copy_inventoryTransferData_intermediateCollection_player) do
local itemBaseData = itemLookup[inventoryTransferData.id]
if itemBaseData.canStack then
for ii, itemStorageSlotData in pairs(playerData.globalData.itemStorage) do
local maxStackSizeForItem = itemBaseData.stackSize or MAX_COUNT_PER_STACK
if inventoryTransferData.id == itemStorageSlotData.id and itemStorageSlotData.stacks < maxStackSizeForItem then
local amountFromMaxStacks = (itemStorageSlotData.stacks + inventoryTransferData.stacks) - maxStackSizeForItem
if amountFromMaxStacks > 0 then
-- this is all overflow
itemStorageSlotData.stacks = amountFromMaxStacks
inventoryTransferData.stacks = maxStackSizeForItem
else
-- this is all underflow, so it merged completely.
itemStorageSlotData.stacks = itemStorageSlotData.stacks + inventoryTransferData.stacks
inventoryTransferData.stacks = 0
table.remove(copy_inventoryTransferData_intermediateCollection_player, i)
hasChanged = true
break
end
end
end
end
if hasChanged then
break
end
end
if not hasChanged then
break
end
end
for i, itemSlotData in pairs(copy_inventoryTransferData_intermediateCollection_player) do
table.insert(playerData.globalData.itemStorage, itemSlotData)
end
playerData.nonSerializeData.playerDataChanged:Fire("inventory")
playerData.nonSerializeData.playerDataChanged:Fire("globalData")
return true, "Successfully transfered to storage."
else
return false, "Failed find item."
end
end
return false, "PlayerData not found."
end
local function int__transferStorageToInventory(player, storageSlotData)
local playerData = playerDataContainer[player]
if playerData then
local player_hasInventorySpace = int__doesPlayerHaveInventorySpaceForTrade(player, {}, {storageSlotData})
if player_hasInventorySpace then
local wasStorageSlotDataRemoved, removeSlotData = false, nil do
for i, storageSlot in pairs(playerData.globalData.itemStorage) do
if storageSlot.id == storageSlotData.id and i == storageSlotData.position then
removeSlotData = table.remove(playerData.globalData.itemStorage, i)
wasStorageSlotDataRemoved = true
end
end
end
if wasStorageSlotDataRemoved then
table.insert(playerData.inventory, removeSlotData)
playerData.nonSerializeData.playerDataChanged:Fire("inventory")
playerData.nonSerializeData.playerDataChanged:Fire("globalData")
return true, "Successfully transfered to inventory."
else
return false, "Failed to find in storage."
end
else
return false, "Player does not have space in inventory."
end
end
return false, "PlayerData not found."
end
local function updateInventorySlots(player)
local playerData = playerDataContainer[player]
if playerData then
local availableSlots = {}
for i_category = 1, #CATEGORIES do
availableSlots[CATEGORIES[i_category]] = {}
for i_slot = 1, MAX_NUMBER_SLOTS_PER_CATEGORY do
availableSlots[CATEGORIES[i_category]][i_slot] = true
end
end
for i, inventorySlotData in pairs(playerData.inventory) do
local itemBaseData = itemLookup[inventorySlotData.id]
if itemBaseData then
-- make sure the slots have stacks value
if not inventorySlotData.stacks then
inventorySlotData.stacks = 1
end
-- TEMP DISABLED WHILE CONVERTING OLD STACK SIZES TO NEW ONES
-- make sure the slots are at their valid stack value and reset if not
--if inventorySlotData.stacks >= (itemBaseData.stackSize or MAX_COUNT_PER_STACK) then
-- inventorySlotData.stacks = itemBaseData.stackSize or MAX_COUNT_PER_STACK
--end
if availableSlots[itemBaseData.category] then
if inventorySlotData.position and availableSlots[itemBaseData.category][inventorySlotData.position] then
availableSlots[itemBaseData.category][inventorySlotData.position] = false
elseif inventorySlotData.position then
-- item trying to take up same category+position as another item
-- reset its position
inventorySlotData.position = nil
end
else
-- item with no category, kick it to the curb
table.remove(playerData.inventory, i)
end
else
-- no itemBaseData, kick it to the curb
table.remove(playerData.inventory, i)
end
end
local storageTransferItems = {}
-- so gross we have to do this cus tables dont autoscale
while true do
local madeChange = false
for i, inventorySlotData in pairs(playerData.inventory) do
if not inventorySlotData.position then
-- find a slot for this
local itemBaseData = itemLookup[inventorySlotData.id]
local slot = getAvailableSlot(availableSlots[itemBaseData.category])
if slot then
inventorySlotData.position = slot
availableSlots[itemBaseData.category][slot] = false
else
-- no open slot, kick it to the curb
warn("moving item to storage due to lack of inv space")
local success, reason = int__transferInventoryToStorage(player, inventorySlotData)
warn("INV->STOR", success, reason)
if not success then
table.remove(playerData.inventory, i)
warn("taking out and wiping it")
end
madeChange = true
break
end
end
end
if not madeChange then
break
end
end
-- try to ensure that stuff is gotten to in order
-- if a.position and b.position then
-- ^ fix for https://forum.playvesteria.com/t/game-breaking-glitch/2991
table.sort(playerData.inventory, function(a, b)
if a.position and b.position then
return a.position > b.position
end
-- default
return false
end)
end
end
-- true = its completed
-- false = its being worked on
-- nil = not assigned
local function autoSavePlayerData(player)
if player.Parent ~= game.Players or player:FindFirstChild("DataLoaded") == nil or player:FindFirstChild("teleporting") or playerDataContainer[player] == nil then
return false
end
-- autosave ongoing statusEffects
if player:FindFirstChild("entityGUID") then
local statusEffects = network:invoke("playerRemovingPackageStatusEffects", player)
if statusEffects then
playerDataContainer[player].packagedStatusEffects = statusEffects
end
end
local playerId = player.userId
local Success, Error, TimeStamp = datastoreInterface:updatePlayerSaveFileData(playerId, playerDataContainer[player])
if Success then
if player:FindFirstChild("DataSaveFailed") then
player.DataSaveFailed:Destroy()
end
else
warn(player.Name,"'s data failed to save.",Error)
network:invoke("reportError", player, "error", "Failed to save player data: "..Error)
network:invoke("reportAnalyticsEvent",player,"data:fail:save")
if player:FindFirstChild("DataSaveFailed") == nil then
local tag = Instance.new("BoolValue")
tag.Name = "DataSaveFailed"
tag.Parent = player
end
network:fireClient("alertPlayerNotification", player, {
text = "Failed to save data: "..Error;
textColor3 = Color3.fromRGB(255, 57, 60)
})
end
-- get rid of packagedstatuseffects
if playerDataContainer[player] then
playerDataContainer[player].packagedStatusEffects = nil
end
return Success, TimeStamp
end
local playerStatTypes = {"dex", "int", "vit", "str"}
local function VALIDATE_SECTION_FOR_CHEAT_WEAPONS(section, holding)
local enchantmentMapping ={
["0.05"] = 1.0;
["0.075"] = 0.7;
["0.1"] = 0.1;
}
for i, inventorySlotData in pairs(section) do
local weaponEnchantmentSuccess = 1
local hasUnrealisticIncrease = false
if inventorySlotData.successfulUpgrades and inventorySlotData.successfulUpgrades > 0 then
local itemBaseData = itemLookup[inventorySlotData.id]
if itemBaseData.baseDamage then
for ii, modifierData in pairs(inventorySlotData.modifierData) do
if modifierData.baseDamage then
local percentIncrease = tostring(math.floor(modifierData.baseDamage / itemBaseData.baseDamage / 0.025) * 0.025)
if enchantmentMapping[percentIncrease] then
weaponEnchantmentSuccess = weaponEnchantmentSuccess * enchantmentMapping[percentIncrease]
else
warn(inventorySlotData.id, "@", inventorySlotData.position, "has weird scaling (", percentIncrease,"% increase)", modifierData.baseDamage, "baseDamage increase")
--hasUnrealisticIncrease = true
end
end
end
end
end
if weaponEnchantmentSuccess * 100 <= 0.5 or hasUnrealisticIncrease then
table.remove(section, i)
table.insert(holding, inventorySlotData)
warn("id", inventorySlotData.id, "@", inventorySlotData.position, "was revoked", "(" .. weaponEnchantmentSuccess * 100 .. "%)")
end
end
end
local function completelyNukeSaveFile(player, playerData)
playerData.level = 1
playerData.exp = 0
playerData.equipment = {}
playerData.inventory = {}
playerData.abilities = {}
playerData.abilityBooks = {}
playerData.statistics = {}
playerData.gold = 0
playerData.class = "Adventurer"
end
local function onLogPerkActivation_server(player, equipmentId)
local playerData = playerDataContainer[player]
if playerData then
playerData.nonSerializeData.perksActivated[tostring(equipmentId)] = tick()
end
end
local function flagCheck(player, playerData)
if not playerData.flags.arrowChangeXD then
playerData.flags.arrowChangeXD = true
local trueEquip do
for i, equip in pairs(playerData.equipment) do
if equip.position == mapping.equipmentPosition.arrow then
trueEquip = equip
end
end
end
if trueEquip and trueEquip.id == nil then
trueEquip.id = 87
elseif not trueEquip then
table.insert(playerData.equipment, {
id = 87;
position = mapping.equipmentPosition.arrow;
stacks = 0;
})
end
end
if not playerData.flags.abilityReset then
playerData.flags.abilityReset = true
if game.PlaceId ~= 2103419922 then
playerData.abilities = {}
for abilityBookName, abilityBookPlayerData in pairs(playerData.abilityBooks) do
abilityBookPlayerData.pointsAssigned = 0
end
while true do
local madeChange = false
for i, hotbarAbilityData in pairs(playerData.hotbar) do
if hotbarAbilityData.dataType == mapping.dataType.ability then
madeChange = true
table.remove(playerData.hotbar, i)
break
end
end
if not madeChange then
break
end
end
playerData.statistics.dex = 0
playerData.statistics.int = 0
playerData.statistics.str = 0
playerData.statistics.vit = 0
end
end
if not playerData.flags.fixNightmareChickensEXPandInfinitePetPickup then
playerData.flags.fixNightmareChickensEXPandInfinitePetPickup = true
if playerData.gold > 25000000 and not runService:IsStudio() then
playerData.gold = 0
end
if playerData.level == 30 or playerData.exp > levels.getEXPToNextLevel(playerData.level) then
playerData.exp = 0
end
end
if not playerData.flags.statCheck then
local needsReset = false
local statSum = 0
-- check individual stats
for i, statName in pairs(playerStatTypes) do
local stat = playerData.statistics[statName] or 0
statSum = statSum + math.abs(stat)
end
if statSum > levels.getStatPointsForLevel(playerData.level or 1) then
needsReset = true
end
playerData.flags.statCheck = true
-- wipe stats if bad
if needsReset then
for i, statName in pairs(playerStatTypes) do
playerData.statistics[statName] = 0
end
end
end
if not playerData.flags.resetQuests then
playerData.flags.resetQuests = true
playerData.quests = {}
playerData.quests.completed = {} --playerSaveFileData.quests.completed or {}
playerData.quests.active = {} --playerSaveFileData.quests.active or {}
end
if configuration.getConfigurationValue("doStartRevokingCheatWeapons") then
if not playerData.flags.revokeCheatWeapons then
playerData.flags.revokeCheatWeapons = true
playerData.holding = {}
VALIDATE_SECTION_FOR_CHEAT_WEAPONS(playerData.inventory, playerData.holding)
VALIDATE_SECTION_FOR_CHEAT_WEAPONS(playerData.equipment, playerData.holding)
end
end
if not playerData.flags.resetStatPointsForV23 then
playerData.flags.resetStatPointsForV23 = true
playerData.statistics.dex = 0
playerData.statistics.int = 0
playerData.statistics.str = 0
playerData.statistics.vit = 0
end
if not playerData.flags.removeSpiderQueenCrown then
playerData.flags.removeSpiderQueenCrown = true
for i, inventorySlotData in pairs(playerData.inventory) do
if inventorySlotData.id == 68 then
table.remove(playerData.inventory, i)
end
end
for i, equipmentSlotData in pairs(playerData.equipment) do
if equipmentSlotData.id == 68 then
table.remove(playerData.equipment, i)
end
end
end
end
local function onGetPlayerEquipmentDataByEquipmentPosition(player, equipmentPosition)
if playerDataContainer[player] then
for i, equipmentData in pairs(playerDataContainer[player].equipment) do
if equipmentData.position == equipmentPosition then
return equipmentData
end
end
end
return nil
end
-- couldn't find a better place to put this
-- spawn point logic
local spawnPoints = Instance.new("Folder")
spawnPoints.Name = "spawnPoints"
local function registerSpawnPoint(Child)
if Child then
local tag = Instance.new("CFrameValue")
tag.Name = ((Child.Name:lower() == "spawnpoint" or Child.Name:lower() == "spawnpart") and "default") or Child.Name
tag.Value = Child.CFrame
if Child:FindFirstChild("description") then
Child.description:Clone().Parent = tag
end
tag.Parent = spawnPoints
end
end
for i,Child in pairs(game.CollectionService:GetTagged("spawnPoint")) do
registerSpawnPoint(Child)
end
game.CollectionService:GetInstanceAddedSignal("spawnPoint"):Connect(registerSpawnPoint)
local function registerTeleportAsSpawn(Child)
if Child and Child:FindFirstChild("teleportDestination") then
local tag = Instance.new("CFrameValue")
local destination = utilities.placeIdForGame(Child.teleportDestination.Value)
tag.Name = tostring(destination)
tag.Value = Child.CFrame
tag.Parent = spawnPoints
local isLoadBarrier = Instance.new("BoolValue")
isLoadBarrier.Name = "isLoadBarrier"
isLoadBarrier.Value = true
isLoadBarrier.Parent = tag
if Child:FindFirstChild("ignore") == nil then
spawn(function()
local description = Instance.new("StringValue")
description.Name = "description"
description.Value = "Path to ".. utilities.getPlaceName(destination)
description.Parent = tag
end)
end
end
end
for i,Child in pairs(game.CollectionService:GetTagged("teleportPart")) do
registerTeleportAsSpawn(Child)
end
game.CollectionService:GetInstanceAddedSignal("teleportPart"):Connect(registerTeleportAsSpawn)
spawnPoints.Parent = game.ReplicatedStorage
-- end spawn point logic
local function signal_inputChanged(player, input)
if input == "xbox" or input == "mobile" or input == "pc" then
if player:FindFirstChild("input") then
player.input.Value = input
end
end
end
local function onPlayerAdded(player, desiredSlot, desiredTimeStamp, accessories)
if player:FindFirstChild("DataLoaded") or playerDataContainer[player] then
spawn(function()
end)
return false, nil, "Data already loaded"
end
local joinData = player:GetJoinData()
local teleportData
if joinData and joinData.TeleportData then
-- support shared teleportData for parties
teleportData = (joinData.TeleportData.members and joinData.TeleportData.members[player.Name]) or joinData.TeleportData
end
if not ((teleportData and teleportData.destination == game.PlaceId) or game.PlaceId == 2015602902 or game.PlaceId == 2015602902 or game.PlaceId == 2376885433 or runService:IsRunMode() or runService:IsStudio()) then
if player:GetRankInGroup(4238824) < 250 then
-- stress test place is always allowed
if game.PlaceId ~= 2103419922 then
player:Kick("Not authorized")
network:invoke("reportError", player, "debug", "Player not authorized to join server")
return false
end
end
end
local existingAnalyticsSession
local joinTime
local wasReferred
if teleportData then
desiredSlot = teleportData.dataSlot
desiredTimeStamp = teleportData.dataTimestamp
if teleportData.analyticsSessionId and teleportData.joinTime then
existingAnalyticsSession = teleportData.analyticsSessionId
joinTime = teleportData.joinTime
end
if teleportData.playerAccessories then
accessories = teleportData.playerAccessories
end
if teleportData.partyData and teleportData.partyData.guid then
network:invoke("resumePartyAfterTeleport", player, teleportData.partyData.guid, teleportData.partyData.partyLeaderUserId)
end
if teleportData.wasReferred then
wasReferred = true
end
local location = teleportData.arrivingFrom
if location and location ~= 2376885433 and location ~= 2015602902 then
spawn(function()
local placeName = utilities.getPlaceName(location)
if teleportData.teleportType and teleportData.teleportType == "rune" then
network:fireAllClients("signal_alertChatMessage", {
Text = player.Name .. " arrived from " .. placeName .. " via a magical rune.";
Font = Enum.Font.SourceSansBold;
Color = Color3.fromRGB(45, 87, 255)
})
elseif teleportData.teleportType and teleportData.teleportType == "death" then
network:fireAllClients("signal_alertChatMessage", {
Text = player.Name .. " escaped from " .. placeName .. ".";
Font = Enum.Font.SourceSansBold;
Color = Color3.fromRGB(45, 87, 255)
})
elseif teleportData.teleportType and teleportData.teleportType == "taxi" then
network:fireAllClients("signal_alertChatMessage", {
Text = player.Name .. " arrived from " .. placeName .. " via Taximan Dave.";
Font = Enum.Font.SourceSansBold;
Color = Color3.fromRGB(45, 87, 255)
})
else
network:fireAllClients("signal_alertChatMessage", {
Text = player.Name .. " arrived from " .. placeName .. ".";
Font = Enum.Font.SourceSansBold;
Color = Color3.fromRGB(45, 87, 255)
})
end
end)
else
network:fireAllClients("signal_alertChatMessage", {
Text = player.Name .. " connected.";
Font = Enum.Font.SourceSansBold;
Color = Color3.fromRGB(45, 87, 255)
})
end
end
-- defaults
desiredSlot = desiredSlot or 1
local success, playerData, errorMsg = datastoreInterface:getPlayerSaveFileData(player, desiredSlot, desiredTimeStamp)
if not success then
warn("Failed to load "..player.Name.."'s data from slot "..desiredSlot.." ("..errorMsg..")")
network:invoke("reportError", player, "error", "Failed to load player data: "..errorMsg)
network:invoke("reportAnalyticsEvent",player,"data:fail:load")
return false, nil, errorMsg
end
if playerData == nil then
warn("Player data is nil???")
end
if game.PlaceId == 2376885433 or game.PlaceId == 2015602902 or game.PlaceId == 4623219432 then
-- lobby server, return data to player
return playerData
end
local professionTag = Instance.new("IntValue")
professionTag.Name = "professions"
spawn(function()
if game.MarketplaceService:UserOwnsGamePassAsync(player.userId, 7785243) or game:GetService("RunService"):IsStudio() then
local tag = Instance.new("BoolValue")
tag.Name = "bountyHunter"
tag.Parent = player
end
end)
if game.ReplicatedStorage.professionLookup then
for i, profession in pairs(game.ReplicatedStorage.professionLookup:GetChildren()) do
if profession:IsA("ModuleScript") then
playerData.professions[profession.Name] = playerData.professions[profession.Name] or {level = 1, exp = 0}
local tag = Instance.new("IntValue")
tag.Name = profession.Name
tag.Value = playerData.professions[profession.Name].level
tag.Parent = professionTag
end
end
end
professionTag.Parent = player
local levelTag = Instance.new("IntValue")
levelTag.Name = "level"
levelTag.Value = playerData.level
levelTag.Parent = player
local moneyTag = Instance.new("NumberValue")
moneyTag.Name = "gold"
moneyTag.Value = playerData.gold
moneyTag.Parent = player
spawn(function()
network:invoke("reportAnalyticsEvent",player,"level:lvl"..tostring(playerData.level),playerData.level)
end)
local inputTag = Instance.new("StringValue")
inputTag.Name = "input"
inputTag.Parent = player
local classTag = Instance.new("StringValue")
classTag.Name = "class"
classTag.Value = playerData.class
classTag.Parent = player
local pvpTag = Instance.new("BoolValue")
pvpTag.Name = "isPVPEnabled"
pvpTag.Value = false
pvpTag.Parent = player
local pvpTag = Instance.new("BoolValue")
pvpTag.Name = "isInPVP"
pvpTag.Value = false
pvpTag.Parent = player
local entityGUIDTag = Instance.new("StringValue")
entityGUIDTag.Name = "entityGUID"
entityGUIDTag.Value = HttpService:GenerateGUID(false)
entityGUIDTag.Parent = player
local playerSpawnTimeTag = Instance.new("IntValue")
playerSpawnTimeTag.Name = "playerSpawnTime"
playerSpawnTimeTag.Value = os.time()
playerSpawnTimeTag.Parent = player
local isPlayerSpawningTag = Instance.new("BoolValue")
isPlayerSpawningTag.Name = "isPlayerSpawning"
isPlayerSpawningTag.Value = true
isPlayerSpawningTag.Parent = player
local isFirstTimeSpawning = Instance.new("BoolValue")
isFirstTimeSpawning.Name = "isFirstTimeSpawning"
isFirstTimeSpawning.Value = true
isFirstTimeSpawning.Parent = isPlayerSpawningTag
local respawnPointTag = Instance.new("ObjectValue")
respawnPointTag.Name = "respawnPoint"
respawnPointTag.Value = nil
respawnPointTag.Parent = player
if not playerData.flags.ancientsRevert then
playerData.flags.ancientsRevert = true
for _, v in pairs(playerData.inventory) do
if v.id == 63 or v.id == 62 or v.id == 64 or v.id == 17 then -- 63, 62, 17, 64 -> 200
v.id = 200
end
end
if playerData.globalData and playerData.globalData.itemStorage then
if not playerData.globalData.ancientsRevertStore then
playerData.globalData.ancientsRevertStore = true
for _, v in pairs(playerData.globalData.itemStorage) do
if v.id == 63 or v.id == 62 or v.id == 64 or v.id == 17 then -- 63, 62, 17, 64 -> 200
v.id = 200
end
end
end
end
end
if not playerData.flags.enchantWipe3 then
playerData.flags.enchantWipe3 = true
if playerData.gold > 50000 then
playerData.gold = 50000
end
local function process(v)
if v.modifierData then
v.modifierData = {}
end
if v.upgrades then
v.upgrades = 0
end
if v.successfulUpgrades then
v.successfulUpgrades = 0
end
if v.blessed then
v.blessed = nil
end
if v.enchantments then
v.enchantments = nil
end
end
for _, v in pairs(playerData.equipment) do
process(v)
end
for _, v in pairs(playerData.inventory) do
process(v)
end
-- for damien: this isnt how storage works pls fix it thnx
--playerData.globalData.itemStorage
if playerData.globalData and playerData.globalData.itemStorage then
if not playerData.globalData.enchantWipe3 then
playerData.globalData.enchantWipe3 = true
for _, v in pairs(playerData.globalData.itemStorage) do
process(v)
end
end
end
playerData.treasureChests = nil
end
-- boom boom
if not playerData.hasCustomizedCharacter then
if accessories then
playerData.accessories = accessories
else
playerData.accessories = require(ReplicatedStorage.defaultCharacterAppearance).accessories
end
playerData.hasCustomizedCharacter = true
end
playerData.accessories.skinColorId = playerData.accessories.skinColorId or 1
playerData.accessories.hairColorId = playerData.accessories.hairColorId or 1
playerData.accessories.shirtColorId = playerData.accessories.shirtColorId or 1
-- location data
playerData.locations = playerData.locations or {}
if game.PrivateServerId == "" and game.PrivateServerOwnerId == 0 then
playerData.locations[tostring(game.PlaceId)] = playerData.locations[tostring(game.PlaceId)] or {}
local spawns = {} do
for i, spawnPart in pairs(CollectionService:GetTagged("spawnPoint")) do
spawns[spawnPart.Name] = spawnPart
end
end
local placeData = playerData.locations[tostring(game.PlaceId)]
placeData.visited = os.time()
placeData.spawns = placeData.spawns or {}
for spawnName, spawnDescription in pairs(placeData.spawns) do
if not spawns[spawnName] then
placeData.spawns[spawnName] = nil
end
end
end
-- treasure data
playerData.treasure = playerData.treasure or {}
playerData.treasure["place-"..game.PlaceId] = playerData.treasure["place-"..game.PlaceId] or {}
playerData.treasure["place-"..game.PlaceId].chests = playerData.treasure["place-"..game.PlaceId].chests or {}
for i, treasureChest in pairs(game.CollectionService:GetTagged("treasureChest")) do
playerData.treasure["place-"..game.PlaceId].chests[treasureChest.Name] = playerData.treasure["place-"..game.PlaceId].chests[treasureChest.Name] or {
open = false
}
end
if wasReferred then
if not playerData.globalData.recievedReferralGift then
playerData.globalData.recievedReferralGift = true
playerData.globalData.ethyr = (playerData.globalData.ethyr or 0) + 200
network:fireClient("signal_alertChatMessage", player, {Text = "Recieved 200 Ethyr referral bonus."; Font = Enum.Font.SourceSansBold; Color = Color3.fromRGB(196, 209, 216)} )
network:invoke("reportCurrency", player, "ethyr", 200, "gift:referral")
end
end
local lastPosition = playerData.lastPhysicalPosition
if lastPosition and (playerData.lastLocation == nil or game.PlaceId == playerData.lastLocation) then
local tag = Instance.new("Vector3Value")
tag.Name = "lastPhysicalPosition"
tag.Value = Vector3.new(lastPosition[1],lastPosition[2],lastPosition[3])
tag.Parent = player
end
-- signal for when data is changed (`_previousValue` and `_newValue` are optional,
-- do not depend on them being there unless you know they will be)
local function signal__playerDataChanged(index, _previousValue, _newValue)
if index == "class" then
player.class.Value = _newValue
generatecompletePlayerStats(player)
elseif index == "gold" then
player.gold.Value = _newValue
elseif index == "exp" then -- EXP CHANGED
while playerData.level < PLAYER_LEVEL_CAP do
local expForNextLevel = levels.getEXPToNextLevel(playerData.level)
if playerData.exp >= expForNextLevel then
-- level the player up and silently remove extra exp
-- NOTE: we are doing this silently because the propogation at the end will get the **CURRENT** exp value
-- which will include this silent reduction.
playerData.exp = playerData.exp - expForNextLevel
-- increase player level and propogate this change
playerData.nonSerializeData.incrementPlayerData("level", 1)
player.level.Value = player.level.Value + 1
spawn(function()
network:invoke("reportAnalyticsEvent",player,"levelup:lvl"..tostring(player.level.Value))
end)
network:fireAllClients("signal_alertChatMessage", {
Text = player.Name .. " has reached Lvl." .. player.level.Value .. "!";
Font = Enum.Font.SourceSansBold;
Color = Color3.fromRGB(255, 219, 12)
})
if player.Character and player.Character.PrimaryPart then
for i,oPlayer in pairs(game.Players:GetPlayers()) do
local ui = script.levelUp:Clone()
ui.Adornee = player.Character.PrimaryPart
ui.Parent = oPlayer.PlayerGui
end
local Sound = Instance.new("Sound")
Sound.Volume = 0.7
Sound.MaxDistance = 500
Sound.SoundId = "rbxassetid://2066645345"
Sound.Parent = player.Character.PrimaryPart
Sound:Play()
game.Debris:AddItem(Sound,10)
local Attach = Instance.new("Attachment")
Attach.Parent = player.Character.PrimaryPart
Attach.Orientation = Vector3.new(0,0,0)
Attach.Axis = Vector3.new(1,0,0)
Attach.SecondaryAxis = Vector3.new(0,1,0)
for i,particle in pairs(script.particles:GetChildren()) do
local newParticle = particle:Clone()
newParticle.Enabled = false
newParticle.Parent = Attach
if newParticle.Name == "Dust" then
newParticle:Emit(40)
else
newParticle:Emit(1)
end
end
game.Debris:AddItem(Attach, 10)
applyMaxHealth(player)
end
else
if playerData.level >= PLAYER_LEVEL_CAP then
playerData.exp = 0
end
break
end
end
elseif index == "equipment" then -- equipment was changed!
generatecompletePlayerStats(player)
replicatePlayerCharacterAppearance(player)
network:fire("playerEquipmentChanged_server", player, playerData.equipment)
--network:fireAllClients("playerEquipmentChanged", player, playerData.equipment)
elseif index == "inventory" then
-- update the inventory
updateInventorySlots(player)
network:fire("playerInventoryChanged_server", player)
-- try to ensure that stuff is gotten to in order
table.sort(playerData[index], function(a, b)
return a.position > b.position
end)
elseif index == "level" then
elseif index == "statistics" then
generatecompletePlayerStats(player)
elseif index == "accessories" then
replicatePlayerCharacterAppearance(player)
elseif index == "nonSerializeData" then
player.isPVPEnabled.Value = playerData.nonSerializeData.isPVPEnabled
player.isInPVP.Value = playerData.nonSerializeData.isGlobalPVPEnabled or #playerData.nonSerializeData.whitelistPVPEnabled > 0
if player.isInPVP.Value and player.Character then
physics:setWholeCollisionGroup(player.Character, "pvpCharacters")
elseif player.Character then
physics:setWholeCollisionGroup(player.Character, "characters")
end
elseif index == "statusEffects" then
generatecompletePlayerStats(player)
--replicatePlayerCharacterActiveStatusEffects(player)
elseif index == "internalData" then
warn("attempt to signal internalData changed.")
end
onClientRequestPropogateCacheData(player, index)
end
-- set-up playerDataChanged event to propogate playerData changes
playerData.nonSerializeData.playerDataChanged = Instance.new("BindableEvent")
playerData.nonSerializeData.playerDataChanged.Event:connect(signal__playerDataChanged)
-- set condition properly if this was a death teleport
if teleportData and teleportData.teleportType == "death" then
playerData.condition = {
health = 1,
mana = 1,
}
end
-- continue statusEffects that were packaged
if playerData.packagedStatusEffects then
network:invoke("playerAddedContinuePackageStatusEffects", player, playerData.packagedStatusEffects)
playerData.packagedStatusEffects = nil
end
-- calculate stats table
generatecompletePlayerStats(player, true, playerData)
replicatePlayerCharacterActiveStatusEffects(player)
-- set up character appearance
replicatePlayerCharacterAppearance(player, playerData)
local function onCharacterSpawn(character)
isPlayerSpawningTag.Value = true
playerSpawnTimeTag.Value = os.time()
if character.Parent == nil then
character.Parent = entityManifestCollectionFolder
end
if character.PrimaryPart == nil then
repeat wait(0.1) until character.PrimaryPart or character:FindFirstChild("hitbox") or character.Parent == nil
end
if character:FindFirstChild("hitbox") and character.PrimaryPart ~= character.hitbox then
character.PrimaryPart = character.hitbox
end
replicatePlayerCharacterAppearance(player, playerData)
replicatePlayerCharacterActiveStatusEffects(player, playerData)
local characterPrimaryPart = character.PrimaryPart
pcall(function()
characterPrimaryPart:SetNetworkOwner(player)
end)
local lastCharacterPostion do
if player:FindFirstChild("lastPhysicalPosition") then
lastCharacterPostion = player.lastPhysicalPosition.Value
player.lastPhysicalPosition:Destroy()
end
end
local targetCharacterSpawnPosition do
if lastCharacterPostion then
targetCharacterSpawnPosition = CFrame.new(lastCharacterPostion) + Vector3.new(0, 4, 0)
elseif ReplicatedStorage:FindFirstChild("spawnPoints") then
local spawnPoint = teleportData and ((teleportData.spawnLocation and ReplicatedStorage.spawnPoints:FindFirstChild(teleportData.spawnLocation)) or teleportData.arrivingFrom and ReplicatedStorage.spawnPoints:FindFirstChild(teleportData.arrivingFrom)) or ReplicatedStorage.spawnPoints:FindFirstChild("default")
-- if the player has a set respawn point, use that
if player:FindFirstChild("respawnPoint") and player.respawnPoint.Value then
spawnPoint = player.respawnPoint.Value
end
if spawnPoint then
local cf = spawnPoint.Value
local goalCFrame = characterPrimaryPart.CFrame - characterPrimaryPart.CFrame.p + cf.p + cf.lookVector * 30 + Vector3.new(math.random() * 3, 4, math.random() * 3)
-- shift randomly if this isn't a load barrier
-- local isLoadBarrier = (teleportData and (teleportData.spawnLocation == nil)) and spawnPoint.Name ~= "default"
local isLoadBarrier = (spawnPoint:FindFirstChild("isLoadBarrier") ~= nil)
if not isLoadBarrier then
goalCFrame = cf + Vector3.new(math.random(-3, 3), math.random(3, 5), math.random(-3, 3))
end
targetCharacterSpawnPosition = goalCFrame
elseif #ReplicatedStorage.spawnPoints:GetChildren() > 0 then
warn(">> spawn point missing!", teleportData and teleportData.arrivingFrom or "no default found")
warn(">> using first spawnPoint")
local cf = ReplicatedStorage.spawnPoints:GetChildren()[1].Value
local goalCFrame = characterPrimaryPart.CFrame - characterPrimaryPart.CFrame.p + cf.p + cf.lookVector * 30 + Vector3.new(math.random() * 3, 4, math.random() * 3)
targetCharacterSpawnPosition = goalCFrame
else
warn(">> no spawn points at all.. ?")
end
else
warn(">> no spawnPoints ?")
end
end
local resurrectTag = player:FindFirstChild("resurrecting")
if resurrectTag ~= nil then
resurrectTag:Destroy()
targetCharacterSpawnPosition = nil
end
if targetCharacterSpawnPosition then
local ray = Ray.new(targetCharacterSpawnPosition.p + Vector3.new(0, 2, 0), Vector3.new(0, -999, 0))
local hitPart = projectile.raycast(ray, {character; entityManifestCollectionFolder})
if hitPart then
wait(0.1)
for _ = 1, 8 do
network:invoke("teleportPlayerCFrame_server", player, targetCharacterSpawnPosition)
wait(0.2)
end
end
end
if player.Character == character and characterPrimaryPart.state.Value ~= "dead" then
isPlayerSpawningTag.Value = false
end
end
if player.Character then
spawn(function()
onCharacterSpawn(player.Character)
end)
end
player.CharacterAdded:connect(onCharacterSpawn)
-- register playerData internally
playerDataContainer[player] = playerData
flagCheck(player, playerData)
-- assign inventory slots that aren't assigned positions
updateInventorySlots(player)
local success, rank = pcall(function()
return player:GetRankInGroup(4238824)
end)
-- push player data to client
onClientRequestFlushPropogationCache(player)
-- some functions to change value easily while also flushing the change to the client
function playerData.nonSerializeData.setPlayerData(index, value)
local previousValue = playerData[index]
playerData[index] = value
-- signal to script something changed
playerData.nonSerializeData.playerDataChanged:Fire(index, previousValue, playerData[index])
end
function playerData.nonSerializeData.incrementPlayerData(index, increment, source)
if index == "exp" and playerData.level >= PLAYER_LEVEL_CAP then
return
end
local previousValue = playerData[index]
playerData[index] = previousValue + increment
-- signal to script something changed
playerData.nonSerializeData.playerDataChanged:Fire(index, previousValue, playerData[index])
-- analytics for gold
if index == "gold" then
local currency = index
local amount = increment
if amount ~= 0 and source then
network:invoke("reportCurrency", player, currency, amount, source)
end
end
end
function playerData.nonSerializeData.setNonSerializeDataValue(index, value)
playerData.nonSerializeData[index] = value
playerData.nonSerializeData.playerDataChanged:Fire("nonSerializeData")
end
-- end easy functions --
if player.Character and player.Character.PrimaryPart then
spawn(function()
onCharacterAdded(player, player.Character)
end)
end
player.CharacterAdded:connect(function(character)
while not character.PrimaryPart and player.Parent == game.Players do
wait(0.1)
end
if character.PrimaryPart then
onCharacterAdded(player, character)
end
end)
local tag = Instance.new("BoolValue")
tag.Name = "DataLoaded"
tag.Parent = player
local tag = Instance.new("IntValue")
tag.Name = "dataLoaded"
tag.Value = os.time()
tag.Parent = player
player:LoadCharacter()
network:fire("playerDataLoaded", player, playerDataContainer[player])
local posTag = Instance.new("Vector3Value")
posTag.Name = "playerCharacterPosition"
posTag.Parent = player
spawn(function()
while player and player.Parent == game.Players do
if player.Character and player.Character.PrimaryPart then
posTag.Value = player.Character.PrimaryPart.Position
end
wait(3)
end
end)
-- more aggresively save lastPhysicalPosition
spawn(function()
local players = game:GetService("Players")
while player and player.Parent == players do
local playerData = playerDataContainer[player]
if playerData and player.Character and player.Character.PrimaryPart then
local position = player.Character.PrimaryPart.Position
playerData["lastPhysicalPosition"] = {
math.floor(position.X),
math.floor(position.Y),
math.floor(position.Z)
}
end
wait(3)
end
end)
spawn(function()
wait(60)
local errorcount = 0
while player and player.Parent == game.Players and player:FindFirstChild("DataLoaded") do
local waittime = 180
if player:FindFirstChild("teleporting") == nil and playerDataContainer[player] then
local success, timestamp = autoSavePlayerData(player)
if not success then
if errorcount == 0 then
waittime = 10
elseif errorcount <= 3 then
waittime = 20
else
waittime = 60
end
errorcount = errorcount + 1
end
end
wait(waittime)
end
end)
-- Admin hunter
spawn(function()
if player:IsInGroup(1200769) then
local Error = "An admin ("..player.Name..") joined the game."
network:invoke("reportError", player, "info", Error)
elseif player:IsInGroup(4199740) then
local Error = "A Star Creator ("..player.Name..") joined the game."
network:invoke("reportError", player, "info", Error)
end
if player:IsInGroup(4484634) then
local Error = "A Red Manta ("..player.Name..") joined the game."
network:invoke("reportError", player, "info", Error)
end
end)
if existingAnalyticsSession then
network:invoke("continueSession", player, existingAnalyticsSession, joinTime)
else
network:invoke("newSession", player)
end
-- tp exploit stuff --
local playerPositionData = {}
playerPositionData.positions = {}
playerPositionData.sketchyMovements = {}
playerPositionDataContainer[player] = playerPositionData
----------------------
--items currently equipped need to have their onEquipped fired
for _, slotData in pairs(playerData.equipment) do
local itemData = itemLookup[slotData.id]
if itemData.perks then
for perkName, _ in pairs(itemData.perks) do
local perkData = perkLookup[perkName]
if perkData and perkData.onEquipped then
local success, err = pcall(function()
perkData.onEquipped(player, itemData, tostring(slotData.position))
end)
if not success then
warn(string.format("item %s equip failed because: %s", itemData.name, err))
end
end
end
end
end
-- data recovery flags processed here, we don't need to care if this is studio
spawn(function()
-- data recovery disabled by this return
-- guess we're not using it for a while
if game.PlaceId ~= 4409709778 then return end
local lastSaveTimestamp = 0
if playerData.globalData then
lastSaveTimestamp = playerData.globalData.lastSaveTimestamp or 0
end
if not playerData.flags.dataRecovery11_14 then
if lastSaveTimestamp < 1573760698 then
playerData.flags.dataRecovery11_14 = true
else
performDataRecovery(player, desiredSlot, "dataRecovery11_14")
end
end
end)
return true, playerData, errorMsg
end
function performDataRecovery(player, desiredSlot, flagName)
local playerData = playerDataContainer[player]
if game.PlaceId == 4409709778 then
while true do
wait(1)
network:fireClient("dataRecoveryRequested", player, playerData, desiredSlot, flagName)
end
else
playerData.dataRecoveryReturnPlaceId = game.PlaceId
repeat
wait(1)
until network:invoke("teleportPlayer", player, 4409709778)
end
end
local DATA_RECOVERY_FLAG_NAMES = {
"dataRecovery11_14",
}
function onDataRecoveryRejected(player, flagName)
-- is this a legal flag?
local legalFlag = false
for _, acceptedFlagName in pairs(DATA_RECOVERY_FLAG_NAMES) do
if acceptedFlagName == flagName then
legalFlag = true
end
end
if not legalFlag then return end
local playerData = playerDataContainer[player]
if not playerData then return end
if playerData.flags[flagName] then return end
playerData.flags[flagName] = true
repeat
wait(1)
until network:invoke("teleportPlayer", player, playerData.dataRecoveryReturnPlaceId)
end
function onDataRecoveryRequested(player, slot, version, flagName)
-- only in data recovery place!
if game.PlaceId ~= 4409709778 then return end
-- is this a legal flag?
local legalFlag = false
for _, acceptedFlagName in pairs(DATA_RECOVERY_FLAG_NAMES) do
if acceptedFlagName == flagName then
legalFlag = true
end
end
if not legalFlag then return end
-- do we have this flag already?
local playerData = playerDataContainer[player]
if playerData.flags[flagName] then return end
local latestVersion = datastoreInterface:getLatestSaveVersion(player)
local success, rollbackData, message = datastoreInterface:getPlayerSaveFileData(player, slot, version)
if not success then return end
rollbackData.timestamp = playerData.timestamp
rollbackData.globalData.version = playerData.globalData.version
rollbackData.flags[flagName] = true
playerDataContainer[player] = rollbackData
datastoreInterface:updatePlayerSaveFileData(player.UserId, rollbackData)
repeat
wait(1)
until network:invoke("teleportPlayer", player, playerData.dataRecoveryReturnPlaceId)
end
game:BindToClose(function()
if shuttingDown then
return false
end
shuttingDown = true
local msg = Instance.new("Message")
msg.Text = "Servers are shutting down for a Vesteria or Roblox client update. Your data is now saving..."
msg.Parent = workspace
if game:GetService("RunService"):IsStudio() then return end
local playersToSave = {}
local playerCount = 0
for i,player in pairs(game.Players:GetPlayers()) do
local playerName = player.Name
playersToSave[playerName] = player
playerCount = playerCount + 1
spawn(function()
local success, err = pcall(onPlayerRemoving,player)
if success then
playersToSave[playerName] = nil
playerCount = playerCount - 1
local destination = 2376885433
if game.GameId == 712031239 then
destination = 2015602902
end
game:GetService("TeleportService"):Teleport(destination, player, {teleportReason = "You were teleported back to the lobby because Vesteria your server was shutdown. This probably means we released an update for new content or bug fixes."}, game.ReplicatedStorage.returnToLobby)
else
warn("Failed to save",playerName,"data")
warn(err)
end
end)
end
repeat wait(0.1) until playerCount <= 0
end)
-- note: category can be factored out because items of same itemId share same category
local function getInventorySlotByItemId(player, itemId, ignoreSlotsThatAreStacked)
local lowestInventorySlot
if playerDataContainer[player] then
for i, inventorySlot in pairs(playerDataContainer[player].inventory) do
local itemBaseData = itemLookup[inventorySlot.id]
if inventorySlot.id == itemId and (not lowestInventorySlot or inventorySlot.position < lowestInventorySlot.position) and (not ignoreSlotsThatAreStacked or (inventorySlot.stacks and inventorySlot.stacks < (itemBaseData.stackSize or MAX_COUNT_PER_STACK))) then
lowestInventorySlot = inventorySlot
end
end
end
return lowestInventorySlot
end
local function onGetPlayerInventorySlotDataByInventorySlotPosition(player, category, inventorySlotPosition)
if not player or not playerDataContainer[player] then return nil end
for trueInventorySlotPosition, inventorySlotData in pairs(playerDataContainer[player].inventory) do
local itemBaseData = itemLookup[inventorySlotData.id]
if itemBaseData and itemBaseData.category == category then
if inventorySlotData.position == inventorySlotPosition then
return inventorySlotData, trueInventorySlotPosition
end
end
end
return nil
end
-- gold_player is how much the player loses, gold_NPC is how much the player gains
-- inventoryTransferDataCollection_player is what the player loses
-- inventoryTransferData_intermediateCollection_NPC is what the player gains
local function int__tradeItemsBetweenPlayerAndNPC(player, inventoryTransferDataCollection_player, gold_player, inventoryTransferData_intermediateCollection_NPC, gold_NPC, source, additionalValues)
local playerData = playerDataContainer[player]
if player and playerData and inventoryTransferDataCollection_player and inventoryTransferData_intermediateCollection_NPC then
local inventoryTransferData_intermediateCollection_player, wasInventoryTransferDataModified_player = int__getInventoryTransferData_intermediateCollectionFromInventoryTransferDataCollection(player, inventoryTransferDataCollection_player)
local player_hasInventorySpace = int__doesPlayerHaveInventorySpaceForTrade(player, inventoryTransferData_intermediateCollection_player, inventoryTransferData_intermediateCollection_NPC)
if not wasInventoryTransferDataModified_player and player_hasInventorySpace then
if not gold_player or playerData.gold >= gold_player then
-- remove items from playerFrom
revokePlayerItemsByInventoryTransferDataCollection(player, inventoryTransferData_intermediateCollection_player)
if gold_player and gold_player ~= 0 then
playerData.nonSerializeData.incrementPlayerData("gold", -gold_player, source)
end
-- grant items from NPC
grantPlayerItemsByInventoryTranferData_intermediateCollection(player, inventoryTransferData_intermediateCollection_NPC)
if gold_NPC and gold_NPC ~= 0 then
playerData.nonSerializeData.incrementPlayerData("gold", gold_NPC, source)
end
if not (additionalValues and additionalValues.overrideItemsRecieved) then
network:fireClient("itemsRecieved", player, inventoryTransferData_intermediateCollection_NPC, gold_NPC)
end
return true
else
return false, player.Name .. " does not have enough gold"
end
else
return false, wasInventoryTransferDataModified_player and "Player inventory was modified" or "Not enough space in inventory!"
end
end
return false, "denied straight-up"
end
local function int__tradeItemsBetweenPlayers(playerFrom, inventoryTransferData_intermediateCollection_playerFrom, gold_playerFrom, playerTo, inventoryTransferData_intermediateCollection_playerTo, gold_playerTo)
local playerFromData = playerDataContainer[playerFrom]
local playerToData = playerDataContainer[playerTo]
if playerFrom:FindFirstChild("DataSaveFailed") or playerTo:FindFirstChild("DataSaveFailed") then
return false, "player is experiencing a DataStore outage"
end
if playerFromData and playerToData then
-- check if playerFrom has inventory space
local playerFrom_hasInventorySpace = int__doesPlayerHaveInventorySpaceForTrade(playerFrom, inventoryTransferData_intermediateCollection_playerFrom, inventoryTransferData_intermediateCollection_playerTo)
if playerFrom_hasInventorySpace then
-- check if playerTo has inventory space
local playerTo_hasInventorySpace = int__doesPlayerHaveInventorySpaceForTrade(playerTo, inventoryTransferData_intermediateCollection_playerTo, inventoryTransferData_intermediateCollection_playerFrom)
if playerTo_hasInventorySpace then
-- remove items from playerFrom
revokePlayerItemsByInventoryTransferDataCollection(playerFrom, inventoryTransferData_intermediateCollection_playerFrom)
-- remove items from playerTo
revokePlayerItemsByInventoryTransferDataCollection(playerTo, inventoryTransferData_intermediateCollection_playerTo)
-- grant items to playerFrom
grantPlayerItemsByInventoryTranferData_intermediateCollection(playerFrom, inventoryTransferData_intermediateCollection_playerTo)
-- grant items to playerTo
grantPlayerItemsByInventoryTranferData_intermediateCollection(playerTo, inventoryTransferData_intermediateCollection_playerFrom)
local source = "player:trade"
playerFromData.nonSerializeData.incrementPlayerData("gold", -gold_playerFrom, source)
playerFromData.nonSerializeData.incrementPlayerData("gold", gold_playerTo * 0.7, source)
playerToData.nonSerializeData.incrementPlayerData("gold", -gold_playerTo, source)
playerToData.nonSerializeData.incrementPlayerData("gold", gold_playerFrom * 0.7, source)
-- replicate these changes
playerFromData.nonSerializeData.playerDataChanged:Fire("inventory")
playerToData.nonSerializeData.playerDataChanged:Fire("inventory")
return true, "ALL GOOD!!!"
else
return false, playerTo.Name .. " does not have inventory space."
end
else
return false, playerFrom.Name .. " does not have inventory space."
end
end
return false, "ERROR!"
end
-- inventoryTransferDataCollection_player1 refers to the data player1 is going to transfer to player2 and vice versa
local function onTradeRequestReceived(player1, inventoryTransferDataCollection_player1, gold_player1, player2, inventoryTransferDataCollection_player2, gold_player2)
if not configuration.getConfigurationValue("isTradingEnabled", player1) or not configuration.getConfigurationValue("isTradingEnabled", player2) then
return false, "This feature has been disabled"
end
if player1:FindFirstChild("DataSaveFailed") then
network:fireClient("alertPlayerNotification", player1, {text = "Cannot trade during DataStore outage."; textColor3 = Color3.fromRGB(255, 57, 60)})
return false, "This feature is temporarily disabled"
end
if player1 and player2 and inventoryTransferDataCollection_player1 and inventoryTransferDataCollection_player2 then
do -- todo: fix this ugly hack
local p1in = {}
for i,v in pairs(inventoryTransferDataCollection_player1) do
table.insert(p1in, v)
end
local p2in = {}
for i,v in pairs(inventoryTransferDataCollection_player2) do
table.insert(p2in, v)
end
inventoryTransferDataCollection_player1 = p1in
inventoryTransferDataCollection_player2 = p2in
end
if player1:FindFirstChild("DataSaveFailed") then
network:fireClient("alertPlayerNotification", player1, {text = "Cannot trade during DataStore outage. Trade canceled."; textColor3 = Color3.fromRGB(255, 57, 60)})
network:fireClient("alertPlayerNotification", player2, {text = "Other player is experiencing a DataStore outage. Trade canceled."; textColor3 = Color3.fromRGB(255, 57, 60)})
return false, "This feature is temporarily disabled"
end
if player2:FindFirstChild("DataSaveFailed") then
network:fireClient("alertPlayerNotification", player2, {text = "Cannot trade during DataStore outage. Trade canceled."; textColor3 = Color3.fromRGB(255, 57, 60)})
network:fireClient("alertPlayerNotification", player1, {text = "Other player is experiencing a DataStore outage. Trade canceled."; textColor3 = Color3.fromRGB(255, 57, 60)})
return false, "This feature is temporarily disabled"
end
local inventoryTransferData_intermediateCollection_player1, wasInventoryTransferDataModified_player1 = int__getInventoryTransferData_intermediateCollectionFromInventoryTransferDataCollection(player1, inventoryTransferDataCollection_player1)
local inventoryTransferData_intermediateCollection_player2, wasInventoryTransferDataModified_player2 = int__getInventoryTransferData_intermediateCollectionFromInventoryTransferDataCollection(player2, inventoryTransferDataCollection_player2)
if not wasInventoryTransferDataModified_player1 then
if not wasInventoryTransferDataModified_player2 then
local success, reason = int__tradeItemsBetweenPlayers(player1, inventoryTransferData_intermediateCollection_player1, gold_player1, player2, inventoryTransferData_intermediateCollection_player2, gold_player2)
return success, reason
else
return false, player2.Name .. " has invalid data"
end
else
return false, player1.Name .. " has invalid data"
end
end
return false, "invalid data"
end
local function incrementPlayerStatPointsByStatName(player, statName)
local playerData = playerDataContainer[player]
if playerData and (statName == "dex" or statName == "int" or statName == "str" or statName == "vit") then
local usedPoints = playerData.statistics.dex + playerData.statistics.int + playerData.statistics.str + playerData.statistics.vit
if levels.getStatPointsForLevel(playerData.level) - usedPoints >= 1 then
playerData.statistics[statName] = playerData.statistics[statName] + 1
playerData.nonSerializeData.playerDataChanged:Fire("statistics")
applyMaxHealth(player, true)
return true, "All good"
else
return false, "not enough"
end
end
return false, "nope"
end
-- todo: handle 'swapping' and 'stack breaking/making' as one function based on the items involved
-- or something like that. it'll be a big function though, but they cross over too much to keep it separate
local function onRequestSplitInventorySlotDataStack(player, category, inventorySlotPosition, targetInventorySlotPosition, splitAmount)
splitAmount = splitAmount or 1
splitAmount = math.floor(splitAmount)
if splitAmount > 0 then
local inventorySlotData, trueInventorySlotPosition = onGetPlayerInventorySlotDataByInventorySlotPosition(player, category, inventorySlotPosition)
if inventorySlotData then
local itemBaseData = itemLookup[inventorySlotData.id]
if itemBaseData and itemBaseData.canStack then
local targetInventorySlotData = onGetPlayerInventorySlotDataByInventorySlotPosition(player, category, targetInventorySlotPosition)
if not targetInventorySlotData then
local cap = math.min((itemBaseData.stackSize or MAX_COUNT_PER_STACK), inventorySlotData.stacks)
local real_split_amount = math.clamp(splitAmount, 1, cap)
local diff = cap - real_split_amount
local itemDataForInv = {id = inventorySlotData.id; count = real_split_amount}
table.insert(playerDataContainer[player].inventory, itemDataForInv)
-- empty stack now :CC
if diff == 0 then
table.remove(playerDataContainer[player].inventory, trueInventorySlotPosition)
else
inventorySlotData.stacks = inventorySlotData.stacks - real_split_amount
end
-- update the inventory slots to assign this new slot a position value
updateInventorySlots(player)
-- force an inventory refresh
onClientRequestPropogateCacheData(player, "inventory")
return true
else
-- targetInventorySlotData is there
if targetInventorySlotData.id == inventorySlotData.id and targetInventorySlotData.stacks and targetInventorySlotData.stacks < (itemBaseData.stackSize or MAX_COUNT_PER_STACK) then
local splitDifference = math.clamp(splitAmount, 1, math.min(inventorySlotData.stacks, (itemBaseData.stackSize or MAX_COUNT_PER_STACK)))
if inventorySlotData.stacks >= splitDifference then
-- clamp this to not overfill the target slot
splitDifference = math.clamp(splitDifference, 1, (itemBaseData.stackSize or MAX_COUNT_PER_STACK) - targetInventorySlotData.stacks)
-- remove from old stack
if inventorySlotData.stacks - splitDifference == 0 then
-- diff is zero, remove old stack!!!!!!
table.remove(playerDataContainer[player].inventory, trueInventorySlotPosition)
else
inventorySlotData.stacks = inventorySlotData.stacks - splitDifference
end
-- add to new stack
targetInventorySlotData.stacks = targetInventorySlotData.stacks + splitDifference
-- update the inventory slots to assign this new slot a position value
updateInventorySlots(player)
-- force an inventory refresh
onClientRequestPropogateCacheData(player, "inventory")
return true
end
end
end
end
end
end
return false
end
-- todo: further validation, rn it just gives it to them
local function onRequestAddItemToInventoryReceived(player, itemId, stacks, metadata)
-- default to one stack!
stacks = stacks or 1
if playerDataContainer[player] and itemLookup[itemId] then
local itemBaseData = itemLookup[itemId]
if itemBaseData then
-- todo, probably use a copy of inventory to prevent tampering?
if itemBaseData.canStack then
while stacks > 0 do
local currentInventorySlotData = getInventorySlotByItemId(player, itemId, true)
if currentInventorySlotData then
local amountTaken = math.clamp(stacks, 1, (itemBaseData.stackSize or MAX_COUNT_PER_STACK) - currentInventorySlotData.stacks)
currentInventorySlotData.stacks = currentInventorySlotData.stacks + amountTaken
stacks = stacks - amountTaken
else
local amountTaken = math.clamp(stacks, 1, (itemBaseData.stackSize or MAX_COUNT_PER_STACK))
table.insert(playerDataContainer[player].inventory, {id = itemId; stacks = amountTaken})
stacks = stacks - amountTaken
end
end
-- update the inventory slots to assign this new slot a position value
updateInventorySlots(player)
-- force an inventory refresh
onClientRequestPropogateCacheData(player, "inventory")
return true
else
stacks = stacks or 1
local inventoryTransferDataCollection = {} do
for i = 1, stacks do
local itemDataForInv = {id = itemId}
for i, v in pairs(metadata or {}) do
itemDataForInv[i] = v
end
table.insert(inventoryTransferDataCollection, itemDataForInv)
end
end
local hasInventorySpace = int__doesPlayerHaveInventorySpaceForTrade(player, {}, inventoryTransferDataCollection)
if hasInventorySpace then
grantPlayerItemsByInventoryTranferData_intermediateCollection(player, inventoryTransferDataCollection)
-- update the inventory slots to assign this new slot a position value
updateInventorySlots(player)
-- force an inventory refresh
onClientRequestPropogateCacheData(player, "inventory")
return true
end
end
end
end
return false
end
-- todo: check for category
local function onSwitchInventorySlotDataRequestReceived(player, category, inventorySlotNumber1, inventorySlotNumber2)
if playerDataContainer[player] and inventorySlotNumber1 and inventorySlotNumber2 then
if inventorySlotNumber1 > 0 and inventorySlotNumber2 > 0 and inventorySlotNumber1 ~= inventorySlotNumber2 and category then
-- lock the state of the player's Inventory to prevent duplications
local playerInventoryCopy = utilities.copyTable(playerDataContainer[player].inventory)
local inventorySlotNumber1_Index
local inventorySlotNumber2_Index
for i, inventorySlotData in pairs(playerInventoryCopy) do
local itemBaseData = itemLookup[inventorySlotData.id]
if itemBaseData and itemBaseData.category == category then
if inventorySlotData.position == inventorySlotNumber1 then
inventorySlotNumber1_Index = i
elseif inventorySlotData.position == inventorySlotNumber2 then
inventorySlotNumber2_Index = i
end
end
end
if inventorySlotNumber1_Index and inventorySlotNumber2_Index then
playerInventoryCopy[inventorySlotNumber1_Index].position = inventorySlotNumber2
playerInventoryCopy[inventorySlotNumber2_Index].position = inventorySlotNumber1
elseif inventorySlotNumber1_Index and not inventorySlotNumber2_Index then
playerInventoryCopy[inventorySlotNumber1_Index].position = inventorySlotNumber2
elseif not inventorySlotNumber1_Index and inventorySlotNumber2_Index then
playerInventoryCopy[inventorySlotNumber2_Index].position = inventorySlotNumber1
else
-- request was denied, force a refresh
onClientRequestPropogateCacheData(player, "inventory")
return false
end
playerDataContainer[player].nonSerializeData.setPlayerData("inventory", playerInventoryCopy)
return true
end
end
-- request was denied, force a refresh
onClientRequestPropogateCacheData(player, "inventory")
return false
end
local function getEquipmentDataByPosition(equipment, position)
for index, data in pairs(equipment) do
if data.position == position then
return data, index
end
end
return nil, nil
end
-- todo make sure equipmentSlotPosition is valid for the weapon being moved, deny if not
local function onTransferInventoryToEquipment(player, category, inventorySlotPosition, equipmentSlotPosition)
if playerDataContainer[player] and inventorySlotPosition and equipmentSlotPosition then
if inventorySlotPosition > 0 and equipmentSlotPosition > 0 then
local playerData = playerDataContainer[player]
-- we make copies to prevent duplications or to prevent the player from doing something
-- fishy when the client script is hung waiting for this to return (ie due to ping)
local playerInventoryCopy = utilities.copyTable(playerData.inventory)
local playerEquipmentCopy = utilities.copyTable(playerData.equipment)
local trueInventorySlotPosition
for i, inventorySlotData in pairs(playerInventoryCopy) do
local itemBaseData = itemLookup[inventorySlotData.id]
if itemBaseData and itemBaseData.category == category then
if inventorySlotData.position == inventorySlotPosition and category then
if not itemBaseData.minLevel or playerData.level >= itemBaseData.minLevel then
if not itemBaseData.minimumClass or isPlayerOfClass(player, itemBaseData.minimumClass) then
trueInventorySlotPosition = i
else
return false, "incorrect class"
end
else
return false, "not high enough level"
end
end
end
end
local trueEquipmentSlotPosition
for i, equipmentSlotData in pairs(playerEquipmentCopy) do
if equipmentSlotData.position == equipmentSlotPosition then
trueEquipmentSlotPosition = i
end
end
if trueInventorySlotPosition then
local baseData = itemLookup[playerInventoryCopy[trueInventorySlotPosition].id]
if baseData.equipmentPosition == mapping.equipmentPosition.arrow then
print("in arrow exception")
if not trueEquipmentSlotPosition then
table.insert(playerEquipmentCopy, {
position = mapping.equipmentPosition.arrow;
id = baseData.id;
stacks = 0;
})
else
playerEquipmentCopy[trueEquipmentSlotPosition].id = baseData.id;
end
playerDataContainer[player].nonSerializeData.setPlayerData("equipment", playerEquipmentCopy)
events:fireEventLocal("playerEquipmentChanged", player)
return
end
-- validate that the equip is ok
local isWeapon = baseData.equipmentSlot == 1
local isWeaponSlot = (equipmentSlotPosition == 1) or (equipmentSlotPosition == 11)
local equippingWeaponToWeaponSlot = isWeapon and isWeaponSlot
local equippingToExactSlot = baseData.equipmentSlot == equipmentSlotPosition
local equipLegal = equippingWeaponToWeaponSlot or equippingToExactSlot
-- only allow berzerkers to equip weapons to offhand
if baseData.equipmentType == "sword" and equipmentSlotPosition == 11 then
equipLegal = equipLegal and isPlayerOfClass(player, "berserker")
end
-- only allow knights to equip shields to offhand
if baseData.equipmentType == "shield" and equipmentSlotPosition == 11 then
equipLegal = equipLegal and isPlayerOfClass(player, "knight")
end
-- hunters can equip a dagger and a bow, but not two of each
if baseData.equipmentType == "dagger" or baseData.equipmentType == "bow" then
if equipmentSlotPosition == 1 then
local offhand = getEquipmentDataByPosition(playerEquipmentCopy, 11)
if offhand then
offhand = itemLookup[offhand.id]
if offhand then
equipLegal = equipLegal and (offhand.equipmentType ~= baseData.equipmentType)
end
end
elseif equipmentSlotPosition == 11 then
local mainHand = getEquipmentDataByPosition(playerEquipmentCopy, 1)
if mainHand then
mainHand = itemLookup[mainHand.id]
if mainHand then
equipLegal = equipLegal and (mainHand.equipmentType ~= baseData.equipmentType)
end
end
end
end
-- make some offhands illegal
if equipmentSlotPosition == 11 then
local equipmentType = baseData.equipmentType
if
equipmentType == "staff" or
equipmentType == "greatsword" or
equipmentType == "fishing-rod"
then
equipLegal = false
end
end
-- no offhands if you're holding a greatsword (unless it's an amulet)
if equipmentSlotPosition == 11 then
local mainHand = getEquipmentDataByPosition(playerEquipmentCopy, 1)
if mainHand then
mainHand = itemLookup[mainHand.id]
if mainHand then
if mainHand.equipmentType == "greatsword" and baseData.equipmentType ~= "amulet" then
equipLegal = false
end
end
end
end
-- no greatswords if you're holding an offhand (unless it's an amulet)
if equipmentSlotPosition == 1 and baseData.equipmentType == "greatsword" then
local offhand = getEquipmentDataByPosition(playerEquipmentCopy, 11)
if offhand then
offhand = itemLookup[offhand.id]
if offhand and (offhand.equipmentType ~= "amulet") then
equipLegal = false
end
end
end
-- now we can use it
if not equipLegal then
return false, "attempt to equip illegal item"
end
end
local function onEquipped(itemId, equipmentSlot)
local itemData = itemLookup[itemId]
if itemData.perks then
for perkName, _ in pairs(itemData.perks) do
local perkData = perkLookup[perkName]
if perkData and perkData.onEquipped then
local success, err = pcall(function()
perkData.onEquipped(player, itemData, tostring(equipmentSlot))
end)
if not success then
warn(string.format("item %s equip failed because: %s", itemData.name, err))
end
end
end
end
end
local function onUnequipped(itemId, equipmentSlot)
local itemData = itemLookup[itemId]
if itemData.perks then
for perkName, _ in pairs(itemData.perks) do
local perkData = perkLookup[perkName]
if perkData and perkData.onUnequipped then
local success, err = pcall(function()
perkData.onUnequipped(player, itemData, tostring(equipmentSlot))
end)
if not success then
warn(string.format("item %s unequip failed because: %s", itemData.name, err))
end
end
end
end
end
-- never allow real swapping with arrows
if equipmentSlotPosition == mapping.equipmentPosition.arrow then
return false
end
if not trueInventorySlotPosition and trueEquipmentSlotPosition then
-- inventory is empty, equipment is not empty.
-- move equipment to that slot
if not int__doesPlayerHaveInventorySpaceForTrade(player, {}, {playerEquipmentCopy[trueEquipmentSlotPosition]}) then
-- denied, force a refresh
onClientRequestPropogateCacheData(player, "inventory")
onClientRequestPropogateCacheData(player, "equipment")
warn("inventory is full, no go")
return false
end
local equipmentSlotData = table.remove(playerEquipmentCopy, trueEquipmentSlotPosition)
equipmentSlotData.position = inventorySlotPosition
table.insert(playerInventoryCopy, equipmentSlotData)
playerDataContainer[player].nonSerializeData.setPlayerData("inventory", playerInventoryCopy)
playerDataContainer[player].nonSerializeData.setPlayerData("equipment", playerEquipmentCopy)
events:fireEventLocal("playerEquipmentChanged", player)
onUnequipped(equipmentSlotData.id, equipmentSlotPosition)
return true
elseif trueInventorySlotPosition and trueEquipmentSlotPosition then
-- inventory is not empty, equipment is not empty.
local inventorySlotData = table.remove(playerInventoryCopy, trueInventorySlotPosition)
inventorySlotData.position = equipmentSlotPosition
local equipmentSlotData = table.remove(playerEquipmentCopy, trueEquipmentSlotPosition)
equipmentSlotData.position = inventorySlotPosition
table.insert(playerInventoryCopy, equipmentSlotData)
table.insert(playerEquipmentCopy, inventorySlotData)
playerDataContainer[player].nonSerializeData.setPlayerData("inventory", playerInventoryCopy)
playerDataContainer[player].nonSerializeData.setPlayerData("equipment", playerEquipmentCopy)
events:fireEventLocal("playerEquipmentChanged", player)
onUnequipped(equipmentSlotData.id, equipmentSlotPosition)
onEquipped(inventorySlotData.id, equipmentSlotPosition)
return true
elseif trueInventorySlotPosition and not trueEquipmentSlotPosition then
-- inventory is not empty, equipment is empty.
local inventorySlotData = table.remove(playerInventoryCopy, trueInventorySlotPosition)
inventorySlotData.position = equipmentSlotPosition
table.insert(playerEquipmentCopy, inventorySlotData)
playerDataContainer[player].nonSerializeData.setPlayerData("inventory", playerInventoryCopy)
playerDataContainer[player].nonSerializeData.setPlayerData("equipment", playerEquipmentCopy)
events:fireEventLocal("playerEquipmentChanged", player)
onEquipped(inventorySlotData.id, equipmentSlotPosition)
return true
else
-- wtf?
--warn("wtf?!?!")
onClientRequestPropogateCacheData(player, "inventory")
onClientRequestPropogateCacheData(player, "equipment")
return false
end
end
end
warn("straight up denial")
-- denied, force a refresh
onClientRequestPropogateCacheData(player, "inventory")
onClientRequestPropogateCacheData(player, "equipment")
return false
end
local function swapPlayerWeapons(player)
local playerData = playerDataContainer[player]
if not playerData then return end
local equipment = playerData.equipment
if not equipment then return end
local equipmentCopy = utilities.copyTable(equipment)
local mainHand, mainHandIndex, offhand, offhandIndex
for index, slotData in pairs(equipmentCopy) do
if slotData.position == 1 then
mainHand = itemLookup[slotData.id]
mainHandIndex = index
elseif slotData.position == 11 then
offhand = itemLookup[slotData.id]
offhandIndex = index
end
end
-- only swap if we have two weapons
if (not mainHand) or (not offhand) then return end
local shouldSwap = false
-- cases where swap is valid, for now only dagger/bow
if mainHand.equipmentType == "dagger" then
shouldSwap = offhand.equipmentType == "bow"
elseif mainHand.equipmentType == "bow" then
shouldSwap = offhand.equipmentType == "dagger"
end
-- perform the swap
if not shouldSwap then return end
equipmentCopy[mainHandIndex].position = 11
equipmentCopy[offhandIndex].position = 1
playerData.nonSerializeData.setPlayerData("equipment", equipmentCopy)
local function firePerkEvents(itemData, oldSlot, newSlot)
if itemData.perks then
for perkName, _ in pairs(itemData.perks) do
local perkData = perkLookup[perkName]
if perkData and perkData.onUnequipped then
perkData.onUnequipped(player, itemData, oldSlot)
end
if perkData and perkData.onEquipped then
perkData.onEquipped(player, itemData, newSlot)
end
end
end
end
firePerkEvents(mainHand, "1", "11")
firePerkEvents(offhand, "11", "1")
events:fireEventLocal("playerEquipmentChanged", player)
end
-- Give clients a section of their data file that they can freely modify/read (maybe with some sanity checks)
local function changePlayerSetting(player, setting, value)
local playerData = getPlayerData(player)
if playerData and playerData.userSettings then
-- todo: maybe do some basic sanity checks?
if type(setting) == "string" and #setting < 40 then
playerData.userSettings[setting] = value
onClientRequestPropogateCacheData(player, "userSettings")
return true
end
end
end
-- keybind stuff
local function playerRequestSetKeyAction(player,key,action)
-- validation
if type(action) == "string" and #action < 40 then
-- store the key preference
local playerData = getPlayerData(player)
local preferences = playerData.userSettings.keybinds or {}
-- remove old keybinds
for key, existingAction in pairs(preferences) do
if existingAction == action then
preferences[key] = nil
end
end
preferences[key] = action
playerData.userSettings.keybinds = preferences
onClientRequestPropogateCacheData(player, "userSettings")
return true
end
end
local function onDamagePlayer(player, damage)
player.Character.PrimaryPart.health.Value = player.Character.PrimaryPart.health.Value - damage
end
local function getTrueInventorySlotDataByInventorySlotDataFromPlayer(player, inventorySlotDataFromPlayer)
local playerData = playerDataContainer[player]
if playerData then
for trueInventorySlot, inventorySlotData in pairs(playerData.inventory) do
if inventorySlotData.position == inventorySlotDataFromPlayer.position and inventorySlotData.id == inventorySlotDataFromPlayer.id then
return trueInventorySlot, inventorySlotData
end
end
end
return nil, nil
end
local function getTrueEquipmentSlotDataByEquipmentSlotDataFromPlayer(player, equipmentSlotDataFromPlayer)
local playerData = playerDataContainer[player]
if playerData then
local itemBaseDataFromPlayer = itemLookup[equipmentSlotDataFromPlayer.id]
for trueEquipmentSlot, equipmentSlotData in pairs(playerData.equipment) do
local itemBaseData = itemLookup[equipmentSlotData.id]
if equipmentSlotData.position == equipmentSlotDataFromPlayer.position and itemBaseData.category == itemBaseDataFromPlayer.category then
return trueEquipmentSlot, equipmentSlotData
end
end
end
return nil, nil
end
-- dont mind me just inserting this here
local megaphoneConnection
local isMessagingEnabled, messagingError = pcall(function()
megaphoneConnection = game:GetService("MessagingService"):SubscribeAsync("megaphone", function(message)
network:fireAllClients("signal_alertChatMessage", {Text = message.Data; Font = Enum.Font.SourceSansBold; Color = Color3.fromRGB(196, 209, 216)} )
end)
end)
if not isMessagingEnabled then
warn("Failed to enable MessagingService", messagingError)
end
local function int__decrementStackSizeForInventorySlotData(player, trueInventorySlotDataPosition, stacksToRemove)
if not player or not playerDataContainer[player] then return nil end
stacksToRemove = stacksToRemove or 1
local inventorySlotData = playerDataContainer[player].inventory[trueInventorySlotDataPosition]
if inventorySlotData then
local stacksRemoved = math.clamp(stacksToRemove, 0, inventorySlotData.stacks)
if inventorySlotData.stacks and inventorySlotData.stacks >= stacksToRemove then
inventorySlotData.stacks = inventorySlotData.stacks - stacksToRemove
if inventorySlotData.stacks <= 0 then
table.remove(playerDataContainer[player].inventory, trueInventorySlotDataPosition)
end
else
stacksRemoved = inventorySlotData.stacks
table.remove(playerDataContainer[player].inventory, trueInventorySlotDataPosition)
end
-- update the inventory
onClientRequestPropogateCacheData(player, "inventory")
return stacksRemoved
end
return 0
end
-- removes _stacks[=1] stacks from inventorySlotData at position `inventorySlotPosition`
local function onRemovePlayerInventorySlotData(player, inventorySlotData, stacksToRemove)
if not player or not playerDataContainer[player] or not inventorySlotData then return nil end
local itemBaseData = itemLookup[inventorySlotData.id]
stacksToRemove = math.clamp(stacksToRemove or 1, 1, (itemBaseData.stackSize or MAX_COUNT_PER_STACK))
local playerData = playerDataContainer[player]
local itemBaseData = itemLookup[inventorySlotData.id]
if itemBaseData then
if itemBaseData.canStack then
for inventorySlotPosition, trueInventorySlotData in pairs(playerData.inventory) do
if trueInventorySlotData.position == inventorySlotData.position and trueInventorySlotData.id == inventorySlotData.id then
local canRemoveStacksRequested = ((trueInventorySlotData.stacks or 1) - stacksToRemove) >= 0
if canRemoveStacksRequested then
if trueInventorySlotData.stacks then
trueInventorySlotData.stacks = trueInventorySlotData.stacks
end
else
return false
end
end
end
else
-- item doesn't stack, can only remove one stack from this item (so entire item)
if stacksToRemove <= 1 then
for inventorySlotPosition, trueInventorySlotData in pairs(playerData.inventory) do
if trueInventorySlotData.position == inventorySlotData.position and trueInventorySlotData.id == inventorySlotData.id then
table.remove(playerData.inventory, inventorySlotPosition)
-- update the inventory
onClientRequestPropogateCacheData(player, "inventory")
return true
end
end
else
return false, "attempt to remove more than 1 stack from non-stackable item"
end
end
end
warn("just nothing found")
return false, 0
end
-- let the player be authoritative in this regard, it's their personal data anyway.
local function onRegisterHotbarSlotData(player, dataType, id, position)
if not playerDataContainer[player] then return end
if not id or not dataType then
if position then
local alteration = false
-- clear out the position for this one
for i, hotbarSlotData in pairs(playerDataContainer[player].hotbar) do
if hotbarSlotData.position == position then
table.remove(playerDataContainer[player].hotbar, i)
alteration = true
end
end
if alteration then
-- update the inventory
onClientRequestPropogateCacheData(player, "hotbar")
return true
end
end
elseif position then
-- clear out the position for this one
for i, hotbarSlotData in pairs(playerDataContainer[player].hotbar) do
if hotbarSlotData.position == position then
table.remove(playerDataContainer[player].hotbar, i)
end
end
table.insert(playerDataContainer[player].hotbar, {dataType = dataType; id = id; position = position})
-- update the inventory
onClientRequestPropogateCacheData(player, "hotbar")
return true
end
end
local seatsTaken = {}
local function isSeatTaken(seat)
for i, otherSeat in pairs(seatsTaken) do
if otherSeat == seat then
return true
end
end
end
local function onReplicateClientStateChanged(player, state, stateVariant, otherData)
-- never replicate the dead state, client is not incharge of this.
if state == "dead" then
return false
end
stateVariant = stateVariant or ""
if player.Character and player.Character.PrimaryPart and (player.Character.PrimaryPart.state.Value ~= state or player.Character.PrimaryPart.state.variant.Value ~= stateVariant) and player.Character.PrimaryPart.state.Value ~= "dead" then
local previousState = player.Character.PrimaryPart.state.Value
player.Character.PrimaryPart.state.variant.Value = stateVariant or ""
player.Character.PrimaryPart.state.Value = state
-- handle sitting/unsitting
if state == "sitting" and otherData then
-- new state is sitting
local existingSeat = seatsTaken[player.Name]
if existingSeat then
game.CollectionService:AddTag(existingSeat,"interact")
seatsTaken[player.Name] = nil
end
if isSeatTaken(otherData) then
-- tell the client to go away
player.Character.PrimaryPart.Anchored = false
player.Character.PrimaryPart:SetNetworkOwner(player)
return false
else
player.Character.PrimaryPart:SetNetworkOwner(nil)
player.Character.PrimaryPart.Anchored = true
seatsTaken[player.Name] = otherData
game.CollectionService:RemoveTag(otherData, "interact")
end
if seatsTaken[player.Name] == otherData then
player.Character.PrimaryPart.grounder.Position = otherData.CFrame.p + Vector3.new(0, 0.5, 0)
player.Character.PrimaryPart.hitboxVelocity.Velocity = Vector3.new()
player.Character.PrimaryPart.hitboxGyro.CFrame = CFrame.new(otherData.CFrame.p + Vector3.new(0, 0.5, 0), otherData.CFrame.p + Vector3.new(0, 0.5, 0) + otherData.CFrame.lookVector)
player.Character.PrimaryPart.CFrame = otherData.CFrame + Vector3.new(0, 0.5, 0)
end
elseif previousState == "sitting" then
if otherData and otherData == "override" then
return false
end
-- old state was sitting
player.Character.PrimaryPart.Anchored = false
player.Character.PrimaryPart:SetNetworkOwner(player)
for userName,seat in pairs(seatsTaken) do
if userName == player.Name or game.Players:FindFirstChild(userName) == nil then
game.CollectionService:AddTag(seat,"interact")
seatsTaken[player.Name] = nil
end
end
elseif state == "gettingUp" and otherData then
local MINIMUM_DAMAGE = 30
local MAXIMUM_DAMAGE = 120
local MINIMUM_DAMAGE_FALL_DISTANCE = 100
local MAXIMUM_DAMAGE_FALL_DISTANCE = 400
local playerData = playerDataContainer[player]
local fallingDistance = math.clamp(math.abs(otherData), MINIMUM_DAMAGE_FALL_DISTANCE, MAXIMUM_DAMAGE_FALL_DISTANCE)
local damageTaken = math.floor(MINIMUM_DAMAGE + (MAXIMUM_DAMAGE - MINIMUM_DAMAGE) * ((fallingDistance - MINIMUM_DAMAGE_FALL_DISTANCE) / (MAXIMUM_DAMAGE_FALL_DISTANCE - MINIMUM_DAMAGE_FALL_DISTANCE))) * math.clamp(1 - playerData.nonSerializeData.statistics_final.featherFalling, 0, 1)
player.Character.PrimaryPart.health.Value = math.clamp(player.Character.PrimaryPart.health.Value - damageTaken, 0, player.Character.PrimaryPart.maxHealth.Value)
end
end
end
local function onReplicateClientWeaponStateChanged(player, weaponState)
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.weaponState.Value ~= weaponState and player.Character.PrimaryPart.state.Value ~= "dead" then
local previousState = player.Character.PrimaryPart.weaponState.Value
local playerData = playerDataContainer[player]
local equipmentData = onGetPlayerEquipmentDataByEquipmentPosition(player, 1)
local weaponBaseData = equipmentData and itemLookup[equipmentData.id] or nil
-- block bad requests
if weaponState ~= nil and weaponState ~= "" then
if not weaponBaseData then
return false
elseif weaponBaseData.equipmentType == "bow" then
if weaponState ~= "stretched" and weaponState ~= "firing" then
return false
end
else
return false
end
end
player.Character.PrimaryPart.weaponState.Value = weaponState or ""
end
end
-- NOTE: This is not the same route that state animations take (ie, walking running etc!)
local function onReplicatePlayerAnimationSequence(player, animationCollection, animationName, extraData)
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.state.Value ~= "dead" then
if playerDataContainer[player] and (animationCollection == "swordAnimations" or animationCollection == "staffAnimations" or animationCollection == "daggerAnimations" or animationCollection == "bowAnimations") then
extraData = extraData or {}
extraData.attackSpeed = playerDataContainer[player].nonSerializeData.statistics_final.attackSpeed or 0
end
if animationCollection == "staffAnimations" and extraData then
if player.Character.PrimaryPart.mana.Value < configuration.getConfigurationValue("mageManaDrainFromBasicAttack") then
extraData.noRangeManaAttack = true
end
end
-- let the server know we are replicating this
network:fire("playerAnimationReplicated", player, animationCollection, animationName, extraData)
-- replicate this to other clients
network:fireAllClientsExcludingPlayer("replicatePlayerAnimationSequence", player, player, animationCollection, animationName, extraData)
end
end
local function isPlayerNearResetCharacter(player)
local char = player.Character
if not char then return false end
local manifest = char.PrimaryPart
if not manifest then return false end
local resetCharacters = game:GetService("CollectionService"):GetTagged("resetCharacter")
if #resetCharacters == 0 then return false end
for _, resetCharacter in pairs(resetCharacters) do
local root = resetCharacter.PrimaryPart
if root then
local distance = (manifest.Position - root.Position).Magnitude
if distance <= 8 then
return true
end
end
end
return false
end
local function playerRequest_transferInventoryToStorage(player, inventorySlotData)
if not configuration.getConfigurationValue("isTradingEnabled", player) then return false, "Storage has been disabled." end
if math.floor(inventorySlotData.stacks) ~= inventorySlotData.stacks then return false, "MagicRebirthed... BAD!" end
--if not player:FindFirstChild("QA") then return false, "Only testers can use Storage right now." end
if not configuration.getConfigurationValue("isStorageEnabled", player) then return false, "Storage is currently disabled" end
local playerData = playerDataContainer[player]
if playerData and #playerData.globalData.itemStorage < MAX_STORAGE_COUNT then
return int__transferInventoryToStorage(player, inventorySlotData)
elseif playerData then
return false, "Inventory full."
end
return false, "PlayerData not found."
end
local function playerRequest_transferStorageToInventory(player, storageSlotData)
if not configuration.getConfigurationValue("isTradingEnabled", player) then return false, "Storage has been disabled." end
if math.floor(storageSlotData.stacks) ~= storageSlotData.stacks then return false, "MagicRebirthed... BAD!" end
--if not player:FindFirstChild("QA") then return false, "Only testers can use Storage right now." end
if not configuration.getConfigurationValue("isStorageEnabled", player) then return false, "Storage is currently disabled" end
local playerData = playerDataContainer[player]
if playerData then
return int__transferStorageToInventory(player, storageSlotData)
end
return false, "PlayerData not found."
end
function module.init(Modules)
datastoreInterface = Modules.datastoreInterface
network = Modules.network
utilities = Modules.utilities
physics = Modules.physics
levels = Modules.levels
mapping = Modules.mapping
configuration = Modules.configuration
placeSetup = Modules.placeSetup
events = Modules.events
detection = Modules.detection
projectile = Modules.projectile
entityManifestCollectionFolder = placeSetup.getPlaceFolder("entityManifestCollection")
temporaryEquipmentFolder = placeSetup.getPlaceFolder("temporaryEquipment")
-- data manipulation
network:create("switchInventorySlotData", "RemoteFunction", "OnServerInvoke", onSwitchInventorySlotDataRequestReceived)
network:create("playerRequest_switchInventorySlotData", "RemoteFunction", "OnServerInvoke", onSwitchInventorySlotDataRequestReceived)
network:create("transferInventoryToEquipment", "RemoteFunction", "OnServerInvoke", onTransferInventoryToEquipment)
network:create("playerRequest_transferInventoryToEquipment", "RemoteFunction", "OnServerInvoke", onTransferInventoryToEquipment)
network:create("playerRequest_transferInventoryToStorage", "RemoteFunction", "OnServerInvoke", playerRequest_transferInventoryToStorage)
network:create("playerRequest_transferStorageToInventory", "RemoteFunction", "OnServerInvoke", playerRequest_transferStorageToInventory)
network:create("requestAddItemToInventory", "BindableFunction", "OnInvoke", onRequestAddItemToInventoryReceived)
network:create("onPlayerRemoving", "BindableFunction", "OnInvoke", onPlayerRemoving)
network:create("teleportPlayerCFrame_server", "BindableFunction", "OnInvoke", function(player, targetCFrame)
if playerPositionDataContainer[player] and player.Character and player.Character.PrimaryPart then
playerPositionDataContainer[player].positions = {{position = targetCFrame.p; velocity = Vector3.new()}}
player.Character.PrimaryPart.CFrame = targetCFrame
end
end)
network:create("playerRequestAlphaGift", "RemoteFunction", "OnServerInvoke")
network:create("playerRequestSetKeyAction","RemoteFunction","OnServerInvoke",playerRequestSetKeyAction)
network:create("playerRequest_changeAccessories", "RemoteFunction", "OnServerInvoke", playerRequest_changeAccessories)
network:create("playerRequest_swapWeapons", "RemoteEvent", "OnServerEvent", swapPlayerWeapons)
network:create("playerRequest_swapWeapons_yielding", "RemoteFunction", "OnServerInvoke", swapPlayerWeapons)
network:create("requestChangePlayerSetting","RemoteFunction","OnServerInvoke",changePlayerSetting)
-- data interfacing with client
network:create("getPropogationCacheLookupTable", "RemoteFunction", "OnServerInvoke", getPropogationCacheLookupTable)
network:create("propogateCacheDataRequest", "RemoteEvent", "OnServerEvent", onClientRequestPropogateCacheData)
network:create("clientFlushPropogationCache", "RemoteEvent", "OnServerEvent", onClientRequestFlushPropogationCache)
network:create("getPlayerEquipment", "RemoteFunction", "OnServerInvoke", onGetPlayerEquipment)
network:create("playerRequest_getPlayerEquipmentData", "RemoteFunction", "OnServerInvoke", onGetPlayerEquipment)
network:create("dataRecoveryRequested", "RemoteEvent", "OnServerEvent", onDataRecoveryRequested)
network:create("playerEquipmentChanged", "RemoteEvent")
network:create("loadPlayerData", "RemoteFunction", "OnServerInvoke", onPlayerAdded)
network:create("playerRequest_setupPlayerData", "RemoteFunction", "OnServerInvoke", onPlayerAdded)
network:create("requestSplitInventorySlotDataStack", "RemoteFunction", "OnServerInvoke", onRequestSplitInventorySlotDataStack)
network:create("playerRequest_splitInventorySlotDataStack", "RemoteFunction", "OnServerInvoke", onRequestSplitInventorySlotDataStack)
network:create("registerHotbarSlotData", "RemoteFunction", "OnServerInvoke", onRegisterHotbarSlotData)
network:create("playerRequest_getHotbarSlotData", "RemoteFunction", "OnServerInvoke", onRegisterHotbarSlotData)
network:create("replicateClientStateChanged", "RemoteEvent", "OnServerEvent", onReplicateClientStateChanged)
network:create("replicateClientWeaponStateChanged", "RemoteEvent", "OnServerEvent", onReplicateClientWeaponStateChanged)
network:create("playerRequest_equipTemporaryEquipment", "RemoteFunction", "OnServerInvoke", onPlayerRequest_equipTemporaryEquipment)
network:create("getIsPlayerOfClass", "RemoteFunction", "OnServerInvoke", isPlayerOfClass)
network:create("serverVerifyCharacterPosition", "RemoteEvent", "OnServerEvent", forceCharacterPosition)
-- data interfacing with server
network:create("getPlayerData", "BindableFunction", "OnInvoke", getPlayerData)
network:create("getPlayerData_remote", "RemoteFunction", "OnServerInvoke", getPlayerData_remote)
network:create("getPlayerEquipmentDataByEquipmentPosition", "BindableFunction", "OnInvoke", onGetPlayerEquipmentDataByEquipmentPosition)
network:create("getPlayerInventorySlotDataByInventorySlotPosition", "BindableFunction", "OnInvoke", onGetPlayerInventorySlotDataByInventorySlotPosition)
network:create("removePlayerInventorySlotData", "BindableFunction", "OnInvoke", onRemovePlayerInventorySlotData)
network:create("playerEquipmentChanged_server", "BindableEvent")
network:create("doesPlayerHaveInventorySpaceForTrade", "BindableFunction", "OnInvoke", int__doesPlayerHaveInventorySpaceForTrade)
network:create("getPlayerGlobalData", "BindableFunction", "OnInvoke", getPlayerGlobalData)
network:create("setPlayerGlobalData", "BindableFunction", "OnInvoke", setPlayerGlobalData)
network:create("serverChangePlayerSetting","BindableFunction","OnInvoke",changePlayerSetting)
network:create("getIsPlayerOfClass_server", "BindableFunction", "OnInvoke", isPlayerOfClass)
-- data routing
network:create("replicatePlayerAnimationSequence", "RemoteEvent", "OnServerEvent", onReplicatePlayerAnimationSequence)
-- events
network:create("playerCharacterDied", "BindableEvent")
network:create("alertPlayerNotification","RemoteEvent")
network:create("playerRequest_incrementPlayerStatPointsByStatName", "RemoteFunction", "OnServerInvoke", incrementPlayerStatPointsByStatName)
network:create("playerStatisticsChanged", "RemoteEvent")
network:create("playerRequest_respawnMyCharacter", "RemoteFunction", "OnServerInvoke", onPlayerRequest_respawnMyCharacter)
network:create("playerRequest_returnToMainMenu", "RemoteFunction", "OnServerInvoke", onPlayerRequest_returnToMainMenu)
network:create("requestTradeBetweenPlayers", "BindableFunction", "OnInvoke", onTradeRequestReceived)
network:create("deathGuiAccepted", "RemoteEvent", "OnServerEvent", onDeathGuiAccepted)
network:create("deathGuiRequested", "RemoteEvent")
network:create("promptPlayerDeathScreen","RemoteEvent")
network:create("playerCharacterLoaded", "BindableEvent")
network:create("deathTrapKnockback", "RemoteEvent")
network:create("logPerkActivation_server", "BindableEvent", "Event", onLogPerkActivation_server)
network:create("playerDataLoaded", "BindableEvent")
network:create("signal_inputChanged", "RemoteEvent", "OnServerEvent", signal_inputChanged)
network:create("playerAppliedScroll","RemoteEvent")
-- todo: probably tighten this
network:create("tradeItemsBetweenPlayerAndNPC", "BindableFunction", "OnInvoke", int__tradeItemsBetweenPlayerAndNPC)
network:create("playerAnimationReplicated", "BindableEvent")
network:create("setStamina", "RemoteEvent")
network:create("signal_exp", "RemoteEvent")
-- random teleport crap
network:create("saveDataForTeleportation", "RemoteFunction", "OnServerInvoke", saveDataForTeleport)
network:create("playerRequest_savePlayerDataForTeleportation", "RemoteFunction", "OnServerInvoke", saveDataForTeleport)
network:create("saveDataForTeleport", "BindableFunction", "OnInvoke", saveDataForTeleport)
network:create("externalTeleport", "RemoteEvent")
-- other trash
network:create("signal_alertChatMessage", "RemoteEvent")
network:create("openLoreBookFromServer", "RemoteEvent")
network:create("playerInventoryChanged_server", "BindableEvent")
network:create("playerWasExhausted", "RemoteEvent", "OnServerEvent", function()
end)
game.Players.PlayerRemoving:connect(onPlayerRemoving)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_product.lua
================================================
local module = {}
local paymentDataCache = {}
local ProductCache = {d = "1"}
local network
local function processPayment(player, receiptInfo, playerGlobalData)
if playerGlobalData == nil then
local playerData = network:invoke("getPlayerData", player)
if playerData and playerData.globalData then
playerGlobalData = playerData.globalData
else
return false
end
end
local additionalInfo = {}
local doFinishTransaction = false
-- 300 ethyr
if receiptInfo.ProductId == 509935760 then
playerGlobalData.ethyr = (playerGlobalData.ethyr or 0) + 300
doFinishTransaction = true
spawn(function()
network:invoke("reportCurrency", player, "ethyr", 300, "product:ethyr300")
end)
-- 120 ethyr
elseif receiptInfo.ProductId == 509934399 then
playerGlobalData.ethyr = (playerGlobalData.ethyr or 0) + 120
doFinishTransaction = true
spawn(function()
network:invoke("reportCurrency", player, "ethyr", 120, "product:ethyr120")
end)
-- 750 ethyr
elseif receiptInfo.ProductId == 509935018 then
playerGlobalData.ethyr = (playerGlobalData.ethyr or 0) + 750
doFinishTransaction = true
spawn(function()
network:invoke("reportCurrency", player, "ethyr", 750, "product:ethyr750")
end)
elseif receiptInfo.ProductId == 539152241 then
-- 3500 ethyr (3000 + 500 bonus)
playerGlobalData.ethyr = (playerGlobalData.ethyr or 0) + 3500
doFinishTransaction = true
spawn(function()
network:invoke("reportCurrency", player, "ethyr", 3500, "product:ethyr3500")
end)
end
return doFinishTransaction, playerGlobalData, additionalInfo
end
function module.init(Modules)
network = Modules.network
network:create("processPayment", "BindableFunction", "OnInvoke", processPayment)
network:create("signal_productPurchaseConfirmed", "RemoteEvent")
if game.PlaceId ~= 2376885433 and game.PlaceId ~= 2015602902 then
game:GetService("MarketplaceService").ProcessReceipt = function(receiptInfo)
local player = game.Players:GetPlayerByUserId(receiptInfo.PlayerId)
if not player then
-- no player, abort.
warn("aborted purchase due to missing player")
return Enum.ProductPurchaseDecision.NotProcessedYet
end
if player:FindFirstChild("DataSaveFailed") then
-- player data has failed to save, abort.
warn("aborted purchase due to save failure")
return Enum.ProductPurchaseDecision.NotProcessedYet
end
local processingStartTime = tick()
repeat wait(0.1) until player == nil or player.Parent ~= game.Players or network:invoke("getPlayerData", player) or tick() - processingStartTime > 15
local playerData = network:invoke("getPlayerData", player)
if playerData == nil then
warn("aborted purchase due to player missing")
return Enum.ProductPurchaseDecision.NotProcessedYet
end
local playerGlobalData = playerData.globalData
if playerGlobalData == nil then
warn("aborted purchase due to player global data missing")
return Enum.ProductPurchaseDecision.NotProcessedYet
end
paymentDataCache[player] = paymentDataCache[player] or {}
local playerPaymentDataCache = paymentDataCache[player]
playerPaymentDataCache.payments = playerPaymentDataCache.payments or {}
local doFinishTransaction
local modifiedGlobalPlayerData
local additionalInfo
-- Do not modify data if payment has already been cached this session
if playerPaymentDataCache.payments[receiptInfo.PurchaseId] then
doFinishTransaction = true
else
doFinishTransaction, modifiedGlobalPlayerData, additionalInfo = network:invoke("processPayment", player, receiptInfo, playerGlobalData)
end
if doFinishTransaction and modifiedGlobalPlayerData then
playerData.nonSerializeData.setPlayerData("globalData", modifiedGlobalPlayerData)
playerPaymentDataCache.payments[receiptInfo.PurchaseId] = true
spawn(function()
if additionalInfo.broadcast then
network:fireAllClients("signal_alertChatMessage", additionalInfo.broadcast)
end
local ProductName = ProductCache[receiptInfo.ProductId]
if ProductName == nil then
local Product = game.MarketplaceService:GetProductInfo(receiptInfo.ProductId,Enum.InfoType.Product)
if Product then
ProductName = Product.Name
ProductCache[receiptInfo.ProductId] = ProductName
else
ProductName = "unknown"
end
end
network:invoke("purchaseMade", player, "Product", ProductName, receiptInfo.CurrencySpent)
end)
return Enum.ProductPurchaseDecision.PurchaseGranted
end
end
end
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_profession.lua
================================================
local module = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local network
local levels
local professionLookup = require(ReplicatedStorage:WaitForChild("professionLookup"))
local function getProfessionLevel(player, profession)
local playerData = network:invoke("getPlayerData", player)
if playerData then
if professionLookup[profession] then
playerData.professions[profession] = playerData.professions[profession] or {level = 1, exp = 0}
return playerData.professions[profession].level
end
end
end
local function grantProfessionExp(player, profession, exp)
local playerData = network:invoke("getPlayerData", player)
if playerData then
-- professions are not hard-coded, can be added at any time.
playerData.professions[profession] = playerData.professions[profession] or {level = 1, exp = 0}
local professionData = playerData.professions[profession]
local expForNextLevel = levels.getEXPToNextLevel(professionData.level)
if professionData.exp >= expForNextLevel then
-- profession level up!
professionData.exp = professionData.exp - expForNextLevel
professionData.level = professionData.level + 1
local professionTag = player.professions:FindFirstChild(profession)
if professionTag then
professionTag.Value = professionData.level
end
if player.Character and player.Character.PrimaryPart then
--[[
local Sound = Instance.new("Sound")
Sound.Volume = 0.7
Sound.MaxDistance = 500
Sound.SoundId = "rbxassetid://2066645345"
Sound.Parent = player.Character.PrimaryPart
Sound:Play()
game.Debris:AddItem(Sound,10)
]]
local Attach = Instance.new("Attachment")
Attach.Parent = player.Character.PrimaryPart
Attach.Orientation = Vector3.new(0,0,0)
Attach.Axis = Vector3.new(1,0,0)
Attach.SecondaryAxis = Vector3.new(0,1,0)
local particle = script.particles.Wave:Clone()
particle.Parent = Attach
particle.Color = ColorSequence.new(professionData.color)
particle:Emit(1)
game.Debris:AddItem(Attach, 5)
end
end
playerData.nonSerializeData.playerDataChanged:Fire("professions")
end
end
function module.init(Modules)
network = Modules.network
levels = Modules.levels
network:create("getProfessionLevel", "BindableFunction", "OnInvoke", getProfessionLevel)
network:create("grantProfessionExp", "BindableFunction", "OnInvoke", grantProfessionExp)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_pvp.lua
================================================
local module = {}
-- from Player Manager, needs to be set up
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local network
local shuttingDown = false
-- TODO: give this a value LOL
local pvpZoneCollectionFolder = workspace.placeFolders.pvpZoneCollection
local function isPlayerInPVPZone(pvpZone, player)
if not player or not player.Character or not player.Character.PrimaryPart then return false end
local isInPVPZone
local points = pvpZone:GetChildren()
for i = 1, #points do
local point1 = pvpZone[tostring(i)]
local point2 = pvpZone[tostring(i == #points and 1 or i + 1)]
local isInsideFace = (point2.Position - point1.Position):Cross(player.Character.PrimaryPart.Position - point1.Position).Y < 0
if isInPVPZone ~= nil and isInsideFace ~= isInPVPZone then
return false
end
isInPVPZone = isInsideFace
end
if isInPVPZone then
local characterY = player.Character.PrimaryPart.Position.Y
local upperYBound = points[1].Position.Y + points[1].Size.Y / 2
local lowerYBound = points[1].Position.Y - points[1].Size.Y / 2
return characterY >= lowerYBound and characterY <= upperYBound
end
return isInPVPZone
end
local function int__tickForPVP()
if #pvpZoneCollectionFolder:GetChildren() == 0 and not ReplicatedStorage:FindFirstChild("isPVPGloballyEnabled") then return end
while not shuttingDown do
for _, player in pairs(game.Players:GetPlayers()) do
local playerData = network:invoke("getPlayerData", player)
if not playerData then return end
local currentPVPValue = playerData.nonSerializeData.isGlobalPVPEnabled
if ReplicatedStorage:FindFirstChild("isPVPGloballyEnabled") and ReplicatedStorage.isPVPGloballyEnabled.Value then
if currentPVPValue == false then
playerData.nonSerializeData.isGlobalPVPEnabled = true
playerData.nonSerializeData.playerDataChanged:Fire("nonSerializeData")
network:fireClient("alertPlayerNotification", player,
{text = "Entered global pvp map."; id = "pvp"},
nil,
"pvp"
)
end
else
local isInPVPZone = false
local isPVPZoneUnsafe = false
for i, pvpZone in pairs(pvpZoneCollectionFolder:GetChildren()) do
if isPlayerInPVPZone(pvpZone, player) then
isInPVPZone = true
if pvpZone.Name == "unsafe" then
isPVPZoneUnsafe = true
end
break
end
end
if currentPVPValue ~= isInPVPZone then
playerData.nonSerializeData.isGlobalPVPEnabled = isInPVPZone
playerData.nonSerializeData.isPVPZoneUnsafe = isPVPZoneUnsafe
playerData.nonSerializeData.playerDataChanged:Fire("nonSerializeData")
if isInPVPZone then
if isPVPZoneUnsafe then
network:fireClient("alertPlayerNotification", player,
{text = "Entered unsafe global PVP zone."; id="pvp"},nil,"pvp")
else
network:fireClient("alertPlayerNotification", player,
{text = "Entered safe global PVP zone."; id="pvp"},nil,"pvp")
end
end
end
end
end
wait(1 / 3)
end
end
local function isPlayerPVPWhitelisted(player, playerToCheck)
local playerData = network:invoke("getPlayerData", player)
if playerData then
for i, whitelistPlayer in pairs(playerData.nonSerializeData.whitelistPVPEnabled) do
if whitelistPlayer == playerToCheck then
return true, i
end
end
end
end
local function requestPVPWhitelistPlayer_server(player, playerToWhitelist)
local playerData = network:invoke("getPlayerData", player)
if playerData then
local isPVPWhitelisted, index = isPlayerPVPWhitelisted(player, playerToWhitelist)
if not isPVPWhitelisted then
table.insert(playerData.nonSerializeData.whitelistPVPEnabled, playerToWhitelist)
playerData.nonSerializeData.playerDataChanged:Fire("nonSerializeData")
end
end
end
local function revokePVPWhitelistPlayer_server(player, playerToRevokeWhitelist)
local playerData = network:invoke("getPlayerData", player)
if playerData then
local isPVPWhitelisted, index = isPlayerPVPWhitelisted(player, playerToRevokeWhitelist)
if isPVPWhitelisted then
table.remove(playerData.nonSerializeData.whitelistPVPEnabled, index)
playerData.nonSerializeData.playerDataChanged:Fire("nonSerializeData")
end
end
end
function module.init(Modules)
network = Modules.network
network:create("requestPVPWhitelistPlayer_server", "BindableFunction", "OnInvoke", requestPVPWhitelistPlayer_server)
network:create("revokePVPWhitelistPlayer_server", "BindableFunction", "OnInvoke", revokePVPWhitelistPlayer_server)
spawn(int__tickForPVP)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_quest.lua
================================================
local module = {}
local network
local cachedPlayerQuestData = {}
function module.completeQuest(player, questId)
end
function module.returnPlayerQuestData(player)
local playerQuestData = cachedPlayerQuestData[player]
if playerQuestData then
return playerQuestData
elseif not playerQuestData and game.Players:FindFirstChild(player.Name) then
module.loadQuestData(player)
return module.returnPlayerQuestData(player)
end
end
function module.loadQuestData(player)
end
function module.saveQuestData(player)
end
function module.init(Modules)
network = Modules.network
network:create("questTriggerOccurred", "BindableEvent", "Event", function(player, trigger, data)
-- TODO: implement (ctrl+shift+F for occurances of questTriggerOccurred)
end)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_resources.lua
================================================
-- Resource Manager
-- Rocky28447 (for Vesteria) nah bro i thought it was for World Zero
-- May 28, 2020
local module = {}
local CollectionService = game:GetService("CollectionService")
local thread
local tableUtil
local placeSetup
local network
--[[
Manages resource harvesting server-side.
Internally tracks how many harvests each player
has left on each resource node. The structure
looks like this:
playerHarvestData = {
[player] = {
[exampleNodeInstance] = {
harvestsLeft = 6
}
}
}
]]--
local nodesFolder
local globalResourceNodeData = {}
local localResourceNodeData = {}
local function getNodeTypeMetadataFromNode(node)
local containingFolder = node:FindFirstAncestorWhichIsA("Folder")
local isNodeGroup = CollectionService:HasTag(containingFolder, "resourceNodeGroupFolder")
local nodeTypeMetadata = isNodeGroup and containingFolder.Parent.Metadata or containingFolder.Metadata
return nodeTypeMetadata
end
local function setupBaseNodeDataForPlayer(player)
local p = {}
localResourceNodeData[player] = p
return p
end
local function newNodeData(node)
local n = {}
local nodeTypeMetadata = require(getNodeTypeMetadataFromNode(node))
local dropPoints = node:FindFirstChild("DropPoints")
n.owners = {}
n.harvestsLeft = nodeTypeMetadata.Harvests
-- n.durability = 0
n.durability = nodeTypeMetadata.Durability
n.DropPoints = dropPoints and dropPoints:GetChildren() or {}
return n
end
local function getNodeDataForPlayer(node, player)
if localResourceNodeData[player] and localResourceNodeData[player][node] then
return localResourceNodeData[player][node]
end
local n = newNodeData(node)
if not localResourceNodeData[player] then
setupBaseNodeDataForPlayer(player)[node] = n
else
localResourceNodeData[player][node] = n
end
return n
end
local function getGlobalDataForNode(node)
if globalResourceNodeData[node] then
return globalResourceNodeData[node]
end
local n = newNodeData(node)
globalResourceNodeData[node] = n
return n
end
local function sumLootTableWeights(lootTable)
local totalWeight = 0
for _, item in pairs (lootTable) do
totalWeight = totalWeight + item.Chance
end
return totalWeight
end
local function rollLootTable(lootTable)
local rng = Random.new()
local roll = rng:NextNumber(0, 1)
local remainingDistance = sumLootTableWeights(lootTable) * roll
-- https://blog.bruce-hill.com/a-faster-weighted-random-choice
-- "Linear Scan" (since we probably aren't gonna be sorting through too many items)
-- This implementation means that in a drop table with 1 item, the chance can be any
-- positive non-zero number and it will still be picked 100% of the time.
for _, item in pairs (lootTable) do
remainingDistance = remainingDistance - item.Chance
if remainingDistance < 0 then
return item
end
end
end
local function calcDamageForNode(node, player)
local nodeTypeMetadata = require(getNodeTypeMetadataFromNode(node))
local playerData = network:invoke("getPlayerData", player)
if nodeTypeMetadata.NodeCategory then
return playerData.nonSerializeData.statistics_final[nodeTypeMetadata.NodeCategory]
end
return 1
end
local function getDepletedResourceNodes(player)
local depleted = {}
for node, data in pairs (globalResourceNodeData) do
if data.Depleted then
depleted[#depleted + 1] = node
end
end
return depleted
end
local function calcNumHarvests(damage, node, nodeData)
local nodeTypeMetadata = require(getNodeTypeMetadataFromNode(node))
local maxdurability = nodeTypeMetadata.Durability
local durability = nodeData.durability
local harvestsLeft = nodeData.harvestsLeft
local toHarvest = math.floor( (damage + durability) / maxdurability )
if damage < durability then
return 0, durability - damage
elseif damage == durability then
return 1, 0
else
return math.min(toHarvest, harvestsLeft), (damage + durability) % maxdurability
end
-- while damage >= maxdurability do
-- damage -= maxdurability
-- end
-- 8 dmg, 3 max durability, 1 durability
-- 5 dmg, 3 max dirability, 1 durability
-- 2 dmg, 3 max dirability, 1 durability
-- -7 durability
-- math.abs( math.floor( (durability - damage) / maxdurability ) )
-- i think that math is right???? maybe???
end
-- this should really be a function of item manager
local function applyVelocityToItem(item, velocity)
local attachmentTarget
if item:IsA("BasePart") then
attachmentTarget = item
elseif item:IsA("Model") and (item.PrimaryPart or item:FindFirstChild("HumanoidRootPart")) then
local primaryPart = item.PrimaryPart or item:FindFirstChild("HumanoidRootPart")
if primaryPart then
attachmentTarget = primaryPart
end
end
attachmentTarget.Velocity = velocity
end
local function resourceNodeReplenished(node, player)
local nodeTypeMetadata = require(getNodeTypeMetadataFromNode(node))
local isNodeGlobal = nodeTypeMetadata.IsGlobal
local nodeData = isNodeGlobal and getGlobalDataForNode(node) or getNodeDataForPlayer(node, player)
local dropPoints = node:FindFirstChild("DropPoints")
nodeData.harvestsLeft = nodeTypeMetadata.Harvests
-- nodeData.durability = 0
nodeData.durability = nodeTypeMetadata.Durability
nodeData.DropPoints = dropPoints and dropPoints:GetChildren() or {}
nodeData.Depleted = false
if isNodeGlobal then
if nodeTypeMetadata.DestroyOnDeplete then
for _, c in pairs (node:GetDescendants()) do
if c:IsA("BasePart") then
c.CanCollide = true
c.Transparency = 0
end
end
end
CollectionService:AddTag(node.PrimaryPart, "attackable")
network:fireAllClients("resourceReplenished", node)
else
network:fireClient("resourceReplenished", player, node)
end
end
local function resourceNodeDepleted(node, player)
local nodeTypeMetadata = require(getNodeTypeMetadataFromNode(node))
local isGlobal = nodeTypeMetadata.IsGlobal
local nodeData = isGlobal and getGlobalDataForNode(node) or getNodeDataForPlayer(node, player)
if nodeTypeMetadata.Replenish ~= 0 then
thread.Delay(nodeTypeMetadata.Replenish, resourceNodeReplenished, node, player)
end
if isGlobal then
-- Disable node collision on server BEFORE spawning items otherwise items
-- will get stuck and will not "fling" away from the node. Nodes that
-- have exterior drop points WILL NOT have this problem.
if nodeTypeMetadata.DestroyOnDeplete then
for _, c in pairs (node:GetDescendants()) do
if c:IsA("BasePart") then
c.CanCollide = false
c.Transparency = 1
end
end
end
CollectionService:RemoveTag(node.PrimaryPart, "attackable")
network:fireAllClients("resourceDepleted", node)
else
network:fireClient("resourceDepleted", player, node)
end
nodeData.Depleted = true
if nodeTypeMetadata.Animations.OnDeplete then
nodeTypeMetadata.Animations.OnDeplete()
end
end
local function harvestResource(player, node)
local playerManifest = player.Character and player.Character.PrimaryPart
assert(playerManifest, "No character")
if node:IsA("Model") and node:IsDescendantOf(nodesFolder) and node.Name ~= "Nodes" and node.Name ~= "Props" then
local nodePosition = node:GetBoundingBox().Position
local charPosition = playerManifest.Position
local distance = (nodePosition - charPosition).Magnitude
if distance < node:GetExtentsSize().Magnitude * 1.5 then
local nodeTypeMetadata = require(getNodeTypeMetadataFromNode(node))
local isNodeGlobal = nodeTypeMetadata.IsGlobal
local numDrops = nodeTypeMetadata.LootTable.Drops
local damage = calcDamageForNode(node, player)
local nodeData = isNodeGlobal and getGlobalDataForNode(node) or getNodeDataForPlayer(node, player)
local harvestsLeft = nodeData.harvestsLeft
local durability = nodeData.durability
durability = math.max(durability - damage, 0)
nodeData.durability = durability
if isNodeGlobal and not table.find(nodeData.owners, player) then
nodeData.owners[#nodeData.owners + 1] = player
end
if harvestsLeft > 0 then
if durability == 0 then
local rng = Random.new()
-- Contingency: If harvest > num drop points we will run out of drop points
-- If this happens, item will drop on node's primary part position
local dropPointNum = rng:NextInteger(1, #nodeData.DropPoints)
local dropPoint = nodeData.DropPoints[dropPointNum]
local dropPosition = dropPoint and dropPoint.Value.DropAttachment.WorldPosition or
node.PrimaryPart.Position + Vector3.new(0, node.PrimaryPart.Size.Y / 2 + 0.5, 0)
tableUtil.FastRemove(nodeData.DropPoints, dropPointNum)
harvestsLeft = harvestsLeft - 1
nodeData.harvestsLeft = harvestsLeft
nodeData.durability = nodeTypeMetadata.Durability
if isNodeGlobal then
if dropPoint then
dropPoint.Value.Transparency = 1
dropPoint.Value.CanCollide = false
end
network:fireAllClients("resourceHarvested", node, dropPoint and dropPoint.Value or nil)
else
network:fireClient("resourceHarvested", player, node, dropPoint and dropPoint.Value or nil)
end
if harvestsLeft == 0 then
resourceNodeDepleted(node, player)
end
for _ = 1, numDrops do
local itemDrop = rollLootTable(nodeTypeMetadata.LootTable.Items)
local numToDrop = itemDrop:Amount()
for _ = 1, numToDrop do
local itemModifiers = itemDrop:Modifiers()
local velocity = Vector3.new((rng:NextNumber() - 0.5) * 10, (2 + rng:NextNumber()) * 25, (rng:NextNumber() - 0.5) * 10)
itemModifiers.id = itemDrop.ID
local item = network:invoke("spawnItemOnGround",
itemModifiers,
dropPosition,
isNodeGlobal and nodeData.owners or {player}
)
applyVelocityToItem(item, velocity)
end
end
return dropPoint and dropPoint.Value or nil
else
if isNodeGlobal then
network:fireAllClients("resourceHarvested", node)
else
network:fireClient("resourceHarvested", player, node)
end
end
end
end
end
end
function module.init(Modules)
network = Modules.network
placeSetup = Modules.placeSetup
thread = Modules.thread
tableUtil = Modules.tableUtil
nodesFolder = placeSetup.getPlaceFolder("resourceNodes")
network:create("harvestResource", "RemoteFunction", "OnServerInvoke", harvestResource)
network:create("getDepletedResourceNodes", "RemoteFunction", "OnServerInvoke", getDepletedResourceNodes)
network:create("resourceHarvested", "RemoteEvent")
network:create("resourceDepleted", "RemoteEvent")
network:create("resourceReplenished", "RemoteEvent")
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_security.lua
================================================
local module = {}
module.priority = 3
local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")
local network
local utilities
local configuration
local abilityLookup
--[[
BANS & SUSPICION
--]]
-- unban
--[[
local banRecord = game:GetService("DataStoreService"):GetDataStore("banRecord")
banRecord:UpdateAsync(userId, function(history)
history.unbanTime = 0
return history
end)
]]
local banRecord = game:GetService("DataStoreService"):GetDataStore("banRecord")
local playerPositionDataContainer = {}
local function handleBanHistory(player, playerBanHistory)
if playerBanHistory and playerBanHistory.unbanTime > os.time() then
if player:FindFirstChild("DataLoaded") then
network:invoke("onPlayerRemoving", player)
end
local banDuration = playerBanHistory.unbanTime - os.time()
if playerBanHistory.reason then
player:Kick("You have been banned for: "..playerBanHistory.reason.." (unbanned in "..utilities.timeToString(banDuration)..")")
else
player:Kick("You have been banned (unbanned in "..utilities.timeToString(banDuration)..")")
end
end
end
local function onPlayerAdded(player)
if not RunService:IsStudio() then
local playerBanHistory = banRecord:GetAsync(player.userId)
if playerBanHistory then
handleBanHistory(player, playerBanHistory)
end
end
end
local function banPlayer(player, duration, reason, source)
source = source or "system"
local playerData = network:invoke("getPlayerData", player)
local playerBanHistory
for _=1, 3 do
local success = pcall(function()
banRecord:UpdateAsync(player.userId, function(banHistory)
banHistory = banHistory or {}
playerBanHistory = banHistory
playerBanHistory.reason = reason
playerBanHistory.source = source
playerBanHistory.previousRecords = playerBanHistory.previousRecords or {}
table.insert(playerBanHistory.previousRecords, {
reason = reason,
source = source,
duration = duration,
timestamp = os.time()
})
if playerData then
playerBanHistory.offendingData = playerData
end
playerBanHistory.unbanTime = playerBanHistory.unbanTime or 0
if playerBanHistory.unbanTime >= os.time() then
playerBanHistory.unbanTime = playerBanHistory.unbanTime + duration
else
playerBanHistory.unbanTime = os.time() + duration
end
return playerBanHistory
end)
end)
if success then
break
end
end
handleBanHistory(player, playerBanHistory)
end
local function addSuspicion(player, amount)
local playerData = network:invoke("getPlayerData", player)
playerData.internalData.suspicion = playerData.internalData.suspicion + amount
if playerData.internalData.suspicion > 100 then
local playerBanHistory
for _ = 1, 3 do
local success = pcall(function()
banRecord:UpdateAsync(player.userId, function(banHistory)
banHistory = banHistory or {}
playerBanHistory = banHistory
local reason = "cheating suspicion"
local source = "system"
local duration = 36000
playerBanHistory.cheatingBans = (playerBanHistory.cheatingBans or 0) + 1
local banCount = playerBanHistory.cheatingBans
if banCount >= 5 then
duration = 14 * 86400
elseif banCount == 4 then
duration = 7 * 86400
elseif banCount == 3 then
duration = 3 * 86400
elseif banCount == 2 then
duration = 86400
end
playerBanHistory.previousRecords = playerBanHistory.previousRecords or {}
table.insert(playerBanHistory.previousRecords, {
reason = reason,
source = source,
duration = duration,
timestamp = os.time()
})
playerBanHistory.reason = reason
playerBanHistory.source = source
if playerData then
playerBanHistory.offendingData = playerData
end
playerBanHistory.unbanTime = playerBanHistory.unbanTime or 0
if playerBanHistory.unbanTime >= os.time() then
playerBanHistory.unbanTime = playerBanHistory.unbanTime + duration
else
playerBanHistory.unbanTime = os.time() + duration
end
return playerBanHistory
end)
end)
if success then
playerData.internalData.suspicion = 0
break
end
end
end
end
-- tp exploit stuff
local function init__exploitBlock()
local positionCheckHeartbeatTick = 1 / 3
local doors = CollectionService:GetTagged("door")
local cannons = CollectionService:GetTagged("cannon")
local escapeRopes = CollectionService:GetTagged("escapeRope")
local ACCEPTABLE_THRESHOLD_FOR_NEARBY = 50
local function isNearbyAcceptableObject(pos, objTable, label)
for _, obj in pairs(objTable) do
local objPos = obj:IsA("Model") and (obj.PrimaryPart and obj.PrimaryPart.Position) or obj.Position
if objPos and (pos - objPos).magnitude <= ACCEPTABLE_THRESHOLD_FOR_NEARBY then
return true, obj
end
end
return false, nil
end
local function getOtherDoorFromDoor(door)
for _, v in pairs(doors) do
if v.Parent.Name == door.Parent.Name and v.Parent ~= door.Parent then
return v
end
end
end
local function isPlayerPerformingUnacceptableSketchyMovements(player)
local timeWindow = configuration.getConfigurationValue("server_TPExploitTimeWindow")
local scoreToFail = configuration.getConfigurationValue("server_TPExploitScoreToFail")
local score = 0
if playerPositionDataContainer[player] then
for _, scoreData in pairs(playerPositionDataContainer[player].sketchyMovements) do
if tick() - scoreData.timestamp <= timeWindow then
score = score + scoreData.movementRatio
end
end
end
return score >= scoreToFail
end
network:create("reportPlayerAttemptDamageEntity", "BindableEvent", "Event", function(player, weaponType, serverHitbox)
if playerPositionDataContainer[player] then
if playerPositionDataContainer[player].positions and #playerPositionDataContainer[player].positions > 0 then
local lastPosition = playerPositionDataContainer[player].positions[#playerPositionDataContainer[player].positions].position
local playerData = network:invoke("getPlayerData", player)
local distanceToTravel = (serverHitbox.Position - lastPosition).magnitude
local distanceMax = playerData.nonSerializeData.statistics_final.walkspeed * 4 * positionCheckHeartbeatTick + 5
if distanceToTravel > distanceMax then
warn("earlyTrigger exploiter!", player)
local response = configuration.getConfigurationValue("tpExploitPunishment")
if response == "suspicion" then
local amount = configuration.getConfigurationValue("tpExploitPunishmentSuspicionAddAmount")
addSuspicion(player, amount or 25)
elseif response == "kick" then
player:Kick("TP Exploiting")
elseif response == "redirect" then
player:Kick("Anti-exploit")
end
end
end
end
end)
local function getRayClosestPoint(ray, point)
-- shift point to be relative to the origin of the ray
local a, b = ray.Origin - point, ray.Direction
-- calculate rejection of a from b
local v = a - ((a:Dot(b)) / (b:Dot(b))) * b
-- add rejection to point to get the closest point on the ray
return point + v
end
network:create("playerRequest_activateEscapeRope", "RemoteEvent", "OnServerEvent", function(player, cEscapeRope)
if cEscapeRope and CollectionService:HasTag(cEscapeRope, "escapeRope") then
local isNearbyEscapeRope, nearestEscapeRope = isNearbyAcceptableObject(player.Character.PrimaryPart.Position, escapeRopes)
if isNearbyEscapeRope and nearestEscapeRope == cEscapeRope then
if nearestEscapeRope.Parent and nearestEscapeRope.Parent:FindFirstChild("Target") then
network:invoke("teleportPlayerCFrame_server", player, nearestEscapeRope.Parent.Target.CFrame)
end
end
end
end)
local function checkIfPlayerIsBad(player)
local playerData = network:invoke("getPlayerData", player)
if playerData then
if isPlayerPerformingUnacceptableSketchyMovements(player) then
if configuration.getConfigurationValue("doLogTPExploitersInPlayerDataFlags") then
playerData.flags.isPlayerTPExploiter = true
end
warn("player exploiting!", player)
local response = configuration.getConfigurationValue("tpExploitPunishment")
if response == "suspicion" then
local amount = configuration.getConfigurationValue("tpExploitPunishmentSuspicionAddAmount")
addSuspicion(player, amount or 25)
elseif response == "kick" then
player:Kick("TP Exploiting")
elseif response == "redirect" then
player:Kick("Anti-exploit")
end
end
end
end
network:create("incrementPlayerArcadeScore", "BindableFunction", "OnInvoke", function(player, scoreToAdd)
local playerPositionData = playerPositionDataContainer[player]
if playerPositionData then
table.insert(playerPositionData.sketchyMovements, {movementRatio = scoreToAdd; timestamp = tick()})
checkIfPlayerIsBad(player)
end
end)
while true do
local step = wait(positionCheckHeartbeatTick)
for player, playerPositionData in pairs(playerPositionDataContainer) do
if player:FindFirstChild("isPlayerSpawning") and not player.isPlayerSpawning.Value and player:FindFirstChild("playerSpawnTime") and (os.time() - player.playerSpawnTime.Value >= 5) then
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart:FindFirstChild("state") and player.Character.PrimaryPart.state.Value ~= "dead" and configuration.getConfigurationValue("isTeleportingExploitFixEnabled", player) then
local playerData = network:invoke("getPlayerData", player)
if #playerPositionData.positions > 0 then
local currPosition = player.Character.PrimaryPart.Position
local currVelocity = player.Character.PrimaryPart.Velocity
local prevPosition = playerPositionData.positions[#playerPositionData.positions].position
local prevVelocity = playerPositionData.positions[#playerPositionData.positions].velocity
local playerVelocityForCalculations = currVelocity.magnitude > prevVelocity.magnitude and currVelocity or prevVelocity
if playerVelocityForCalculations.magnitude < 0.1 then
if (currPosition - prevPosition).magnitude >= 0.1 then
if (currPosition - prevPosition).magnitude < playerData.nonSerializeData.statistics_final.walkspeed * step then
playerVelocityForCalculations = (currPosition - prevPosition).unit * playerData.nonSerializeData.statistics_final.walkspeed * step
end
else
-- actually nothing is going on
playerVelocityForCalculations = Vector3.new()
end
elseif playerVelocityForCalculations.magnitude < playerData.nonSerializeData.statistics_final.walkspeed * step then
playerVelocityForCalculations = playerVelocityForCalculations.unit * playerData.nonSerializeData.statistics_final.walkspeed * step
end
-- pretend double?
playerVelocityForCalculations = playerVelocityForCalculations * configuration.getConfigurationValue("server_TPExploitVelocityMultiplier")
local delta = ((currPosition - prevPosition) * Vector3.new(1, 0, 1)).magnitude
-- stuns set your walkspeed to zero so let's not ban people just for getting stunned yeah?
local walkspeed = math.max(playerData.nonSerializeData.statistics_final.walkspeed, 0.1)
local movementRatio = delta / (math.max((playerVelocityForCalculations * Vector3.new(1, 0, 1)).magnitude, walkspeed) * step)
-- we're not kicking anyone for going too slowly, just chill out
if playerVelocityForCalculations.Magnitude < 16 then
movementRatio = 1
end
table.insert(playerPositionData.positions, {position = currPosition; velocity = player.Character.PrimaryPart.Velocity})
if #playerPositionData.positions > 10 then
table.remove(playerPositionData.positions, 1)
end
-- 2x is running
if delta > (playerData.nonSerializeData.statistics_final.walkspeed * 3) * step then
-- flag!
local isPlayerGood = false
if not isPlayerGood then
local isCurrNearDoor, currDoor = isNearbyAcceptableObject(currPosition, doors, "curr")
local isPrevNearDoor, prevDoor = isNearbyAcceptableObject(prevPosition, doors, "prev")
if isCurrNearDoor and isPrevNearDoor then
isPlayerGood = true
elseif isCurrNearDoor or isPrevNearDoor then
local door1 = currDoor or prevDoor
local otherDoor = getOtherDoorFromDoor(door1)
if otherDoor then
local ray = Ray.new(door1.Position, otherDoor.Position - door1.Position)
if (currPosition - getRayClosestPoint(ray, currPosition)).magnitude <= ACCEPTABLE_THRESHOLD_FOR_NEARBY and (prevPosition - getRayClosestPoint(ray, prevPosition)).magnitude <= ACCEPTABLE_THRESHOLD_FOR_NEARBY then
isPlayerGood = true
warn("is good at intersection!")
end
end
end
end
if not isPlayerGood then
local isNearbyEscapeRope, nearestEscapeRope = isNearbyAcceptableObject(prevPosition, escapeRopes)
if isNearbyEscapeRope and (nearestEscapeRope.Parent.Target.Position - currPosition).magnitude <= ACCEPTABLE_THRESHOLD_FOR_NEARBY then
isPlayerGood = true
elseif nearestEscapeRope then
local ray = Ray.new(nearestEscapeRope.Position, nearestEscapeRope.Parent.Target.Position - nearestEscapeRope.Position)
if (currPosition - getRayClosestPoint(ray, currPosition)).magnitude <= ACCEPTABLE_THRESHOLD_FOR_NEARBY and (prevPosition - getRayClosestPoint(ray, prevPosition)).magnitude <= ACCEPTABLE_THRESHOLD_FOR_NEARBY then
isPlayerGood = true
warn("is good at intersection for rope!")
end
end
end
if not isPlayerGood then
local isNearbyCannon, cannon = isNearbyAcceptableObject(prevPosition, cannons)
if isNearbyCannon then
if math.acos((cannon.Parent.target.CFrame.lookVector * Vector3.new(1, 0, 1)).unit:Dot(((currPosition - prevPosition).unit))) <= math.pi / 2 then
playerPositionData.lastCannon = cannon
playerPositionData.lastTimeNearCannon = tick()
end
end
if playerPositionData.lastTimeNearCannon and tick() - playerPositionData.lastTimeNearCannon <= 7 then
if math.acos((playerPositionData.lastCannon.Parent.target.CFrame.lookVector * Vector3.new(1, 0, 1)).unit:Dot(((currPosition - prevPosition).unit))) <= math.pi / 2 then
isPlayerGood = true
end
else
playerPositionData.lastCannon = nil
playerPositionData.lastTimeNearCannon = nil
end
end
if not isPlayerGood then
local playerUnitVelocity = playerVelocityForCalculations.magnitude > 0.01 and playerVelocityForCalculations.unit or Vector3.new()
-- if the directions at least 72 degrees align..
if math.acos((playerUnitVelocity * Vector3.new(1, 0, 1)):Dot(((currPosition - prevPosition).unit * Vector3.new(1, 0, 1)))) <= math.pi / 2 then
-- ok... figure out now what couldve caused this massive velocity..
if movementRatio <= 1 then
isPlayerGood = true
end
end
end
-- check abilities
if not isPlayerGood then
local activeAbilityIds = network:invoke("getCurrentlyActiveAbilityGUIDsForPlayer", player)
for _, abilityId in pairs(activeAbilityIds) do
local ability = abilityLookup[abilityId](playerData)
if ability and ability.__serverValidateMovement then
if ability.__serverValidateMovement(player, prevPosition, currPosition) then
isPlayerGood = true
break
end
end
end
end
if not isPlayerGood then
table.insert(playerPositionData.sketchyMovements, {movementRatio = movementRatio; timestamp = tick()})
checkIfPlayerIsBad(player)
end
end
else
table.insert(playerPositionData.positions, {position = player.Character.PrimaryPart.Position; velocity = player.Character.PrimaryPart.Velocity})
end
else
playerPositionData.positions = {}
end
else
-- wipe if respawning, just incase
if playerPositionData.positions and #playerPositionData.positions > 0 then
playerPositionData.positions = {}
end
end
end
end
end
function module.init(Modules)
network = Modules.network
utilities = Modules.utilities
configuration = Modules.configuration
abilityLookup = Modules.abilityLookup
network:create("banPlayer", "BindableFunction", "OnInvoke", banPlayer)
network:create("addSuspicion", "BindableFunction", "OnInvoke", addSuspicion)
game.Players.PlayerAdded:connect(onPlayerAdded)
for _, player in pairs(game.Players:GetPlayers()) do
onPlayerAdded(player)
end
spawn(init__exploitBlock)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_servers.lua
================================================
-- Communicates to other servers of the same place and allows players to teleport between them
local module = {}
local placeKey = "pl-"..tostring(game.PlaceId)
local httpService = game:GetService("HttpService")
local messaging = game:GetService("MessagingService")
local network
local success, err
local messagingConnection
local servers = {}
local serversDataValue = Instance.new("StringValue")
serversDataValue.Name = "serversData"
serversDataValue.Parent = game.ReplicatedStorage
local function serversDataUpdated()
serversDataValue.Value = httpService:JSONEncode(servers)
end
local function registerMessage(message)
local data = message.Data
local timestamp = message.Sent
local jobId = data.jobId
if jobId ~= game.JobId then
if data.status == "open" then
servers[tostring(jobId)] = {
players = data.players;
updated = timestamp;
}
elseif data.status == "close" then
servers[tostring(jobId)] = nil
end
serversDataUpdated()
end
end
local function playerRequest_teleportToJobId(player, jobId)
if servers[jobId] then
if player.Character and player.Character.PrimaryPart then
if player.Character.PrimaryPart.state.Value ~= "dead" and player.Character.PrimaryPart.health.Value > 0 then
network:invoke("teleportPlayerToJobId", player, game.PlaceId, jobId)
end
end
return true
end
return false
end
local function playerRequest_returnToMainMenu(player)
if player.Character and player.Character.PrimaryPart then
if player.Character.PrimaryPart.state.Value ~= "dead" and player.Character.PrimaryPart.health.Value > 0 then
network:invoke("teleportPlayer", player, 2376885433)
end
end
end
local serverClosing
local function connect()
if game.PrivateServerId == "" and game.PlaceId ~= 4561988219 and game.PlaceId ~= 4041427413 then
local success, err
repeat
wait(1)
success, err = pcall(function()
messagingConnection = messaging:SubscribeAsync(
placeKey,
registerMessage
)
end)
until success
game:BindToClose(function()
serverClosing = true
if game:GetService("RunService"):IsStudio() then return end
local message = {
jobId = game.JobId;
status = "close";
}
local success, err
repeat
success, err = pcall(function()
messaging:PublishAsync(placeKey, message)
end)
wait(1)
until success
end)
while not serverClosing do
local message = {
jobId = game.JobId;
status = "open";
players = #game.Players:GetPlayers();
}
local messageSent, err = pcall(function()
messaging:PublishAsync(placeKey, message)
end)
wait(messageSent and 60 or 20)
end
end
end
function module.init(Modules)
network = Modules.network
network:create("playerRequest_teleportToJobId", "RemoteFunction", "OnServerInvoke", playerRequest_teleportToJobId)
network:create("playerRequest_returnToMainMenu", "RemoteFunction", "OnServerInvoke", playerRequest_returnToMainMenu)
spawn(connect)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_statusEffect.lua
================================================
-- author: Polymorphic
local module = {}
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local network
local utilities
local configuration
local terrainUtil
-- >>> BIGGGG REALIZATION!!! Do not let the client tell you what status effect
-- to apply. The server will decide to apply a status effect as it receives
-- requests from the client to use an item or use an ability!
-- ie, in manager_ability when activeAbilityId is set, determine if there
-- is an associated status effect with the activation of the ability and
-- if so, channel it through manager_statusEffect
local statusEffectLookup = require(replicatedStorage.statusEffectLookup)
local activeStatusEffectsCollectionContainer = {}
--[[
activeStatusEffect {}
int sourceType["ability"; "entity"; "consumable"]
int sourceId
string statusEffectType
statusEffectData_intermediate statusEffectData
int
string sourceEntityGUID
--]]
local function updatePlayerStatusEffects(player)
end
local function onGetStatusEffectsOnEntityManifestByEntityGUID(entityGUID)
local statusEffects = {}
for i, activeStatusEffectData in pairs(activeStatusEffectsCollectionContainer) do
if activeStatusEffectData.affecteeEntityGUID == entityGUID then
table.insert(statusEffects, activeStatusEffectData)
end
end
return statusEffects
end
local function doesEntityManifestHaveStatusEffectBySourceType(entityManifest, sourceType)
local entityGUID = utilities.getEntityGUIDByEntityManifest(entityManifest)
if entityGUID then
for i, activeStatusEffectData in pairs(activeStatusEffectsCollectionContainer) do
if activeStatusEffectData.affecteeEntityGUID == entityGUID and activeStatusEffectData.sourceType == sourceType then
return true
end
end
end
return false
end
local function removeStatusEffectByIndex(index)
local activeStatusEffectData = activeStatusEffectsCollectionContainer[index]
table.remove(activeStatusEffectsCollectionContainer, index)
-- potentially call an on-ended status effect thing
local statusEffectBaseData = statusEffectLookup[activeStatusEffectData.statusEffectType]
if statusEffectBaseData and statusEffectBaseData.onEnded_server then
local entityManifest = utilities.getEntityManifestByEntityGUID(activeStatusEffectData.affecteeEntityGUID)
if entityManifest then
statusEffectBaseData.onEnded_server(activeStatusEffectData, entityManifest)
end
end
end
local function updateStatusEffectsForEntityManifestByEntityGUID(entityGUID)
local statusEffects = onGetStatusEffectsOnEntityManifestByEntityGUID(entityGUID)
local entityManifest = utilities.getEntityManifestByEntityGUID(entityGUID)
if entityManifest and entityManifest:FindFirstChild("statusEffectsV2") then
local success, returnValue = utilities.safeJSONEncode(statusEffects)
if success then
entityManifest.statusEffectsV2.Value = returnValue
if entityManifest.Parent then
local player = game.Players:GetPlayerFromCharacter(entityManifest.Parent)
if player then
local playerData = network:invoke("getPlayerData", player)
if playerData then
playerData.nonSerializeData.playerDataChanged:Fire("statusEffects")
end
end
end
end
end
end
local function removeStatusEffectFromEntityManifestBySourceType(entityManifest, sourceType)
local entityGUID = utilities.getEntityGUIDByEntityManifest(entityManifest)
if entityGUID then
for i = #activeStatusEffectsCollectionContainer, 1, -1 do
local activeStatusEffectData = activeStatusEffectsCollectionContainer[i]
if activeStatusEffectData.affecteeEntityGUID == entityGUID and activeStatusEffectData.sourceType == sourceType then
local statusEffectBaseData = statusEffectLookup[activeStatusEffectData.statusEffectType]
if statusEffectBaseData._serverCleanupFunction then
statusEffectBaseData._serverCleanupFunction(activeStatusEffectData, entityManifest)
end
removeStatusEffectByIndex(i)
updateStatusEffectsForEntityManifestByEntityGUID(entityGUID)
end
end
end
return false
end
local function onPlayerRemovingPackageStatusEffects(player)
if not player:FindFirstChild("entityGUID") then warn("FAILED TO FIND PLAYER ENTITYGUID FOR STATUS EFFECTS PACKAGING") return false end
local playerEntityGUID = player.entityGUID.Value
local statusEffects = {}
for i = #activeStatusEffectsCollectionContainer, 1, -1 do
local activeStatusEffectData = activeStatusEffectsCollectionContainer[i]
local statusBaseData = statusEffectLookup[activeStatusEffectData.statusEffectType]
local doNotSave =
(activeStatusEffectData.DO_NOT_SAVE) or
(statusBaseData and statusBaseData.notSavedToPlayerData)
if activeStatusEffectData.affecteeEntityGUID == playerEntityGUID and not doNotSave then
removeStatusEffectByIndex(i)
table.insert(statusEffects, activeStatusEffectData)
end
end
return statusEffects
end
local function onPlayerAddedContinuePackageStatusEffects(player, packageStatusEffects)
if not player:FindFirstChild("entityGUID") then warn("FAILED TO FIND PLAYER ENTITYGUID") return false end
for i, activeStatusEffectData in pairs(packageStatusEffects) do
-- update to current player
activeStatusEffectData.affecteeEntityGUID = player.entityGUID.Value
-- pop to be updated
table.insert(activeStatusEffectsCollectionContainer, activeStatusEffectData)
end
end
local function int__startTickingAbilities()
local activeStatusEffectTickTimePerSecond = configuration.getConfigurationValue("activeStatusEffectTickTimePerSecond")
local function tickStatusEffects()
local requiresUpdate = {}
local indicesToRemove = {}
-- index in reverse so we can table.remove just fine, remove finished statusEffects
for i = #activeStatusEffectsCollectionContainer, 1, -1 do
local activeStatusEffectData = activeStatusEffectsCollectionContainer[i]
local entityManifest, isInWorld = utilities.getEntityManifestByEntityGUID(activeStatusEffectData.affecteeEntityGUID)
local shouldUpdateEntity = false
if entityManifest then
local statusEffectBaseData = statusEffectLookup[activeStatusEffectData.statusEffectType]
if statusEffectBaseData and statusEffectBaseData.execute then
statusEffectBaseData.execute(activeStatusEffectData, entityManifest, activeStatusEffectTickTimePerSecond)
end
if activeStatusEffectData.ticksMade then
activeStatusEffectData.ticksMade = activeStatusEffectData.ticksMade + 1
if activeStatusEffectData.ticksMade >= activeStatusEffectData.ticksNeeded then
removeStatusEffectByIndex(i)
end
shouldUpdateEntity = true
end
elseif not isInWorld then
-- pop it
shouldUpdateEntity = true
removeStatusEffectByIndex(i)
end
if shouldUpdateEntity then
local guid = activeStatusEffectData.affecteeEntityGUID
if guid and not requiresUpdate[guid] then
requiresUpdate[guid] = true
end
end
end
-- update everything
for guid, _ in pairs(requiresUpdate) do
updateStatusEffectsForEntityManifestByEntityGUID(guid)
end
end
local savedTime = 0
local tickDuration = 1 / activeStatusEffectTickTimePerSecond
local function onHeartbeat(dt)
savedTime = savedTime + dt
while savedTime > tickDuration do
savedTime = savedTime - tickDuration
tickStatusEffects()
end
end
game:GetService("RunService").Heartbeat:Connect(onHeartbeat)
end
-- "applyStatusEffectToEntityManifest", player.Character.PrimaryPart, "regenerate", {health = 25; duration = 5}, {sourceId = item}
local function onApplyStatusEffectToEntityManifest(entityManifest, statusEffectType, statusEffectModifierData, sourceEntityManifest, sourceType, sourceId, variant)
print("APPLYING AN EFFECT")
if not statusEffectLookup[statusEffectType] then
return false, "invalid status effect"
end
local activeStatusEffectTickTimePerSecond = configuration.getConfigurationValue("activeStatusEffectTickTimePerSecond")
local sourceEntityGUID = utilities.getEntityGUIDByEntityManifest(sourceEntityManifest)
local affecteeEntityGUID = utilities.getEntityGUIDByEntityManifest(entityManifest)
local statusEffectBaseData = statusEffectLookup[statusEffectType]
local activeStatusEffectData = {}
-- help us decipher where this statusEffect came from
activeStatusEffectData.sourceType = sourceType
activeStatusEffectData.sourceId = sourceId
if variant then
activeStatusEffectData.variant = variant
end
activeStatusEffectData.sourceEntityGUID = sourceEntityGUID
-- what kind of status effect is this?
activeStatusEffectData.statusEffectType = statusEffectType
activeStatusEffectData.statusEffectModifier = statusEffectModifierData
activeStatusEffectData.statusEffectGUID = httpService:GenerateGUID(false)
-- who does this affect?
activeStatusEffectData.affecteeEntityGUID = affecteeEntityGUID
activeStatusEffectData.timestamp = tick()
-- handle internal stuff here --
if activeStatusEffectData.statusEffectModifier.duration then
activeStatusEffectData.ticksMade = 0
activeStatusEffectData.ticksNeeded = activeStatusEffectData.statusEffectModifier.duration * activeStatusEffectTickTimePerSecond
else
activeStatusEffectData.isPermanent = true
end
if statusEffectBaseData.hideInStatusBar then
activeStatusEffectData.hideInStatusBar = true
end
if activeStatusEffectData.statusEffectModifier.DO_NOT_SAVE then
activeStatusEffectData.DO_NOT_SAVE = true
end
if activeStatusEffectData.statusEffectModifier.icon then
activeStatusEffectData.icon = activeStatusEffectData.statusEffectModifier.icon
else
if (sourceType ~= "item") and (sourceType ~= "ability") then
activeStatusEffectData.icon = statusEffectBaseData.image
end
end
-- pop duplicate statusEffects out
for i = #activeStatusEffectsCollectionContainer, 1, -1 do
local _statusEffectData = activeStatusEffectsCollectionContainer[i]
local sameType = _statusEffectData.sourceType == sourceType
local sameSource = _statusEffectData.sourceId == sourceId
local sameAffectee = _statusEffectData.affecteeEntityGUID == affecteeEntityGUID
if sameType and sameSource and sameAffectee then
removeStatusEffectByIndex(i)
end
end
if statusEffectBaseData._serverExecutionFunction then
statusEffectBaseData._serverExecutionFunction(activeStatusEffectData, entityManifest)
end
if statusEffectBaseData.onStarted_server then
statusEffectBaseData.onStarted_server(activeStatusEffectData, entityManifest)
end
table.insert(activeStatusEffectsCollectionContainer, activeStatusEffectData)
-- update status effects
updateStatusEffectsForEntityManifestByEntityGUID(affecteeEntityGUID)
if sourceId == "item" then
utilities.playSound("item_buff", entityManifest)
end
return true, activeStatusEffectData.statusEffectGUID
end
local function revokeStatusEffectByStatusEffectGUID(statusEffectGUID)
for i = #activeStatusEffectsCollectionContainer, 1, -1 do
local status = activeStatusEffectsCollectionContainer[i]
if status.statusEffectGUID == statusEffectGUID then
removeStatusEffectByIndex(i)
updateStatusEffectsForEntityManifestByEntityGUID(status.affecteeEntityGUID)
return true
end
end
return false
end
-- when a player enters water, certain status effects should be wiped
local function onPlayerEnteredWater(player, clientPosition)
local char = player.Character
if not char then return end
local root = char.PrimaryPart
if not root then return end
local guid = utilities.getEntityGUIDByEntityManifest(root)
if not guid then return end
if not terrainUtil.isPointUnderwater(clientPosition) then return end
local sanityRangeSq = 6 ^ 2
local delta = clientPosition - root.Position
local distanceSq = delta.X ^ 2 + delta.Y ^ 2 + delta.Z ^ 2
if distanceSq > sanityRangeSq then return end
local updateRequired = false
for index = #activeStatusEffectsCollectionContainer, 1, -1 do
local activeStatusEffectData = activeStatusEffectsCollectionContainer[index]
if activeStatusEffectData.affecteeEntityGUID == guid then
-- here is where we determine if a status must be removed
if activeStatusEffectData.statusEffectType == "ablaze" then
removeStatusEffectByIndex(index)
updateRequired = true
end
end
end
if updateRequired then
updateStatusEffectsForEntityManifestByEntityGUID(guid)
end
end
function module.init(Modules)
network = Modules.network
utilities = Modules.utilities
configuration = Modules.configuration
terrainUtil = Modules.terrainUtil
network:create("applyStatusEffectToEntityManifest", "BindableFunction", "OnInvoke", onApplyStatusEffectToEntityManifest)
network:create("revokeStatusEffectByStatusEffectGUID", "BindableFunction", "OnInvoke", revokeStatusEffectByStatusEffectGUID)
network:create("onPlayerEnteredWater", "RemoteEvent", "OnServerEvent", onPlayerEnteredWater)
network:create("removeStatusEffectFromEntityManifestBySourceType", "BindableFunction", "OnInvoke", removeStatusEffectFromEntityManifestBySourceType)
network:create("doesEntityManifestHaveStatusEffectBySourceType", "BindableFunction", "OnInvoke", doesEntityManifestHaveStatusEffectBySourceType)
network:create("doesEntityHaveStatusEffect", "BindableFunction", "OnInvoke", function(manifest, effectType)
if not manifest then return false end
local guid = utilities.getEntityGUIDByEntityManifest(manifest)
if not guid then return false end
local statuses = network:invoke("getStatusEffectsOnEntityManifestByEntityGUID", guid)
for _, status in pairs(statuses) do
if status.statusEffectType == effectType then
return true, status
end
end
return false
end)
network:create("updateStatusEffectsForEntityManifestByEntityGUID", "BindableFunction", "OnInvoke", updateStatusEffectsForEntityManifestByEntityGUID)
network:create("playerRemovingPackageStatusEffects", "BindableFunction", "OnInvoke", onPlayerRemovingPackageStatusEffects)
network:create("playerAddedContinuePackageStatusEffects", "BindableFunction", "OnInvoke", onPlayerAddedContinuePackageStatusEffects)
network:create("getStatusEffectsOnEntityManifestByEntityGUID", "BindableFunction", "OnInvoke", onGetStatusEffectsOnEntityManifestByEntityGUID)
network:create("getIsManifestStunned", "BindableFunction", "OnInvoke", function(manifest)
if not manifest then return false end
local guid = utilities.getEntityGUIDByEntityManifest(manifest)
if not guid then return false end
local statuses = network:invoke("getStatusEffectsOnEntityManifestByEntityGUID", guid)
for _, status in pairs(statuses) do
if status.statusEffectType == "stunned" then
return true
end
end
return false
end)
spawn(int__startTickingAbilities)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_taxi/dialogue.lua
================================================
local extraOptionsByPlaceId = {
-- mushtown
[2064647391] = {
{
response = "Where's your horse?",
dialogue = {{text = "Taximan Dave doesn't need a horse, silly!"}},
},
},
-- port fidelio
[2546689567] = {
{
response = "Where's your swimsuit?",
dialogue = {{text = "Taximan Dave doesn't get to have fun, silly!"}},
}
},
-- warrior stronghold
[2470481225] = {
{
response = "Where's your coat?",
dialogue = {{text = "Taximan Dave doesn't get cold, silly!"}},
}
},
-- guild hall
[4653017449] = {
{
response = "Where's your cart?",
dialogue = {{text = "Taximan Dave doesn't need a cart, silly!"}},
}
},
}
return {
sound = "npc_male_ahaa",
id = "startTalkingTo",
canExit = true,
dialogue = {{text = "Greetings Adventurer! You know me, the one and only Taximan Dave! I can take you to any location that you've visited before."}},
options = function(util)
local utilities = util.utilities
local network = util.network
local options = {
{
canExit = true,
id = "choose";
response = "Let's go somewhere.",
responseButtonColor = Color3.fromRGB(234, 174, 53),
dialogue = {{text = "Alright Adventurer! Where would you like me to take ya?"}},
taxiMenu = true;
options = {
{
id = "undiscovered";
dialogue = {{text = "There's a lot of places that I don't know how to get to yet. Maybe if you discover them you can tell me how to get there!"}};
moveToId = "choose";
};
{
id = "spawns";
dialogue = function(util, extraData)
return {{text = "Alright! Where in"},{text = extraData.taxiLocationName,font = Enum.Font.SourceSansBold},{text = "should I take ya?"}};
end;
canExit = true;
options = function(util, extraData)
local locations = network:invoke("getCacheValueByNameTag", "locations")
local placeData = locations[extraData.taxiLocation]
local checkpointOptions = {}
for spawnName,spawnData in pairs(placeData.spawns) do
if spawnData.text then
table.insert(checkpointOptions, {
response = spawnData.text;
dialogue = function(util)
local success = network:invokeServer("playerRequest_taximanDave", extraData.taxiLocation, spawnName)
if success then
return {{text = "Giddy up, lets go!"}}
else
return {{text = "I can't help you right now. Sorry!"}}
end
end;
})
end
end
return checkpointOptions
end
};
};
--[[
options = function(util)
local player = game:GetService("Players").LocalPlayer
local taxiInfo = util.network:invokeServer("getTaxiInfo")
local options = {}
for _, info in pairs(taxiInfo) do
local color = Color3.fromRGB(234, 174, 53)
if not info.enabled then
color = Color3.new(0.4, 0.4, 0.4)
end
local option = {
response = string.format("%s (%d Silver)", info.name, info.price),
responseButtonColor = color,
dialogue = function(util)
local success, reason, message = util.network:invokeServer("takeTaxiToDestination", info.id)
if success then
return {{text = "All right, then! Giddy up, let's go!"}}
else
if reason == "notEnoughMoney" then
return {{text = "Come back when you can afford the trip."}}
elseif reason == "notValidDestination" then
return {{text = "Sorry, uh, I've never heard of that place."}}
elseif reason == "conditionUnfulfilled" then
return {{text = message}}
else
return {{text = reason}}
end
end
end,
}
table.insert(options, option)
end
return options
end
]]
}
}
local extraOptions = extraOptionsByPlaceId[utilities.originPlaceId(game.PlaceId)]
if extraOptions then
for _, option in pairs(extraOptions) do
table.insert(options, option)
end
end
return options
end
}
================================================
FILE: src/ServerScriptService/contents/manager_taxi/init.lua
================================================
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local utilities = modules.load("utilities")
local taximan
local function setUpTaximan()
taximan = workspace:FindFirstChild("Taximan Dave", true)
if not taximan then return end
local dialogue = script.dialogue:Clone()
dialogue.Parent = taximan.UpperTorso
end
network:create("setUpTaximan", "BindableFunction", "OnInvoke", setUpTaximan)
setUpTaximan()
local function playerRequest_taximanDave(player, destination, spawnLocation)
if not taximan then return end
if (not player.Character) or (not player.Character.PrimaryPart) then return end
if (player.Character.PrimaryPart.Position - taximan.PrimaryPart.Position).magnitude >= 100 then return end
local playerData = network:invoke("getPlayerData", player)
if not playerData then return false end
local placeData = playerData.locations[destination]
if placeData and placeData.spawns and placeData.spawns[spawnLocation] then
network:invoke("teleportPlayer", player, tonumber(destination), spawnLocation, nil, "taxi")
end
end
network:create("playerRequest_taximanDave", "RemoteFunction", "OnServerInvoke", playerRequest_taximanDave)
-- old taximan dave code by davidii
-- some cool stuff in there like determining distance between locations
-- maybe we'll use in the future
-- constants that help do math
local silverPerDistance = 5
-- destinations are formatted like so
-- string name = the name of the place as displayed in the dialogue
-- int id = the place id of the destination
-- bool, string condition(player) = a function that returns whether or not a player can travel to that location and a message as to why not
local destinations = {
portFidelio = {
name = "Port Fidelio",
id = 2546689567,
},
warriorStronghold = {
name = "Warrior Stronghold",
id = 2470481225,
},
treeOfLife = {
name = "Tree of Life",
id = 3112029149,
},
nilgarf = {
name = "Nilgarf",
id = 2119298605,
},
mushtown = {
name = "Mushtown",
id = 2064647391,
},
hog = {
name = "The Gauntlet",
id = 3360349837,
condition = function(player)
local data = network:invoke("getPlayerData", player)
if data.flags.completedGauntlet then
return true
else
return false, "Sorry, haven't been able to get up that way since the bandits moved in."
end
end,
},
guildHall = {
priceOverride = 10,
name = "Guild Hall",
id = 4653017449,
condition = function(player)
local guildId = player:FindFirstChild("guildId")
if (not guildId) or (guildId.Value == "") then
return false, "You're not in a guild, silly!"
end
guildId = guildId.Value
local guildData = network:invoke("getGuildData", player, guildId)
if not guildData then
return false, "Couldn't find your guild data, silly!"
end
if guildData.level < 3 then
return false, "Sorry, I don't do work for small-time guilds. Get a level 3 guild, silly!"
end
if not guildData.hallLocation then
return false, "Uh, you might want to have a guild hall first, silly!"
end
return true
end,
travelFunction = function(player)
local guildId = player:FindFirstChild("guildId")
if not guildId then return end
guildId = guildId.Value
if guildId == "" then return end
network:invoke("teleportPlayersToGuildHall", {player}, guildId)
end,
},
}
-- connections
-- the system thinks of the game like a network of roads
-- it uses dijkstra's algorithm to find the shortest path
-- and creates the price of the route automatically using
-- the connections created here
local function addConnection(placeA, placeB, distance)
if not destinations[placeA] then
error("Invalid connection: "..placeA.." is not a valid destination.")
end
if not destinations[placeB] then
error("Invalid connection: "..placeB.." is not a valid destination.")
end
local infoA = destinations[placeA]
if not infoA.connections then
infoA.connections = {}
end
infoA.connections[placeB] = distance
local infoB = destinations[placeB]
if not infoB.connections then
infoB.connections = {}
end
infoB.connections[placeA] = distance
end
-- here is where the connections get made, two names
-- and a distance between them for price generation
-- no need to do reversals, the system automatically links
-- in both directions
addConnection("nilgarf", "mushtown", 2)
addConnection("nilgarf", "portFidelio", 3)
addConnection("nilgarf", "warriorStronghold", 4)
addConnection("nilgarf", "treeOfLife", 3)
addConnection("nilgarf", "guildHall", 1)
addConnection("portFidelio", "hog", 2)
local function getDestinationById(placeId)
for id, info in pairs(destinations) do
if info.id == placeId then
return id, info
end
end
return nil
end
local function getTaxiInfoForPlayer(player)
local hereId, hereInfo = getDestinationById(utilities.originPlaceId(game.PlaceId))
if not (hereId and hereInfo) then
error("Attempted to get taxi data in a place without taxi data.")
end
local visited = {}
local taxiInfo = {}
local function addDestination(id, info, distance)
local enabled = true
local reason = nil
if info.condition then
enabled, reason = info.condition(player)
end
table.insert(taxiInfo, {
id = info.id,
name = info.name,
price = (hereInfo.priceOverride) or (info.priceOverride) or (distance * silverPerDistance),
enabled = enabled,
reason = reason,
travelFunction = info.travelFunction,
})
end
local queue = {
{id = hereId, distance = 0},
}
while #queue > 0 do
local id = queue[1].id
local info = destinations[id]
local distance = queue[1].distance
table.remove(queue, 1)
if not visited[id] then
visited[id] = true
if id ~= hereId then
addDestination(id, info, distance)
end
for nextId, nextDistance in pairs(info.connections or {}) do
table.insert(queue, {id = nextId, distance = distance + nextDistance})
end
end
end
return taxiInfo
end
network:create("getTaxiInfo", "RemoteFunction", "OnServerInvoke", getTaxiInfoForPlayer)
--[[
local function takeTaxiToDestination(player, placeId)
local taxiInfo = getTaxiInfoForPlayer(player)
local destinationInfo
for _, info in pairs(taxiInfo) do
if info.id == placeId then
destinationInfo = info
break
end
end
if not destinationInfo.enabled then
return false, "conditionUnfulfilled", destinationInfo.reason
end
local success = network:invoke("tradeItemsBetweenPlayerAndNPC", player, {}, destinationInfo.price * 1000, {}, 0, "etc:taxi")
if success then
delay(0.2, function()
if destinationInfo.travelFunction then
destinationInfo.travelFunction(player)
else
network:invoke("teleportPlayer", player, destinationInfo.id, "taxi")
end
end)
return true, ""
else
return false, "notEnoughMoney"
end
end
network:create("takeTaxiToDestination", "RemoteFunction", "OnServerInvoke", takeTaxiToDestination)
]]
return {}
================================================
FILE: src/ServerScriptService/contents/manager_teleport.lua
================================================
local module = {}
module.priority = 3
local TeleportService = game:GetService("TeleportService")
local network
local utilities
local configuration
local function preparePlayerToTeleport(player, destination)
if player:FindFirstChild("DataSaveFailed") then
network:fireClient("alertPlayerNotification", player, {
text = "Cannot teleport during a DataStore outage.";
textColor3 = Color3.fromRGB(255, 57, 60)
})
return false
end
if player:FindFirstChild("Teleporting") or player:FindFirstChild("teleporting") then
return false
end
if player:FindFirstChild("DataLoaded") == nil then
return false
end
destination = utilities.placeIdForGame(destination)
local timestamp = network:invoke("saveDataForTeleport", player)
if timestamp then
local teleportData = {}
teleportData.arrivingFrom = game.PlaceId
teleportData.destination = destination
teleportData.dataTimestamp = timestamp
teleportData.dataSlot = player.dataSlot.Value
teleportData.analyticsSessionId = player.AnalyticsSessionId.Value
teleportData.joinTime = player.JoinTime.Value
teleportData.partyData = network:invoke("getPartyDataByPlayer", player)
return teleportData
else
network:fireClient("alertPlayerNotification", player, {
text = "Failed to save your data. Teleportation canceled.";
textColor3 = Color3.fromRGB(255, 57, 60)
})
return false
end
end
local function createPartyTeleportData(playersToTeleport, destination, partyLeaderUserId, spawnLocation)
for _, player in pairs(playersToTeleport) do
network:fireClient("alertPlayerNotification", player, {text = "Preparing to teleport party...";})
end
local groupTeleportData = {}
groupTeleportData.members = {}
for _, player in pairs(playersToTeleport) do
if player:FindFirstChild("teleporting") then
network:fireClient("alertPlayerNotification", player, {
text = "Party teleport failed: "..player.Name.." is already teleporting.";
textColor3 = Color3.fromRGB(255, 57, 60)
})
return false
end
end
local playersSuccessfullySaved = {}
for _, player in pairs(playersToTeleport) do
local playerTeleportData = preparePlayerToTeleport(player, destination)
warn(player.Name, "teleport data", game.HttpService:JSONEncode(playerTeleportData))
if playerTeleportData and playerTeleportData.partyData then
playerTeleportData.partyData.partyLeaderUserId = partyLeaderUserId
if spawnLocation then
playerTeleportData.spawnLocation = spawnLocation
end
table.insert(playersSuccessfullySaved, player)
groupTeleportData.members[player.Name] = playerTeleportData
else
for _, oPlayer in pairs(playersToTeleport) do
network:fireClient("alertPlayerNotification", oPlayer, {
text = player.Name.." failed to teleport with the party.";
textColor3 = Color3.fromRGB(234, 129, 59)
})
end
end
end
if #playersSuccessfullySaved > 0 then
warn("group data", game.HttpService:JSONEncode(groupTeleportData))
return playersSuccessfullySaved, groupTeleportData
else
return false
end
end
local function getReserveServerKeyForMirrorDestination(destination)
destination = utilities.placeIdForGame(destination)
local reserveServerKey
local success, err = pcall(function()
local mwv = configuration.getConfigurationValue("mirrorWorldVersion")
local mirrorWorldStore = game:GetService("DataStoreService"):GetDataStore("mirrorWorld"..mwv)
reserveServerKey = mirrorWorldStore:GetAsync(tostring(destination))
if reserveServerKey == nil then
reserveServerKey = TeleportService:ReserveServer(destination)
mirrorWorldStore:SetAsync(tostring(destination), reserveServerKey)
end
end)
return reserveServerKey, success, err
end
local function teleportPlayersToReserveServer(players, destination, spawnLocation, realm, teleportType, reserveServerKey)
destination = utilities.placeIdForGame(destination)
teleportType = teleportType or "default"
reserveServerKey = reserveServerKey or TeleportService:ReserveServer(destination)
for _, player in pairs(players) do
spawn(function()
if player:FindFirstChild("teleporting") then
return false
end
if player:FindFirstChild("dataLoaded") == nil or os.time() - player.dataLoaded.Value <= 5 then
return false, "Please wait before teleporting"
end
network:fireClient("signal_teleport", player, destination)
local teleportData = preparePlayerToTeleport(player, destination)
if teleportData then
if spawnLocation then
teleportData.spawnLocation = spawnLocation
end
teleportData.teleportType = teleportType
if game.ReplicatedStorage:FindFirstChild("mirrorWorld") or realm == "mirror" then
local reserveServerKey = getReserveServerKeyForMirrorDestination(destination)
if reserveServerKey then
TeleportService:TeleportToPrivateServer(destination, reserveServerKey, {player}, nil, teleportData)
else
return false, "Failed to find mirror world reserve server key"
end
else
TeleportService:TeleportToPrivateServer(destination, reserveServerKey, {player}, nil, teleportData)
end
end
return false, "Failed to prepare teleportData"
end)
end
end
local function teleportParty(playersToTeleport, destination, partyLeaderUserId, spawnLocation)
destination = utilities.placeIdForGame(destination)
local playersSuccessfullySaved, groupTeleportData = createPartyTeleportData(playersToTeleport, destination, partyLeaderUserId, spawnLocation)
if playersSuccessfullySaved and groupTeleportData then
local playerstring = ""
local n = #playersToTeleport
for i,player in pairs(playersToTeleport) do
playerstring = playerstring .. player.Name
if n > 1 and i == n-1 then
playerstring = playerstring .. " & "
elseif i < n then
playerstring = playerstring .. ","
end
end
if game.ReplicatedStorage:FindFirstChild("mirrorWorld") then
local reserveServerKey = getReserveServerKeyForMirrorDestination(destination)
if reserveServerKey then
TeleportService:TeleportToPrivateServer(destination, reserveServerKey, playersSuccessfullySaved, nil, groupTeleportData)
else
return false, "Failed to find key for mirror world teleport"
end
else
TeleportService:TeleportPartyAsync(destination, playersSuccessfullySaved, groupTeleportData --[[, replicatedStorage.teleportUI]])
end
spawn(function()
network:fireAllClients("signal_alertChatMessage", {
Text = playerstring .. " departed towards " .. utilities.getPlaceName(destination) .. ".";
Font = Enum.Font.SourceSansBold;
Color = Color3.fromRGB(45, 87, 255)
})
end)
return true, "Teleporting"
end
return false, "Failed to teleport"
end
local function teleportPlayerToJobId(player, destination, jobId, spawnLocation)
destination = utilities.placeIdForGame(destination)
local teleportType = "serverBrowser"
if player:FindFirstChild("teleporting") then
return false
end
if player:FindFirstChild("dataLoaded") == nil or os.time() - player.dataLoaded.Value <= 5 then
return false, "Please wait before teleporting"
end
network:fireClient("signal_teleport", player, destination)
local teleportData = preparePlayerToTeleport(player, destination)
if teleportData then
if spawnLocation then
teleportData.spawnLocation = spawnLocation
end
teleportData.teleportType = teleportType
spawn(function()
TeleportService:TeleportToPlaceInstance(destination, jobId, player, nil, teleportData)
end)
return true, "teleporting"
end
return false, "failed to prepare teleportdata"
end
local function teleportPlayer(player, destination, spawnLocation, realm, teleportType)
destination = utilities.placeIdForGame(destination)
local playerName = player.Name
teleportType = teleportType or "default"
if player:FindFirstChild("teleporting") then
return false
end
if player:FindFirstChild("dataLoaded") == nil or os.time() - player.dataLoaded.Value <= 5 then
return false, "Please wait before teleporting"
end
network:fireClient("signal_teleport", player, destination, teleportType)
local teleportData = preparePlayerToTeleport(player, destination)
if teleportData then
if spawnLocation then
teleportData.spawnLocation = spawnLocation
end
teleportData.teleportType = teleportType
if game.ReplicatedStorage:FindFirstChild("mirrorWorld") or realm == "mirror" then
local reserveServerKey = getReserveServerKeyForMirrorDestination(destination)
if reserveServerKey then
TeleportService:TeleportToPrivateServer(destination, reserveServerKey, {player}, nil, teleportData)
return true, "teleporting"
else
return false, "Failed to find key for mirror world teleport"
end
else
spawn(function()
TeleportService:Teleport(destination, player, teleportData)
end)
if teleportType == "death" and not player:FindFirstChild("disconnected") then
network:fireAllClients("signal_alertChatMessage", {
Text = playerName .. " escaped to " .. utilities.getPlaceName(destination) .. ".";
Font = Enum.Font.SourceSansBold;
Color = Color3.fromRGB(45, 87, 255)
})
end
return true, "teleporting"
end
end
return false, "failed to prepare teleportdata"
end
local function teleportPlayer_rune(player, destination)
destination = utilities.placeIdForGame(destination)
local playerName = player.Name
local success, reason = teleportPlayer(player, destination, nil, nil, "rune")
if success then
spawn(function()
network:fireAllClients("signal_alertChatMessage", {
Text = playerName .. " departed towards " .. utilities.getPlaceName(destination) .. " using a magical rune.";
Font = Enum.Font.SourceSansBold;
Color = Color3.fromRGB(45, 87, 255)
})
end)
end
return success, reason
end
local function playerRequest_useTeleporter(player, teleporter)
if teleporter and teleporter:IsA("BasePart") and game.CollectionService:HasTag(teleporter, "teleportPart") and teleporter:FindFirstChild("teleportDestination") then
if player.Character and player.Character.PrimaryPart and player:DistanceFromCharacter(teleporter.Position) <= 50 then
local playerName = player.Name
local success, reason = teleportPlayer(player, teleporter.teleportDestination.Value)
if success then
spawn(function()
network:fireAllClients("signal_alertChatMessage", {
Text = playerName .. " departed towards " .. utilities.getPlaceName(teleporter.teleportDestination.Value) .. ".";
Font = Enum.Font.SourceSansBold;
Color = Color3.fromRGB(45, 87, 255)
})
end)
end
return success, reason
end
end
return false
end
function module.init(Modules)
network = Modules.network
utilities = Modules.utilities
configuration = Modules.configuration
network:create("getPlayerTeleportData", "BindableFunction", "OnInvoke", preparePlayerToTeleport)
network:create("teleportPlayersToReserveServer", "BindableFunction", "OnInvoke", teleportPlayersToReserveServer)
network:create("createPartyTeleportData", "BindableFunction", "OnInvoke", createPartyTeleportData)
network:create("teleportParty", "BindableFunction", "OnInvoke", teleportParty)
network:create("teleportPlayerToJobId", "BindableFunction", "OnInvoke", teleportPlayerToJobId)
network:create("teleportPlayer", "BindableFunction", "OnInvoke", teleportPlayer)
network:create("teleportPlayer_rune", "BindableFunction", "OnInvoke", teleportPlayer_rune)
network:create("playerRequest_useTeleporter", "RemoteFunction", "OnServerInvoke", playerRequest_useTeleporter)
network:create("signal_teleport", "RemoteEvent")
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_trade.lua
================================================
-- manages trades
-- author: Polymorphic
local module = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local HttpService = game:GetService("HttpService")
local network
local configuration
local itemLookup = require(ReplicatedStorage.itemData)
--[[
playerTradeSessionData {}
instance player
string state
int gold
{inventoryTransferData} inventoryTransferDataCollection
tradeSessionData {}
string guid
string state
playerTradeSessionData playerTradeSessionData_player1
playerTradeSessionData playerTradeSessionData_player2
--]]
-- contains tradeSessionData
local tradeSessionDataCollection = {}
local pendingTrade_guids = {}
-- if IFFIsActiveTrade is set, the trade must be ongoing to return the sessionData
local function getTradeSessionDataByPlayer(player)
for i, tradeSessionData in pairs(tradeSessionDataCollection) do
if tradeSessionData.playerTradeSessionData_player1.player == player or tradeSessionData.playerTradeSessionData_player2.player == player then
return tradeSessionData
end
end
return nil
end
local function propogateTradeDataUpdate(tradeSessionData)
network:fireClient("signal_tradeSessionChanged", tradeSessionData.playerTradeSessionData_player1.player, tradeSessionData)
network:fireClient("signal_tradeSessionChanged", tradeSessionData.playerTradeSessionData_player2.player, tradeSessionData)
end
local function invalidatePendingTradesForPlayer(player)
for i, v in pairs(pendingTrade_guids) do
if v.playerToTradeWith == player or v.tradeRequester == player then
pendingTrade_guids[i] = nil
end
end
end
local invitationCD = {}
local function playerRequest_requestTrade(tradeRequester, playerToTradeWith)
if not configuration.getConfigurationValue("isTradingEnabled", tradeRequester) or not configuration.getConfigurationValue("isTradingEnabled", playerToTradeWith) then
return false, "Trading is disabled right now."
end
if tradeRequester:FindFirstChild("DataSaveFailed") then
network:fireClient("alertPlayerNotification", tradeRequester, {text = "Cannot trade during DataStore outage."; textColor3 = Color3.fromRGB(255, 57, 60)})
return false, "This feature is temporarily disabled"
end
if tradeRequester and playerToTradeWith and tradeRequester ~= playerToTradeWith then
if not getTradeSessionDataByPlayer(tradeRequester) and not getTradeSessionDataByPlayer(playerToTradeWith) then
if not invitationCD[tradeRequester] or (tick() - invitationCD[tradeRequester] > 3) then
invitationCD[tradeRequester] = tick()
local guid = HttpService:GenerateGUID(false)
pendingTrade_guids[guid] = {
tradeRequester = tradeRequester;
playerToTradeWith = playerToTradeWith;
}
network:fireClient("signal_playerTradeRequest", playerToTradeWith, tradeRequester, guid)
return true
else
return false, "stop sending trades so fast."
end
end
elseif tradeRequester == playerToTradeWith then
return false, "you can't trade with yourself, idiot."
end
return false, "player is already trading"
end
-- playerInventoryChanged_server
local function isPlayerInventoryTransferDataValid(player, inventoryTransferDataCollection)
local playerData = network:invoke("getPlayerData", player)
local count = 0 do
for i, inventoryTransferData in pairs(inventoryTransferDataCollection) do
local itemBaseData = itemLookup[inventoryTransferData.id]
if itemBaseData.soulbound then
return false
end
if not inventoryTransferData.stacks then
inventoryTransferData.stacks = 1
end
if inventoryTransferData.stacks <= 0 then
return false
elseif math.floor(inventoryTransferData.stacks) ~= inventoryTransferData.stacks then
return false
end
count = count + 1
end
end
if playerData then
local matches = 0
local trueInventoryTransferDataCollection = {}
for i, inventorySlotData in pairs(playerData.inventory) do
for ii, inventoryTransferData in pairs(inventoryTransferDataCollection) do
if inventorySlotData.position == inventoryTransferData.position and inventorySlotData.id == inventoryTransferData.id and (inventoryTransferData.stacks or 1) <= (inventorySlotData.stacks or 1) then
if inventorySlotData.soulbound then
return false
end
trueInventoryTransferDataCollection[ii] = inventorySlotData
matches = matches + 1
break
end
end
end
return matches == count, trueInventoryTransferDataCollection
end
end
local function getTradeSessionDataById(tradeSessionId)
for i, tradeSessionData in pairs(tradeSessionDataCollection) do
if tradeSessionData.id == tradeSessionId then
return tradeSessionData
end
end
return nil
end
local function playerRequest_cancelTrade(player)
local tradeSessionData = getTradeSessionDataByPlayer(player)
if tradeSessionData then
tradeSessionData.state = "canceled"
tradeSessionData.playerTradeSessionData_player1.state = "denied"
tradeSessionData.playerTradeSessionData_player2.state = "denied"
propogateTradeDataUpdate(tradeSessionData)
for i, _tradeSessionData in pairs(tradeSessionDataCollection) do
if _tradeSessionData == tradeSessionData then
table.remove(tradeSessionDataCollection, i)
break
end
end
end
end
local processTicketDuplicationValidationQueue = {}
local function playerRequest_updatePlayerTradeSessionData(player, guid, key, value)
local tradeSessionData = getTradeSessionDataByPlayer(player)
if not tradeSessionData then return false, "no tradeSessionData found for player" end
if tradeSessionData.guid ~= guid then return false, "invalid guid for tradeSessionData" end
local p1 = tradeSessionData.playerTradeSessionData_player1.player
local p2 = tradeSessionData.playerTradeSessionData_player2.player
-- stop teleporting while trading
if p1.Parent == nil or p1:FindFirstChild("teleporting") or p1:FindFirstChild("DataLoaded") == nil then
return false, "invalid players"
end
-- stop teleporting while trading
if p2.Parent == nil or p2:FindFirstChild("teleporting") or p2:FindFirstChild("DataLoaded") == nil then
return false, "invalid players"
end
local playerData = network:invoke("getPlayerData", player)
if playerData and tradeSessionData and tradeSessionData.state ~= "completed" and tradeSessionData.state ~= "canceled" then
local playerTradeSessionData_player = (tradeSessionData.playerTradeSessionData_player1.player == player and tradeSessionData.playerTradeSessionData_player1) or (tradeSessionData.playerTradeSessionData_player2.player == player and tradeSessionData.playerTradeSessionData_player2)
if key == "state" and (value == "approved" or value == "denied" or value == "none") then
if value ~= playerTradeSessionData_player.state then
playerTradeSessionData_player.state = value
-- prevent players from spamming approve!
local processTicket = HttpService:GenerateGUID(false)
processTicketDuplicationValidationQueue[guid] = processTicket
if key == "state" and value == "denied" and tradeSessionData.state == "countdown" then
tradeSessionData.state = "active"
propogateTradeDataUpdate(tradeSessionData)
return true
end
if tradeSessionData.state ~= "countdown" and tradeSessionData.playerTradeSessionData_player1.state == "approved" and tradeSessionData.playerTradeSessionData_player2.state == "approved" then
tradeSessionData.state = "countdown"
propogateTradeDataUpdate(tradeSessionData)
local startTime = tick()
while (tick() - startTime) < 6 and (tradeSessionData.playerTradeSessionData_player1.state == "approved" and tradeSessionData.playerTradeSessionData_player2.state == "approved") do
wait(1 / 4)
end
-- stop teleporting while trading
if p1.Parent ~= game.Players or p1:FindFirstChild("teleporting") or p1:FindFirstChild("DataLoaded") == nil or not network:invoke("getPlayerData", p1) then
return false, "invalid players call 2"
end
-- stop teleporting while trading
if p2.Parent ~= game.Players or p2:FindFirstChild("teleporting") or p2:FindFirstChild("DataLoaded") == nil or not network:invoke("getPlayerData", p2) then
return false, "invalid players call 2"
end
if not isPlayerInventoryTransferDataValid(p1, tradeSessionData.playerTradeSessionData_player1.inventoryTransferDataCollection) then
return false, tostring(p1) .. " has invalid trade data"
end
if not isPlayerInventoryTransferDataValid(p2, tradeSessionData.playerTradeSessionData_player2.inventoryTransferDataCollection) then
return false, tostring(p2) .. " has invalid trade data"
end
if processTicketDuplicationValidationQueue[guid] == processTicket and (tradeSessionData.playerTradeSessionData_player1.state == "approved" and tradeSessionData.playerTradeSessionData_player2.state == "approved") then
local success, errMsg = network:invoke("requestTradeBetweenPlayers", tradeSessionData.playerTradeSessionData_player1.player, tradeSessionData.playerTradeSessionData_player1.inventoryTransferDataCollection, tradeSessionData.playerTradeSessionData_player1.gold, tradeSessionData.playerTradeSessionData_player2.player, tradeSessionData.playerTradeSessionData_player2.inventoryTransferDataCollection, tradeSessionData.playerTradeSessionData_player2.gold)
if success then
tradeSessionData.state = "completed"
propogateTradeDataUpdate(tradeSessionData)
else
playerRequest_cancelTrade(player)
end
-- empty the guid
processTicketDuplicationValidationQueue[guid] = nil
for i, v in pairs(tradeSessionDataCollection) do
if v.guid == guid then
table.remove(tradeSessionDataCollection, i)
end
end
return success, errMsg
end
else
tradeSessionData.state = "active"
propogateTradeDataUpdate(tradeSessionData)
end
return true
end
elseif key == "gold" and (type(value) == "number" and value >= 0) then
if playerData.gold >= value then
playerTradeSessionData_player.gold = value
tradeSessionData.playerTradeSessionData_player1.state = "none"
tradeSessionData.playerTradeSessionData_player2.state = "none"
tradeSessionData.state = "active"
propogateTradeDataUpdate(tradeSessionData)
return true
else
return false, "invalid gold"
end
elseif key == "inventoryTransferDataCollection" and (typeof(value) == "table") then
local playerInventoryTransferDataCollectionIsValid, trueInventoryTransferDataCollection = isPlayerInventoryTransferDataValid(player, value)
if playerInventoryTransferDataCollectionIsValid then
tradeSessionData.playerTradeSessionData_player1.state = "none"
tradeSessionData.playerTradeSessionData_player2.state = "none"
tradeSessionData.state = "active"
playerTradeSessionData_player.inventoryTransferDataCollection = trueInventoryTransferDataCollection
propogateTradeDataUpdate(tradeSessionData)
return true
else
return false, "invalid trade transfer data"
end
else
return false, "invalid key"
end
end
return false, "invalid"
end
local function playerRequest_acceptTradeRequest(player, guid)
if player:FindFirstChild("DataSaveFailed") then
network:fireClient("alertPlayerNotification", player, {text = "Cannot trade during DataStore outage."; textColor3 = Color3.fromRGB(255, 57, 60)})
return false, "This feature is temporarily disabled"
end
if pendingTrade_guids[guid] then
-- cancel all current trades involving player except for this trade.
local pendingTradeData = pendingTrade_guids[guid]
invalidatePendingTradesForPlayer(pendingTradeData.tradeRequester)
invalidatePendingTradesForPlayer(pendingTradeData.playerToTradeWith)
if not pendingTradeData.tradeRequester.Parent or not pendingTradeData.playerToTradeWith.Parent then return false end
--[[
playerTradeSessionData {}
instance player
string state
int gold
{inventoryTransferData} inventoryTransferDataCollection
tradeSessionData {}
string guid
string state
playerTradeSessionData playerTradeSessionData_player1
playerTradeSessionData playerTradeSessionData_player2
--]]
local tradeSessionData = {}
tradeSessionData.guid = guid
tradeSessionData.state = "active"
tradeSessionData.playerTradeSessionData_player1 = {}
tradeSessionData.playerTradeSessionData_player1.player = pendingTradeData.tradeRequester
tradeSessionData.playerTradeSessionData_player1.state = "pending"
tradeSessionData.playerTradeSessionData_player1.gold = 0
tradeSessionData.playerTradeSessionData_player1.inventoryTransferDataCollection = {}
tradeSessionData.playerTradeSessionData_player2 = {}
tradeSessionData.playerTradeSessionData_player2.player = pendingTradeData.playerToTradeWith
tradeSessionData.playerTradeSessionData_player2.state = "pending"
tradeSessionData.playerTradeSessionData_player2.gold = 0
tradeSessionData.playerTradeSessionData_player2.inventoryTransferDataCollection = {}
-- register this trade :]
table.insert(tradeSessionDataCollection, tradeSessionData)
propogateTradeDataUpdate(tradeSessionData)
return true
end
return false, "invalid or inactive guid"
end
local function onPlayerInventoryChanged_server(player)
local tradeSessionData = getTradeSessionDataByPlayer(player)
if tradeSessionData then
tradeSessionData.playerTradeSessionData_player1.state = "none"
tradeSessionData.playerTradeSessionData_player2.state = "none"
tradeSessionData.state = "active"
end
end
local function onPlayerRemoving(player)
invitationCD[player] = nil
end
function module.init(Modules)
network = Modules.network
configuration = Modules.configuration
game.Players.PlayerRemoving:connect(onPlayerRemoving)
network:create("playerRequest_cancelTrade", "RemoteFunction", "OnServerInvoke", playerRequest_cancelTrade)
network:create("playerRequest_requestTrade", "RemoteFunction", "OnServerInvoke", playerRequest_requestTrade)
network:create("playerRequest_acceptTradeRequest", "RemoteFunction", "OnServerInvoke", playerRequest_acceptTradeRequest)
network:create("playerRequest_updatePlayerTradeSessionData", "RemoteFunction", "OnServerInvoke", playerRequest_updatePlayerTradeSessionData)
network:create("signal_playerTradeRequest", "RemoteEvent")
network:create("signal_tradeSessionChanged", "RemoteEvent")
network:connect("playerInventoryChanged_server", "Event", onPlayerInventoryChanged_server)
end
return module
================================================
FILE: src/ServerScriptService/contents/manager_treasure.lua
================================================
-- NEW and IMPROVED Treasure chest manager
local module = {}
local INTERVAL = 30 * 60 * 24
-- local current = math.floor(getTime() / INTERVAL)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local itemLookup = require(ReplicatedStorage.itemData)
local network
local utilities
local configuration
local levels
local rand = Random.new()
local currentDay = 0
local dailyChestCount = 7
local currentChests = {}
local function getTime()
-- 7AM/PM PT, 10AM/PM ET
return os.time() - INTERVAL / 12
end
local lootTable = {
{id = "health potion"; stacks = 10; chance = 30; minLevel = 0; maxLevel = 5;};
{id = "mana potion"; stacks = 10; chance = 30; minLevel = 0; maxLevel = 5;};
{id = "health potion"; stacks = 20; chance = 5; minLevel = 0; maxLevel = 5;};
{id = "mana potion"; stacks = 20; chance = 5; minLevel = 0; maxLevel = 5;};
{id = "100% armor defense scroll"; stacks = 1; chance = 15; minLevel = 0; maxLevel = 100;};
{id = "100% weapon attack scroll"; stacks = 1; chance = 15; minLevel = 0; maxLevel = 100;};
{id = "megaphone"; stacks = 1; chance = 1; minLevel = 10; maxLevel = 100;};
}
-- map-specific potions!!!!!!
local mapSpecificLoot = {
-- Whispering Dunes
["3303140173"] = {
{id = "crystal beetle"; stacks = 3; chance = 10;};
{id = "broken crystal beetle"; stacks = 6; chance = 30;};
{id = "broken crystal beetle"; stacks = 7; chance = 30;};
{id = "broken crystal beetle"; stacks = 8; chance = 30;};
{id = "cactus fruit"; stacks = 15; chance = 25;};
{id = "item dye yellow"; stacks = 1; chance = 1;};
};
-- Port Fidelio
["2546689567"] = {
{id = "rune hunter"; stacks = 3; chance = 25;};
{id = "health potion chalice"; stacks = 10; chance = 80;};
{id = "dexterity potion"; stacks = 1; chance = 10;};
{id = "arrow"; stacks = 50; chance = 20;};
{id = "banana"; stacks = 15; chance = 25;};
{id = "mighty sub"; stacks = 16; chance = 3;};
{id = "wayfarer ticket"; stacks = 1; chance = 2;};
{id = "item dye green"; stacks = 1; chance = 1;};
{id = "wooden bow"; attribute = "pristine"; stacks = 1; chance = 5;};
{id = "hunter bandit vest"; attribute = "pristine"; stacks = 1; chance = 5;};
{id = "hunter bandit mask"; attribute = "pristine"; stacks = 1; chance = 5;};
};
-- spider abyss
["3207211233"] = {
{id = "spider fang"; stacks = 30; chance = 80;};
{id = "royal spider egg"; stacks = 1; chance = 20;};
{id = "spider potion"; stacks = 1; chance = 10;};
{id = "spider fang dagger"; stacks = 1; chance = 3;};
{id = "spider bow"; stacks = 1; chance = 3;};
{id = "spider staff"; stacks = 1; chance = 3;};
{id = "spider sword"; stacks = 1; chance = 3;};
{id = "item dye purple"; stacks = 1; chance = 1;};
};
-- Scallop Shores
["2471035818"] = {
{id = "rune hunter"; stacks = 3; chance = 25;};
{id = "dexterity potion"; stacks = 1; chance = 10;};
{id = "tomahawk"; attribute = "pristine"; stacks = 1; chance = 5;};
{id = "arrow"; stacks = 50; chance = 20;};
{id = "item dye green"; stacks = 1; chance = 1;};
};
-- Shiprock Bottom
["3232913902"] = {
{id = "snel snel shell"; stacks = 1; chance = 1;};
{id = "snelleth shell"; stacks = 1; chance = 1;};
{id = "snelly shell"; stacks = 1; chance = 1;};
{id = "snelvin shell"; stacks = 1; chance = 1;};
};
-- Seaside Path
["2093766642"] = {
{id = "rune hunter"; stacks = 3; chance = 25;};
{id = "fish"; stacks = 10; chance = 80;};
{id = "tall blue fish"; stacks = 10; chance = 20;};
{id = "yellow puffer fish"; stacks = 3; chance = 10;};
};
-- The Colosseum
["2496503573"] = {
{id = "rune colosseum"; stacks = 3; chance = 25;};
{id = "health potion horn"; stacks = 10; chance = 80;};
};
-- Warrior Stronghold
["2470481225"] = {
{id = "rune warrior"; stacks = 3; chance = 25;};
{id = "health potion flagon"; stacks = 10; chance = 80;};
{id = "strength potion"; stacks = 1; chance = 10;};
{id = "item dye red"; stacks = 1; chance = 1;};
{id = "bronze helmet"; attribute = "pristine"; stacks = 1; chance = 5;};
{id = "bronze armor"; attribute = "pristine"; stacks = 1; chance = 5;};
{id = "bronze mace"; attribute = "pristine"; stacks = 1; chance = 5;};
};
-- Redwood Pass
["2376890690"] = {
{id = "rune warrior"; stacks = 3; chance = 25;};
{id = "strength potion"; stacks = 1; chance = 10;};
};
-- Enchanted forest
["2260598172"] = {
{id = "rune mage"; stacks = 3; chance = 25;};
{id = "health potion silver"; stacks = 10; chance = 80;};
};
-- Tree of life
["3112029149"] = {
{id = "rune mage"; stacks = 3; chance = 25;};
{id = "health potion silver"; stacks = 10; chance = 80;};
{id = "item dye blue"; stacks = 1; chance = 1;};
{id = "mage hat 2"; attribute = "pristine"; stacks = 1; chance = 5;};
{id = "mage robes 2"; attribute = "pristine"; stacks = 1; chance = 5;};
{id = "willow staff"; attribute = "pristine"; stacks = 1; chance = 5;};
};
-- The Clearing
["2060556572"] = {
{id = "oak axe"; attribute = "pristine"; stacks = 1; chance = 10;};
};
-- Nilgarf
["2119298605"] = {
{id = "rune nilgarf"; stacks = 3; chance = 25;};
{id = "pear"; stacks = 15; chance = 25;};
{id = "item renamer"; stacks = 1; chance = 1;};
{id = "item lore"; stacks = 1; chance = 1;};
{id = "item dye grey"; stacks = 1; chance = 1;};
};
-- Mushtropolis
["3273679677"] = {
{id = "mushroom soup"; stacks = 1; chance = 5;};
{id = "rune mushtown"; stacks = 3; chance = 25;};
{id = "golden mushroom"; stacks = 5; chance = 35;};
};
-- Mushroom Grotto
["2060360203"] = {
{id = "mushroom beard"; stacks = 30; chance = 20;};
{id = "mushroom mini"; stacks = 30; chance = 40;};
{id = "mushroom soup"; stacks = 1; chance = 1;};
{id = "rune mushtown"; stacks = 3; chance = 25;};
};
-- Mushtown
["2064647391"] = {
{id = "rune mushtown"; stacks = 3; chance = 25;};
{id = "mushroom mini"; stacks = 30; chance = 25;};
{id = "apple"; stacks = 15; chance = 25;};
{id = "mushroom soup"; stacks = 1; chance = 1;};
};
-- Mushroom Forest
["2035250551"] = {
{id = "rune mushtown"; stacks = 3; chance = 25;};
{id = "mushroom mini"; stacks = 30; chance = 40;};
{id = "mushroom soup"; stacks = 1; chance = 1;};
};
-- Internal build (testing)
-- ["2061558182"] = {
-- {id = "bronze helmet"; attribute = "pristine"; stacks = 1; chance = 500;};
-- };
}
local chestStorage = Instance.new("Folder")
chestStorage.Name = "chestStorage"
chestStorage.Parent = game.ServerStorage
local dailyChests = {}
local n = 1
for _, chest in pairs(game.CollectionService:GetTagged("treasureChest")) do
local specialContents = chest:FindFirstChild("inventory") or chest:FindFirstChild("ironChest") or chest:FindFirstChild("goldChest")
if not specialContents and chest.Name == "Chest" then
chest.Name = "Chest" .. n
n = n + 1
table.insert(dailyChests, chest)
chest.Parent = chestStorage
end
end
-- Shuffle random chests
local function newDay()
local today = math.floor(getTime() / INTERVAL)
local rand = Random.new(today)
local chance = dailyChestCount / #dailyChests
-- remove existing chests
for _, chest in pairs(currentChests) do
chest:Destroy()
end
currentChests = {}
-- add in new chests (clone)
for i, chest in pairs(dailyChests) do
if rand:NextNumber() <= chance then
local newChest = chest:Clone()
newChest.Parent = workspace
table.insert(currentChests, newChest)
end
end
end
-- Day loop
local function getTreasureForChest(player, chest)
local placeId = utilities.originPlaceId(game.PlaceId)
assert(chest, "Chest cannot be nil!")
local level = chest.chestLevel.Value
local goldReward = 0
local playerData = network:invoke("getPlayerData", player)
local mapLoot = mapSpecificLoot[tostring(placeId)]
local rewards = {}
local function selectReward()
-- ethyr reward
local ethyrReward
local globalData = playerData.globalData
if globalData then
local today = math.floor(getTime() / INTERVAL)
globalData.ethyrRewards = globalData.ethyrRewards or 0
if globalData.lastEthyrReward == nil or globalData.lastEthyrReward < today then
globalData.ethyrRewards = 0
globalData.lastEthyrReward = today
end
if globalData.ethyrRewards < 5 then
local chance = 0.025
if level >= 40 then
chance = 0.15
elseif level >= 25 then
chance = 0.1
elseif level >= 18 then
chance = 0.075
elseif level >= 7 then
chance = 0.04
end
if chance and rand:NextNumber() <= chance then
globalData.ethyrRewards = globalData.ethyrRewards + 1
ethyrReward = true
end
end
playerData.nonSerializeData.setPlayerData("globalData", globalData)
end
if ethyrReward then
local reward = {id = "ethyr pile"; stacks = 1;}
table.insert(rewards, {id = reward.id; stacks = reward.stacks})
elseif rand:NextInteger(1,5) == 4 then
-- 1/5 chance to recieve money
goldReward = (goldReward or 0) + levels.getQuestGoldFromLevel(level or 1) * 0.5
else
-- standard loot table
local lottery = {}
for i, lootInfo in pairs(lootTable) do
if (level >= (lootInfo.minLevel or 0)) and (level <= (lootInfo.maxLevel or 999999)) then
for i=1, (lootInfo.chance or 0) do
table.insert(lottery, lootInfo)
end
end
end
if mapLoot then
for i, lootInfo in pairs(mapLoot) do
if (level >= (lootInfo.minLevel or 0)) and (level <= (lootInfo.maxLevel or 999999)) then
for i=1, (lootInfo.chance or 0) do
table.insert(lottery, lootInfo)
end
end
end
end
if #lottery > 0 then
local reward = lottery[rand:NextInteger(1, #lottery)]
local rewardItemData = {}
for key, value in pairs(reward) do
if key ~= "minLevel" and key ~= "maxLevel" and key ~= "chance" then
rewardItemData[key] = value
end
end
table.insert(rewards, rewardItemData)
end
end
end
selectReward()
-- double trouble(rare)
if rand:NextNumber() >= 0.85 then
selectReward()
end
-- treasure hunt quest
for i, quest in pairs(playerData.quests.active) do
if quest.id == 10 then
local hadRelic = false
for ii, item in pairs(playerData.inventory) do
if item.id == 138 then
hadRelic = true
end
end
if not hadRelic then
local chance = rand:NextInteger(1,3)
if chance == 1 then
table.insert(rewards, {id = 138; stacks = 1})
end
end
end
end
return {
rewards = rewards,
gold = goldReward,
}
end
local function playerRequest_openTreasureChest(player, chest)
if player.Character and player.Character.PrimaryPart then
if chest and chest.PrimaryPart then
local dist = (chest.PrimaryPart.Position - player.Character.PrimaryPart.Position).magnitude
if dist >= 40 then
local factor = math.floor(dist/40)
network:invoke("incrementPlayerArcadeScore", player, configuration.getConfigurationValue("server_TPExploitScoreToFail") * (factor / 5))
-- (previously just a flat increment of 1/5... i decided to make it fun)
return false
end
end
end
if game.CollectionService:HasTag(chest, "treasureChest") then
local playerData = network:invoke("getPlayerData", player)
if playerData then
local treasureData = playerData.treasure
local chestData = treasureData["place-"..game.PlaceId].chests[chest.Name]
if chestData == nil or chestData.blocked then
return false, "Invalid chest"
end
local chestInfo = getTreasureForChest(player, chest) or {}
-- check for inventory
local specialContents = chest:FindFirstChild("inventory") or chest:FindFirstChild("ironChest") or chest:FindFirstChild("goldChest")
local today = math.floor(getTime() / INTERVAL)
if specialContents then
if chestData.open then
return false, "Already opened"
end
chestInfo = require(specialContents)
-- Allow custom prereqs for chests
if chestInfo.prereq then
local success, status = chestInfo.prereq(player, {network = network; utilities = utilities})
if not success then
return false, status
end
end
if specialContents.Name == "goldChest" then
spawn(function()
game.BadgeService:AwardBadge(player.userId, 2124445630)
end)
end
else
if chestData.open and chestData.open >= today then
return false, "Already opened"
end
--
end
local rewards = chestInfo.rewards or {}
local goldReward = chestInfo.gold or 0
if chest:FindFirstChild("minLevel") then
if player.level.Value < chest.minLevel.Value then
return false, "This chest is too sturdy to for you to break right now!"
end
else
local chestLevel = chest:FindFirstChild("chestLevel")
if chestLevel then
if player.level.Value < chestLevel.Value - 10 then
return false, "This chest is too sturdy to for you to break right now!"
end
else
return false, "This chest is broken! Let a dev know!"
end
end
local rewardInfo = utilities.copyTable(rewards)
if goldReward > 0 then
table.insert(rewardInfo, {id = 1; value = goldReward})
end
chestData.open = today
playerData.nonSerializeData.setPlayerData("treasure", treasureData)
spawn(function()
wait(0.784)
for _, itemInfo in pairs(rewardInfo) do
local dropInformation = {
lootDropData = itemInfo,
dropPosition = (chest.PrimaryPart.CFrame * CFrame.new(0,chest.PrimaryPart.Size.Y*2, 0)).Position,
itemOwners = {player},
}
local item = network:invoke("spawnItemOnGround",dropInformation.lootDropData, dropInformation.dropPosition, dropInformation.itemOwners)
if item == nil then break end
local attachmentTarget
local rand = Random.new()
local cf = chest.PrimaryPart.CFrame * CFrame.Angles(0, -math.pi/2, 0)
local velo = Vector3.new((rand:NextNumber() - 0.5) * 10, (2 + rand:NextNumber()) * 25, (rand:NextNumber() - 0.5) * 10) + cf.lookVector * 10
if item:IsA("BasePart") then
item.Velocity = velo
elseif item:IsA("Model") and (item.PrimaryPart or item:FindFirstChild("HumanoidRootPart")) then
local primaryPart = item.PrimaryPart or item:FindFirstChild("HumanoidRootPart")
if primaryPart then
primaryPart.Velocity = velo
end
end
wait(2/5)
end
end)
return true
end
return false, "Not a chest"
end
end
local function dayLoop()
while wait(1) do
local today = math.floor(getTime() / INTERVAL)
if today > currentDay then
newDay()
currentDay = today
end
end
end
local function conversion()
for placeId, contents in pairs(mapSpecificLoot) do
if tonumber(placeId) == utilities.originPlaceId(game.PlaceId) then
for _, lootEntry in pairs(contents) do
table.insert(lootTable, lootEntry)
end
end
end
for _, lootDrop in pairs(lootTable) do
if type(lootDrop.id) == "string" then
lootDrop.id = itemLookup[lootDrop.id].id
end
end
for placeId, placeLootTable in pairs(mapSpecificLoot) do
for _, lootDrop in pairs(placeLootTable) do
if type(lootDrop.id) == "string" then
local lookupLootDrop = itemLookup[lootDrop.id]
if (lookupLootDrop) then
lootDrop.id = itemLookup[lootDrop.id].id
else
if RunService:IsStudio() and game.PlaceId == placeId then
warn("Treasure manager: Item \"" .. lootDrop.id .. " does not exist in itemLookup!")
end
end
end
end
end
end
function module.init(Modules)
network = Modules.network
utilities = Modules.utilities
configuration = Modules.configuration
levels = Modules.levels
spawn(dayLoop)
conversion()
network:create("playerRequest_openTreasureChest","RemoteFunction","OnServerInvoke", playerRequest_openTreasureChest)
end
return module
================================================
FILE: src/ServerScriptService/contents/secretCodes/init.lua
================================================
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local Module = require(script.verify)
script.verify:Destroy()
local function redeemcode(player, code)
if string.sub(code,1,1) == "$" and string.len(code) > 6 and string.len(code) < 10 then
if game.MarketplaceService:PlayerOwnsAsset(player, 2376885433) then
local success = Module.Verify(player, code)
if success then
--[[
spawn(function()
game.BadgeService:AwardBadge(player.userId,1742681250)
end)
]]
return success
end
end
end
end
network:create("playerRequest_redeemcode", "RemoteFunction", "OnServerInvoke", redeemcode)
return {}
================================================
FILE: src/ServerScriptService/contents/secretCodes/verify/init.lua
================================================
local module = {}
local httpService = game:GetService("HttpService")
local key = require(script.key)
function module.Verify(player, code)
if player:FindFirstChild("VerifyCooldown") == nil then
local tag = Instance.new("BoolValue")
tag.Name = "VerifyCooldown"
tag.Parent = player
spawn(function()
wait(10)
if tag then
tag:Destroy()
end
end)
local playerRank = player:GetRankInGroup(1137635)
if type(code)=="string" and code ~= "" then
local rolesTable = {}
rolesTable["Verified"] = true
rolesTable["Vesterian"] = true
local resp = httpService:GetAsync("http://104.236.92.94:8005/process?userId=" .. player.userId .. "&code=" .. code .. "&doPromote=" .. tostring(playerRank == 1) .. "&roles=" .. httpService:JSONEncode(rolesTable).."&key="..key)
if string.lower(resp) == "true" or string.lower(resp) == "success" then
return true
else
game.ReplicatedStorage.Error:FireClient(player, "Server denied code: Response: "..resp)
return false
end
end
else
return false
end
end
return module
================================================
FILE: src/ServerScriptService/contents/secretCodes/verify/key.lua
================================================
return "Dfdkj26flkdsJzSDFkkDSF29lDjxkp9sos"
================================================
FILE: src/ServerScriptService/contents/teleport.lua
================================================
local module = {}
return module
================================================
FILE: src/ServerScriptService/modules/cannonScript.lua
================================================
return function()
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local utilities = modules.load("utilities")
local tween = modules.load("tween")
local debounceTable = {}
local animationController = script.Parent.Parent.AnimationController
local track = animationController:LoadAnimation(script.Parent.Parent.Animation)
track.Looped = false
local idle = animationController:LoadAnimation(script.Parent.Parent.idle)
idle:Play()
local lastPlayer
network:create("signal_playerReadyToBeBOOMEDByTheCannon", "RemoteEvent")
function BOOM(player, cannon)
if cannon ~= script.Parent.Parent then
return false
end
-- play cannon sounds
lastPlayer = player
-- animation
track.Looped = false
track:Play()
-- hack
local connection
connection = track.KeyframeReached:connect(function(keyframe)
if keyframe == "finish" then
track:Stop()
connection:disconnect()
connection = nil
end
end)
--[[
connection = track.DidLoop:connect(function()
track:Stop()
connection:disconnect()
connection = nil
end)
]]
script.Parent.Parent.target.CannonFire:Play()
network:fireClient("signal_playerReadyToBeBOOMEDByTheCannon", player, script.Parent.Parent)
script.Parent.Parent.target.smoke.Enabled = true
script.Parent.Parent.target.light.Enabled = true
script.Parent.Parent.target.explode:Emit(100)
script.Parent.Parent.target.light.Range = 15
for i=5,1,-1 do
wait(0.1)
script.Parent.Parent.target.explode:Emit(i)
if lastPlayer == player then
script.Parent.Parent.target.light.Range = i * 3
end
end
wait(0.5)
if lastPlayer == player then
script.Parent.Parent.target.smoke.Enabled = false
script.Parent.Parent.target.light.Enabled = false
end
track.Looped = false
spawn(function()
wait(5)
if connection then
connection:disconnect()
end
end)
end
network:create("signal_playerHasDecidedThatTheyWantToUseTheCannon", "RemoteEvent", "OnServerEvent", BOOM)
--network:create("playerRequest_cannonGoBOOM", "RemoteFunction", "OnServerInvoke", BOOM)
end
================================================
FILE: src/ServerScriptService/modules/enterGame.lua
================================================
-- main menu control script
return function()
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local configuration = modules.load("configuration")
local banList = {"chrisinator66";"Guest40231";"pup115888";"inearthly";"ReferalKing";"Raqnaar";"ivorfy";
"Axdreid";"Avalian";"Silent_Karasu";"QuakeBeard";"Harder_Dude";"howdoisnarfsnarf";
"GusFront";"longrod247";"JokeChef";"A4lt";"FEIMZIGRON";"cmack4243";
"buttmanthecool";"in2dream";"Linqed";"Ascusis";"RandomUsername64x";"decaul";"Fluxxxx_222";
"SpiritedNoFace";"Yoclash";"WrongStarZ";"JosephCorozzo";"YouKindaCutee";"Triseria";"Chronicxz";
"winter_soldat";"ctjepkes";"Gmasterjkh";"aleccc";"caine_felix";"EchikaKurosaki";"goithefat1";"Kyene";"AddictBloxy";"Thyemium";"RandoOof";"Xena_SR";"ASecretNote";
"MGsha3883";"FlamingExpert";"Alex_itk";"Imperfectjosh";"firestoney";"certifiedkidfiddler";
"thebowmaster2345";"willow";"Dvcember";"Cozmicc";"insaisissable";"GodzLightning";"WhiiteBeard";"swamp";"TehChickenBoy";"Gamer1OderSoHD";"boobuu444";"zxZTHeDeMonZxz";"kerso";"Wen3698";"iRunBoYi";
"TrollLexBR2";"VesteriaFreak";"Storage_Magical6";"link2uu";
"TheTruSucc";"Dony164";"sourtings";"DevilsSoul";"TheDark_Mage";
"LittlePeep";"K4Kiva";"Alekx1235x";"Kain87";"RONKLEY";"I7906";"chino146";"lightdarksun";
"LokiCard";"Thistlerow";"4legitly";"MikeGarciaAGM";"teuwaiseuu";"x0iAlan";"Achillez";"Christmas_Awaits";"YoRHa_9S";"shadowrider101";"Zaharielekk";"Maserq";
"Impossibilities";"25nite";"SaxoMaxyy";"rayverberm24";"Athynasius";"Avalinity";"Chro11oLucifer";"Ansonabc";"bf_g50";"SpeedWolfBG";
"cghcgh25801";"superbaconplayer1020";"Gidein";"m4nil410";
"SquishyRockets";"kxlebs";"VesteriaReferrals";"YezzSirrr";"Ezmaster01";"Vesteriakills";"Zinszo";"0dd_1";"Xorias";"ViaDarkNessTH";"AeonixTheFiend";
"nex_s2";"Kain2212";"Jank_z";"ScroogeMcDuck3679";"HieiSan";"aleister90";"BoneChills";
"Ros_ilia";"darkghost110";"DisastrousTemptation";"overpath41";"LesRocket";"wraithies";"JameO1818";"Decryptional";"F_oop";"AltVaziec";"alphabeto2444";"decryptedMIRAGE";"ThatCriminal";"TurkiSD";"VesteriaStoranger";
"CaterBois";"GeeTeeArgh";"decipheredENIGMA";"theRGBKing";
"IIIIIlllllllIIllIIIl";"MemezzMachine";"BIGLIVERTHEMAX1212"; "xxxwomenbeater62"; "vsk_0"; "vsk_god"; "dad_killer69"; "EpicMetatableMoment";
"children_sniffer72"; "vsk_1"; "Legoracer", "X6M"; "Draco7898"}
local banUserIdList = {}
spawn(function()
for i, playerName in pairs(banList) do
pcall(function()
local userId = game.Players:GetUserIdFromNameAsync(playerName)
if userId then
table.insert(banUserIdList, userId)
end
end)
end
end)
local function checkBanList(player)
for i, playerName in pairs(banList) do
if player.Name == playerName then
player:Kick("Banned")
return false
end
end
for i, userId in pairs(banUserIdList) do
if player.userId == userId then
player:Kick("Banned")
return false
end
end
return true
end
game.Players.PlayerAdded:Connect(checkBanList)
local playerGlobalDataContainer = {}
local paymentDataCache = {}
local playerAnalyticsId = {}
local transactionPendingHold = {}
local ProductCache = {d = "1"}
network:create("globalDataUpdated", "RemoteEvent")
game:GetService("MarketplaceService").ProcessReceipt = function(receiptInfo)
local player = game.Players:GetPlayerByUserId(receiptInfo.PlayerId)
if not player then
-- no player, abort.
warn("aborted purchase due to missing player")
return Enum.ProductPurchaseDecision.NotProcessedYet
end
if player:FindFirstChild("DataSaveFailed") then
-- player data has failed to save, abort.
warn("aborted purchase due to save failure")
return Enum.ProductPurchaseDecision.NotProcessedYet
end
local processingStartTime = tick()
-- ensure the player data is loaded in
repeat wait(0.1) until player == nil or player.Parent ~= game.Players or playerGlobalDataContainer[player] or tick() - processingStartTime > 15
if player and player.Parent == game.Players and playerGlobalDataContainer[player] then
local playerGlobalData = playerGlobalDataContainer[player]
paymentDataCache[player] = paymentDataCache[player] or {}
local playerPaymentDataCache = paymentDataCache[player]
playerPaymentDataCache.payments = playerPaymentDataCache.payments or {}
local doFinishTransaction
local modifiedGlobalPlayerData
-- Do not modify data if payment has already been cached this session
if playerPaymentDataCache.payments[receiptInfo.PurchaseId] then
doFinishTransaction = true
else
doFinishTransaction, modifiedGlobalPlayerData = network:invoke("processPayment", player, receiptInfo, playerGlobalData)
end
-- Cache previously-accepted payments in case data doesn't save
if doFinishTransaction and modifiedGlobalPlayerData then
playerPaymentDataCache.payments[receiptInfo.PurchaseId] = true
playerGlobalData = modifiedGlobalPlayerData
playerGlobalDataContainer[player] = modifiedGlobalPlayerData
end
if doFinishTransaction then
local transactionHoldTime = os.time()
transactionPendingHold[player] = transactionHoldTime
playerGlobalData.version = playerGlobalData.version + 1
local success, status, version = network:invoke("setPlayerGlobalData", player, playerGlobalData)
if transactionPendingHold[player] == transactionHoldTime then
transactionPendingHold[player] = nil
end
if success then
playerPaymentDataCache.globalDataVersion = version
spawn(function()
local ProductName = ProductCache[receiptInfo.ProductId]
if ProductName == nil then
local Product = game.MarketplaceService:GetProductInfo(receiptInfo.ProductId,Enum.InfoType.Product)
if Product then
ProductName = Product.Name
ProductCache[receiptInfo.ProductId] = ProductName
else
ProductName = "unknown"
end
end
print("transaction recorded:", ProductName)
network:invoke("purchaseMade", player, "Product", ProductName, receiptInfo.CurrencySpent)
end)
network:fireClient("globalDataUpdated", player, playerGlobalData)
print("Transaction success")
return Enum.ProductPurchaseDecision.PurchaseGranted
else
warn("Transaction failed")
return Enum.ProductPurchaseDecision.NotProcessedYet
end
end
end
warn("no player global data found")
return Enum.ProductPurchaseDecision.NotProcessedYet
end
network:create("loadGame", "RemoteFunction", "OnServerInvoke", function(player)
if not checkBanList(player) then
return false
end
print("starting new session")
playerAnalyticsId[player] = network:invoke("newSession", player)
print(playerAnalyticsId[player])
local success, data, status = network:invoke("getPlayerGlobalData", player)
if success then
if data == nil then
local newtag = Instance.new("BoolValue")
newtag.Name = "newPlayerTag"
newtag.Parent = player
end
playerGlobalDataContainer[player] = data or {}
return success, data, status
end
return false, nil, status
end)
local referralStore = game:GetService("DataStoreService"):GetDataStore("Referrals2")
local referralConnection
local isMessagingEnabled, messagingError
spawn(function()
repeat
isMessagingEnabled, messagingError = pcall(function()
if referralConnection then
referralConnection:Disconnect()
referralConnection = nil
end
referralConnection = game:GetService("MessagingService"):SubscribeAsync("acceptedReferrals", function(message)
local userId = message.Data
local hasReferred
local success, fail = pcall(function()
referralStore:UpdateAsync(tostring(userId), function(previouslyReferred)
hasReferred = previouslyReferred
return true
end)
end)
if not success then
warn("oh no!", fail)
end
local player = game.Players:GetPlayerByUserId(userId)
if player then
if not hasReferred then
local acceptedTag = Instance.new("BoolValue")
acceptedTag.Name = "acceptedReferral"
acceptedTag.Parent = player
end
end
end)
end)
wait(10)
until isMessagingEnabled
end)
network:create("sendReferral", "RemoteFunction", "OnServerInvoke", function(player, username)
if not checkBanList(player) then
return false
end
if not (isMessagingEnabled and referralConnection ~= nil) then
return false, "Roblox's MessagingService is offline. Please wait or rejoin to attempt a referral"
end
if player:FindFirstChild("acceptedReferral") then
return false, "Already referred"
end
if not player:FindFirstChild("newPlayerTag") then
return false, "Invalid - not a new player"
end
if player:FindFirstChild("messagePending") then
return false, "Invalid - message is already sending"
end
local hasReferred
local success, err = pcall(function()
hasReferred = referralStore:GetAsync(tostring(player.userId))
end)
if not success then
return false, "DataStore error - "..(err or "unknown")
end
if hasReferred then
return false, "You've already referred"
end
if username == player.Name then
return false, "You're not funny"
end
local userId
local idSuccess = pcall(function()
userId = game.Players:GetUserIdFromNameAsync(username)
end)
if not idSuccess then
return false, "Failed to find player userId, try again."
end
local tag = Instance.new("StringValue")
tag.Name = "messagePending"
tag.Value = username
tag.Parent = player
game.Debris:AddItem(tag, 10)
local messageSuccess, errMsg = pcall(function()
game:GetService("MessagingService"):PublishAsync("user-"..tostring(userId), {messageType = "referral"; referredUserId = player.userId; referredUsername = player.Name})
end)
if messageSuccess then
return true, "Awaiting a response, please do not leave this page..."
else
tag:Destroy()
return false, "Message error - "..(errMsg or "unknown")
end
end)
game.Players.PlayerRemoving:Connect(function(player)
playerGlobalDataContainer[player] = nil
paymentDataCache[player] = nil
playerAnalyticsId[player] = nil
end)
--[[
{
"lastSave": {
"-slot1": 1549216535,
"-slot7": 11,
"-slot5": 13,
"-slot3": 1549242980,
"-slot9": 15,
"-slot8": 4,
"-slot6": 3,
"-slot2": 1549139589,
"-slot10": 2,
"-slot4": 74
},
"ethyr": 12100,
"itemStorage": [],
"saveSlotData": {
"-slot4": {
"customized": true,
"level": 8,
"lastLocation": 2093766642,
"class": "Adventurer",
"accessories": {
"hair": 12,
"face": 2,
"shirtColorId": 4,
"undershirt": 2,
"skinColorId": 9,
"underwear": 4,
"hairColorId": 1
},
"equipment": [{
"upgrades": 2,
"successfulUpgrades": 2,
"position": 1,
"id": 3,
"modifierData": [{
"baseDamage": 5,
"dex": 2
}, {
"baseDamage": 1
}],
"stacks": 1
}]
}
},
"version": 1091
}
--]]
local teleportService = game:GetService("TeleportService")
local function getReserveServerKeyForMirrorDestination(destination)
local reserveServerKey
local success, err = pcall(function()
local mirrorWorldStore = game:GetService("DataStoreService"):GetDataStore("mirrorWorld"..configuration.getConfigurationValue("mirrorWorldVersion"))
reserveServerKey = mirrorWorldStore:GetAsync(tostring(destination))
if reserveServerKey == nil then
reserveServerKey = teleportService:ReserveServer(destination)
mirrorWorldStore:SetAsync(tostring(destination), reserveServerKey)
end
end)
return reserveServerKey, success, err
end
network:create("enterGame", "RemoteFunction", "OnServerInvoke", function(player, _, timestamp, slot, customize, realm)
if not checkBanList(player) then
return false
end
local rank = player:GetRankInGroup(4238824)
local isAdmin = true -- rank >=20
local isLegend = rank >= 2
local allowedSlots = (isAdmin and 20) or (isLegend and 10) or 4
-- todo remove this lmao
if not isAdmin then
player:Kick("Not authorized.")
return false
end
if slot > allowedSlots then
player:Kick("Not authorized.")
return false
end
if realm == "mirror" then
-- teleport to the mirror world (admin only)
if not isAdmin then
player:Kick("Not authorized.")
return false
end
local destination = 3372071669
print("Going to the mirror world")
local teleportData = {
destination = destination;
dataTimestamp = timestamp;
dataSlot = slot;
playerAccessories = customize;
arrivingFrom = game.PlaceId;
analyticsSessionId = player:FindFirstChild("AnalyticsSessionId") and player.AnalyticsSessionId.Value;
joinTime = player:FindFirstChild("JoinTime") and player.JoinTime.Value;
}
local reserveServerKey = getReserveServerKeyForMirrorDestination(destination)
if reserveServerKey then
teleportService:TeleportToPrivateServer(destination, reserveServerKey, {player}, nil, teleportData)
else
print("Mirror TP failed")
return false, "Failed to find key for mirror world teleport"
end
else
-- teleport to the normal world (everyone else)
local realDestination
local playerGlobalData = playerGlobalDataContainer[player]
if playerGlobalData and playerGlobalData.saveSlotData then
local slotData = playerGlobalData.saveSlotData["-slot"..tostring(slot)]
if slotData and slotData.lastLocation then
realDestination = slotData.lastLocation
else
if game.GameId == 712031239 then
realDestination = 4041449372
else
realDestination = 2064647391
end
end
end
if transactionPendingHold[player] then
local startTime = os.time()
repeat wait() until transactionPendingHold[player] == nil or player == nil or player.Parent ~= game.Players
if player == nil or player.Parent ~= game.Players then
return false
end
end
local destination = realDestination or (game.GameId == 712031239 and 4041449372) or 2064647391
local playerPaymentDataCache = paymentDataCache[player]
if playerPaymentDataCache and playerPaymentDataCache.globalDataVersion then
timestamp = playerPaymentDataCache.globalDataVersion
end
local wasReferred = player:FindFirstChild("acceptedReferral") and true
local spawnLocation
if not realDestination then
spawnLocation = "newb"
end
local teleportData = {
destination = destination;
dataTimestamp = timestamp;
dataSlot = slot;
playerAccessories = customize;
arrivingFrom = game.PlaceId;
analyticsSessionId = player:FindFirstChild("AnalyticsSessionId") and player.AnalyticsSessionId.Value;
joinTime = player:FindFirstChild("JoinTime") and player.JoinTime.Value;
wasReferred = wasReferred;
spawnLocation = spawnLocation
}
network:fireClient("signal_teleport", player, destination)
spawn(function()
wait(0.5)
game:GetService("TeleportService"):Teleport(destination, player, teleportData--[[, replicatedStorage:FindFirstChild("teleportUI")]])
end)
end
end)
local TeleportService = game:GetService("TeleportService")
local function isUserInSameGame(userId)
local success, errorMessage, placeId, jobId
for i=1,2 do
local psuccess, perror = pcall(function()
success, errorMessage, placeId, jobId = TeleportService:GetPlayerPlaceInstanceAsync(userId)
end)
if psuccess or success or jobId then
break
end
end
return success, errorMessage, placeId, jobId
end
local function fetchPlayerFriendsInfo(Player, friendInfo)
print("!!start fetching")
local onlineFriends = {}
for i,friend in pairs(friendInfo) do
if friend.IsOnline then
if friend.PlaceId and friend.PlaceId > 0 then
local success, err, placeId, jobId = isUserInSameGame(friend.VisitorId)
local location = friend.LastLocation
--[[
location = string.gsub(location, "playing ", "In ")
location = string.gsub(location, "Playing ", "In ")
if string.find(location:lower(), "creating") then
location = "Roblox Studio"
end
if string.find(location, "Vesteria") then
location = "Main Menu"
end
]]
if placeId or jobId then
if placeId == 2376885433 then
location = "Main Menu"
else
pcall(function()
local info = game.MarketplaceService:GetProductInfo(placeId)
if info then
location = "In " .. info.Name
end
end)
end
friend.LastLocation = location
table.insert(onlineFriends, friend)
end
end
end
end
print("!!done fetching")
return onlineFriends
end
network:create("fetchPlayerFriendsInfo","RemoteFunction","OnServerInvoke",fetchPlayerFriendsInfo)
end
================================================
FILE: src/ServerScriptService/modules/firepitScript.lua
================================================
return function()
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local firetime = 0
local DURATION = 300
local fireLoop
local fireIgnite
--[[
local fireLoop = Instance.new("Sound")
fireLoop.SoundId = "rbxassetid://2564068359"
fireLoop.Looped = true
fireL
local fireIgnite = Instance.new("Sound")
fireIgnite.SoundId = "rbxassetid://2564068060"
]]
game.CollectionService:AddTag(script.Parent, "firepit")
if script.Parent:IsA("BasePart") then
script.Parent.Anchored = true
end
local sounds = game.ReplicatedStorage.assets:WaitForChild("sounds")
if sounds:FindFirstChild("fireLoop") then
fireLoop = sounds.fireLoop:Clone()
fireLoop.Parent = script.Parent
end
if sounds:FindFirstChild("fireIgnite") then
fireIgnite = sounds.fireIgnite:Clone()
fireIgnite.Parent = script.Parent
end
local function ignite(player, firepit)
if firepit == script.Parent then
if fireIgnite then
fireIgnite:Play()
end
for _, v in pairs(script.Parent:GetChildren()) do
pcall(function()
v.Enabled = true
end)
end
if fireLoop then
fireLoop:Play()
end
game.CollectionService:RemoveTag(script.Parent,"interact")
firetime = os.time()
end
end
local function dampen()
for _, v in pairs(script.Parent:GetChildren()) do
pcall(function()
v.Enabled = false
end)
end
if fireLoop then
fireLoop:Stop()
end
game.CollectionService:AddTag(script.Parent,"interact")
end
network:create("igniteFirePit","RemoteEvent","OnServerEvent",ignite)
while wait(1) do
if script.Parent.Fire.Enabled and os.time() - firetime > DURATION then
dampen()
end
end
--
end
================================================
FILE: src/ServerScriptService/server.server.lua
================================================
-- Master server script
local modules = {}
local ServerScriptService = game:GetService("ServerScriptService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local directories = {ReplicatedStorage.modules, ServerScriptService.contents}
local beginInit = false
local function setup(directory)
for _, moduleScript in pairs(directory:GetDescendants()) do
if moduleScript:IsA("ModuleScript") then
print("$ server", "require module", moduleScript.Name)
modules[moduleScript.Name] = require(moduleScript)
end
end
end
local function initialize()
for moduleName, module in pairs(modules) do
if typeof(module) == "table" and (module.init and not module.__initialized) then
print("$ server", "initialize module", moduleName)
module.init(modules)
module.__initialized = true
end
end
end
for _, directory in pairs(directories) do
-- Get all static modules
setup(directory)
-- Ongoing support
directory.DescendantAdded:connect(function(moduleScript)
if moduleScript:IsA("ModuleScript") then
print("$ server", "require module", moduleScript.Name)
local module = require(moduleScript)
modules[moduleScript.Name] = module
if typeof(module) == "table" and module.init and beginInit then
print("$ server", "initialize module", moduleScript.Name)
module.init(modules)
end
end
end)
end
table.sort(modules, function(module1, module2)
return (module1.priority or 10) < (module2.priority or 10)
end)
beginInit = true
initialize()
print("$ server", "all modules in queue initialized")
================================================
FILE: src/StarterGui/abilities.lua
================================================
local module = {}
local menu = script.Parent.gameUI.menu_abilities
function module.show()
menu.Visible = not menu.Visible
end
function module.hide()
menu.Visible = false
end
local replicatedStorage = game:GetService("ReplicatedStorage")
local abilityLookup = require(replicatedStorage:WaitForChild("abilityLookup"))
function module.init(Modules)
local network = Modules.network
local uiCreator = Modules.uiCreator
local abilityDataPairing = {}
local function update(abilities)
-- playerData.abilities
abilities = abilities or network:invoke("getCacheValueByNameTag", "abilities")
local unlockedAbilities = {}
print(abilities)
for _, abilityData in pairs(abilities) do
-- TODO: check if REALLY unlocked
table.insert(unlockedAbilities, abilityData)
--print(abilityData)
end
-- clear existing buttons
for _, button in pairs(menu.content:GetChildren()) do
if button:IsA("GuiObject") then
button:Destroy()
end
abilityDataPairing = {}
end
for i = 1, 9 do
local template = menu.sampleAbility:clone()
template.Name = i
local abilityData = unlockedAbilities[i]
if abilityData then
local abilityInfo = abilityLookup[abilityData.id]
template.item.Image = abilityInfo.image
abilityDataPairing[template] = abilityData
uiCreator.drag.setIsDragDropFrame(template.item)
uiCreator.setIsDoubleClickFrame(template.item, 0.2, function()
local thing, error = network:invoke("abilityUseRequest", abilityData.id)
end)
end
template.Parent = menu.content
template.Visible = true
end
end
update()
network:connect("propogationRequestToSelf", "Event", function(key, value)
if key == "abilities" then
update(value)
end
end)
network:create("getAbilitySlotDataByAbilitySlotUI", "BindableFunction", "OnInvoke", function(button)
return abilityDataPairing[button.Parent]
end)
end
menu.close.Activated:connect(module.hide)
return module
================================================
FILE: src/StarterGui/bossHealth.lua
================================================
local module = {}
local frame = script.Parent.gameUI.bossHealth
function module.init(Modules)
local network = Modules.network
frame.Visible = false
network:create("prepareBossHealthUIForMonster", "BindableFunction", "OnInvoke", function(monsterData)
if monsterData.portrait then
frame.thumbnail.Image = monsterData.portrait
end
frame.Visible = true
return frame
end)
end
return module
================================================
FILE: src/StarterGui/chat.lua
================================================
local module = {}
function module.init()
local function main()
game.StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.All, false)
spawn(function()
wait()
-- game.StarterGui:SetCore("ChatWindowPosition", UDim2.new(0,0,0,0))
game.StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.Chat, true)
game.StarterGui:SetCore("ChatBarDisabled", false)
game.StarterGui:SetCore("ChatActive", true)
end)
end
main()
end
return module
================================================
FILE: src/StarterGui/checkpoint.lua
================================================
local module = {}
function module.init(Modules)
local tween = Modules.tween
local events = Modules.events
local currentSpawnPoint
local textLabel = script.Parent.gameUI.checkpoint
events:registerForEvent("playerRespawnPointChanged", function(spawnPoint)
if spawnPoint:FindFirstChild("description") then
currentSpawnPoint = spawnPoint
textLabel.Text = spawnPoint.description.Value
textLabel.TextStrokeTransparency = 1
textLabel.TextTransparency = 1
textLabel.Visible = true
tween(textLabel, {"TextTransparency", "TextStrokeTransparency"}, 0, 1)
delay(3, function()
if currentSpawnPoint == spawnPoint then
tween(textLabel, {"TextTransparency", "TextStrokeTransparency"}, 1, 1)
end
end)
end
end)
end
return module
================================================
FILE: src/StarterGui/customization.lua
================================================
-- character customization module
-- berezaa
local ui = script.Parent.customize
local accessories = game.ReplicatedStorage.assets.accessories
local module = {}
local connection
local targetTableTop
local currentDesiredAppearance
function module.display()
end
function module.hide()
end
local runService = game:GetService("RunService")
function module.init(Modules)
local network = Modules.network
local tween = Modules.tween
local input = Modules.input
local utilities = Modules.utilities
local lookup = accessories
local renderedItems = Instance.new("Folder")
renderedItems.Name = "renderedOptions"
renderedItems.Parent = workspace
local characterRender
local rand = Random.new(os.time())
local characterTable = {}
renderedItems.DescendantAdded:connect(function(object)
if object.Name == "bodyPart" then
local part = object.Parent
part.Color = lookup:FindFirstChild("skinColor"):FindFirstChild(tostring(characterTable.accessories.skinColorId or 1)).Value
elseif object.Name == "hair_Head" and object:IsA("BasePart") then
object.Color = lookup:FindFirstChild("hairColor"):FindFirstChild(tostring(characterTable.accessories.hairColorId or 1)).Value
elseif object.Name == "shirt" or object.Name == "shirtTag" then
if object.Name == "shirtTag" then
object = object.Parent
end
if object:IsA("BasePart") then
object.Color = lookup:FindFirstChild("shirtColor"):FindFirstChild(tostring(characterTable.accessories.shirtColorId or 1)).Value
end
end
end)
local pastLanding = true
local function getItemFromHitpart(hitpart)
for _, renderItem in pairs(renderedItems:GetChildren()) do
if hitpart:IsDescendantOf(renderItem) then
return renderItem
end
end
end
local selectedItem
local function selectItem(item)
if selectedItem and selectedItem.Parent then
selectedItem.PrimaryPart.Transparency = 1
selectedItem = nil
end
if item then
selectedItem = item
item.PrimaryPart.Transparency = 0.3
end
end
local inputModule = input
local mouseDownTime = 0
game:GetService("UserInputService").InputBegan:connect(function(input, absorbed)
if not absorbed and input.UserInputType == Enum.UserInputType.MouseButton1 then
mouseDownTime = tick()
end
end)
local currentCategory
game:GetService("UserInputService").InputEnded:connect(function(input, absorbed)
if not absorbed and input.UserInputType == Enum.UserInputType.MouseButton1 and tick() - mouseDownTime <= 2 then
if selectedItem then
local currentDisplayCategory = currentCategory
if currentDisplayCategory == "skinColor" then
currentDisplayCategory = "skinColorId"
end
characterTable.accessories[currentDisplayCategory] = tonumber(selectedItem.Name)
network:invoke("applyCharacterAppearanceToRenderCharacter", characterRender.entity, characterTable)
end
end
end)
local function reset()
for _, oslot in pairs(script.Parent.Frame.DataSlots:GetChildren()) do
if oslot:IsA("ImageButton") then
oslot.ImageColor3 = Color3.fromRGB(126, 126, 126)
end
end
end
local debounce = false
local function getModelAveragePosition(model)
local preavg = Vector3.new()
local totalcount = 0
for _, part in pairs(model:GetChildren()) do
if part:IsA("BasePart") then
preavg = preavg + part.Position
totalcount = totalcount + 1
end
end
return preavg / totalcount
end
local function updateColors()
for _, object in pairs(renderedItems:GetDescendants()) do
if object.Name == "bodyPart" then
local part = object.Parent
part.Color = lookup:FindFirstChild("skinColor"):FindFirstChild(tostring(characterTable.accessories.skinColorId or 1)).Value
elseif object.Name == "hair_Head" and object:IsA("BasePart") then
object.Color = lookup:FindFirstChild("hairColor"):FindFirstChild(tostring(characterTable.accessories.hairColorId or 1)).Value
elseif object.Name == "shirt" or object:FindFirstChild("shirtTag") then
object.Color = lookup:FindFirstChild("shirtColor"):FindFirstChild(tostring(characterTable.accessories.shirtColorId or 1)).Value
end
end
network:invoke("applyCharacterAppearanceToRenderCharacter", characterRender.entity, characterTable)
end
local function createRepresentationOfItem(item)
local repre
if item:IsA("Color3Value") then
repre = script.colorRepre:Clone()
repre.value.Color = item.Value
repre.Name = item.Name
else
repre = item:Clone()
end
local avgpos = getModelAveragePosition(repre)
-- reparent everything to root
for _, part in pairs(repre:GetDescendants()) do
if part:IsA("BasePart") then
if part.Parent == repre and part:FindFirstChild("colorOverride") == nil then
local bodyPartTag = Instance.new("BoolValue")
bodyPartTag.Name = "bodyPart"
bodyPartTag.Parent = part
else
part.Parent = repre
end
part.Anchored = true
end
end
-- create a PrimaryPart using the avgPos
local primaryPart = Instance.new("Part")
primaryPart.Size = Vector3.new(2,0.5,2)
primaryPart.CFrame = CFrame.new(avgpos - Vector3.new(0,2,0))
primaryPart.Parent = repre
primaryPart.Anchored = true
primaryPart.TopSurface = Enum.SurfaceType.Smooth
primaryPart.Material = Enum.Material.Neon
primaryPart.Transparency = 1
local immuneTag = Instance.new("BoolValue")
immuneTag.Name = "colorOverride"
immuneTag.Parent = primaryPart
repre.PrimaryPart = primaryPart
return repre
end
local displayConnection
local lastXboxSelected
local function generateCoolButtons()
ui.xboxButtons:ClearAllChildren()
for _, item in pairs(renderedItems:GetChildren()) do
if item and item.PrimaryPart then
local vector, onScreen = workspace.CurrentCamera:WorldToScreenPoint(item.PrimaryPart.Position)
if onScreen then
local button = ui.sampleXboxButton:Clone()
button.Name = item.Name
button.Parent = ui.xboxButtons
button.Visible = input.mode.Value == "xbox"
button.Position = UDim2.new(0, vector.X, 0, vector.Y + game.GuiService:GetGuiInset().Y)
button.Activated:connect(function()
if input.mode.Value == "xbox" then
characterTable.accessories[currentCategory] = tonumber(button.Name)
network:invoke("applyCharacterAppearanceToRenderCharacter", characterRender.entity, characterTable)
end
end)
button.SelectionGained:connect(function()
lastXboxSelected = item
selectItem(item)
end)
button.SelectionLost:connect(function()
if lastXboxSelected == item then
selectItem(nil)
end
end)
end
end
end
end
input.mode.Changed:connect(function()
for _, button in pairs(ui.xboxButtons:GetChildren()) do
if button:IsA("GuiObject") then
button.Visible = input.mode.Value == "xbox"
end
end
end)
local function displayCategory(categoryName)
renderedItems:ClearAllChildren()
lookup = accessories
local items = lookup:FindFirstChild(categoryName)
if items then
local x = 0
local z = 0
local target = targetTableTop
for _, item in pairs(items:GetChildren()) do
local repre = createRepresentationOfItem(item)
local cf = target.CFrame * CFrame.new((z * 6) - target.Size.X/2 + 1.1, target.Size.Y/2 + (z * 3.2), (x * 2.5) - target.Size.Z/2 + 1.1)
repre:SetPrimaryPartCFrame(cf)
repre.Parent = renderedItems
x = x + 1
if x > 4 then
x = 0
z = z + 1
end
end
end
for _, button in pairs(ui.buttons:GetChildren()) do
if button:IsA("ImageButton") then
button.ImageColor3 = Color3.fromRGB(103, 255, 212)
end
end
local newbutton = ui.buttons:FindFirstChild(categoryName)
if newbutton then
newbutton.ImageColor3 = Color3.fromRGB(255, 255, 255)
end
currentCategory = categoryName
ui.hairColor.Visible = false
ui.shirtColor.Visible = false
if categoryName == "hair" then
ui.hairColor.Visible = true
elseif categoryName == "undershirt" then
ui.shirtColor.Visible = true
end
generateCoolButtons()
end
for _, button in pairs(ui.buttons:GetChildren()) do
if button:IsA("ImageButton") then
button.Activated:connect(function()
displayCategory(button.Name)
end)
end
end
for _, color in pairs(lookup:WaitForChild("hairColor"):GetChildren()) do
if color:IsA("Color3Value") then
local buttonRepre = ui.colorButtonSample:Clone()
buttonRepre.ImageColor3 = color.value
buttonRepre.Name = color.Name
buttonRepre.Parent = ui.hairColor
buttonRepre.Visible = true
-- change hairColor
buttonRepre.Activated:connect(function()
characterTable.accessories["hairColorId"] = tonumber(buttonRepre.Name)
-- characterTable.accessories["hairColor"] = tonumber(buttonRepre.Name)
updateColors()
end)
end
end
for _, color in pairs(lookup:WaitForChild("shirtColor"):GetChildren()) do
if color:IsA("Color3Value") then
local buttonRepre = ui.colorButtonSample:Clone()
buttonRepre.ImageColor3 = color.value
buttonRepre.Name = color.Name
buttonRepre.Parent = ui.shirtColor
buttonRepre.Visible = true
-- change shirtColor
buttonRepre.Activated:connect(function()
characterTable.accessories["shirtColorId"] = tonumber(buttonRepre.Name)
-- characterTable.accessories["shirtColor"] = tonumber(buttonRepre.Name)
updateColors()
end)
end
end
function module.hide()
network:invoke("lockCameraPosition", false)
ui.Enabled = false
ui.Parent.gameUI.Enabled = true
if connection then
connection:disconnect()
connection = nil
end
if characterRender then
characterRender:Destroy()
end
end
ui.options.cancel.Activated:connect(module.hide)
ui.options.done.Activated:connect(function()
currentDesiredAppearance = characterTable
module.hide()
end)
function module.display(tableTop)
currentDesiredAppearance = nil
ui.Enabled = true
ui.Parent.gameUI.Enabled = false
targetTableTop = tableTop
if input.mode.Value == "xbox" then
game.GuiService.GuiNavigationEnabled = true
game.GuiService.SelectedObject = input.getBestButton(ui)
end
characterTable.accessories = {}
-- default values
for i,category in pairs(lookup:GetChildren()) do
local children = category:GetChildren()
if #children > 0 then
characterTable.accessories[category.Name] = 1;
end
end
-- real from player appearance
local player = game.Players.LocalPlayer
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart:FindFirstChild("appearance") then
local success, playerAppearance = utilities.safeJSONDecode(player.Character.PrimaryPart.appearance.Value)
if success and playerAppearance.accessories then
for accessory, value in pairs(playerAppearance.accessories) do
characterTable.accessories[accessory] = value
end
end
end
displayCategory("hair")
if characterRender then
characterRender:Destroy()
end
characterRender = network:invoke("createRenderCharacterContainerFromCharacterAppearanceData", workspace:WaitForChild("characterMask"), characterTable)
characterRender.Parent = workspace
local animationController = characterRender.entity:WaitForChild("AnimationController")
local track = animationController:LoadAnimation(workspace.characterMask:WaitForChild("idle"))
track.Looped = true
track.Priority = Enum.AnimationPriority.Idle
track:Play()
local cameraTarget = CFrame.new(targetTableTop.Position + Vector3.new(0,4.4,0) + targetTableTop.CFrame.rightVector * -28, targetTableTop.Position + Vector3.new(0,2.9,0)) * CFrame.new(-4,0,0)
network:invoke("lockCameraPosition", cameraTarget, 0.7)
tween(workspace.CurrentCamera,{"FieldOfView"},30,0.5)
utilities.playSound("swoosh")
wait(0.7)
generateCoolButtons()
if connection then
connection:disconnect()
end
local connection = game:GetService("UserInputService").InputChanged:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.MouseMovement then
local camSway = 0
local hitpart, hitpos
if pastLanding and renderedItems and ui.Enabled then
camSway = 70
local ray = workspace.CurrentCamera:ScreenPointToRay(input.Position.X,input.Position.Y,1)
local renderedChildren = renderedItems:GetChildren()
if #renderedChildren > 0 and inputModule.mode.Value == "pc" then
local hitpart, hitpos = workspace:FindPartOnRayWithWhitelist(Ray.new(ray.Origin, ray.Direction * 100), renderedChildren, true)
if hitpart then
local item = getItemFromHitpart(hitpart)
selectItem(item)
else
selectItem()
end
end
-- add camera sway effect, to a lessor extent
elseif not pastLanding then
camSway = 100
end
if camSway > 0 then
local ray = workspace.CurrentCamera:ScreenPointToRay(input.Position.X,input.Position.Y,camSway)
hitpart, hitpos = workspace:FindPartOnRay(ray)
if hitpos then
local lookat = cameraTarget.Position + cameraTarget.lookVector * 50
lookat = Vector3.new(hitpos.x + lookat.x * 25, hitpos.y + lookat.y * 25, hitpos.z + lookat.z * 25)/26
--workspace.CurrentCamera.CFrame = CFrame.new(cameraTarget.Position, lookat)
network:invoke("lockCameraPosition",CFrame.new(cameraTarget.Position, lookat),0.2)
end
end
end
end)
end
network:create("displayCharacterCustomizationScreen", "BindableFunction", "OnInvoke", module.display)
network:create("hideCharacterCustomizationScreen", "BindableFunction", "OnInvoke", module.hide)
-- yield until the player makes a selection
function module.yieldDesiredAppearance(tableTop)
if ui.Enabled then
return false, "Appearance picker is already active"
end
module.display(tableTop)
repeat runService.Heartbeat:wait() until not ui.Enabled
return currentDesiredAppearance
end
network:create("yieldCharacterCustomizationScreen", "BindableFunction", "OnInvoke", module.yieldDesiredAppearance)
end
return module
================================================
FILE: src/StarterGui/dataFail.lua
================================================
local module = {}
local player = game.Players.LocalPlayer
function module.init(Modules)
local tween = Modules.tween
local frame = script.Parent.gameUI.leftBar.dataFail
frame.Visible = false
local function display(n)
frame.success.Visible = false
frame.fail.Visible = false
frame.queue.Visible = false
frame.Visible = true
local frame = frame.fail
if n == 1 then
frame = frame.queue
end
frame.Visible = true
for i=1,4 do
local flare = frame.flare:Clone()
flare.Name = "flareCopy"
flare.ImageColor3 = frame.spinner.ImageColor3
flare.Parent = frame
flare.Visible = true
flare.Size = UDim2.new(1,4,1,4)
flare.Position = UDim2.new(0,-2,0.5,0)
flare.AnchorPoint = Vector2.new(0,0.5)
local x = (180 - 40*i)
local y = (14 - 2*i)
local EndPosition = UDim2.new(0,-y/2,0.5,0)
local EndSize = UDim2.new(1,x,1,y)
tween(flare,{"Position","Size","ImageTransparency"},{EndPosition, EndSize, 1},0.5*i)
end
end
local function hide()
if frame.Visible then
frame.fail.Visible = false
frame.success.Position = UDim2.new(0,0,0,0)
frame.success.Visible = true
spawn(function()
wait(10)
if frame.success.Visible then
tween(frame.success, {"Position"}, {UDim2.new(-1,-20,0,0)}, 0.5)
wait(0.5)
frame.Visible = false
end
end)
end
end
if player:FindFirstChild("DataSaveFailed") then
display(player.DataSaveFailed.Value)
end
player.ChildAdded:Connect(function(Child)
if Child.Name == "DataSaveFailed" then
display(Child.Value)
end
end)
player.ChildRemoved:Connect(function(Child)
if frame.Visible and frame.fail.Visible and player:FindFirstChild("DataSaveFailed") == nil then
hide()
end
end)
end
return module
================================================
FILE: src/StarterGui/deathScreen.lua
================================================
local module = {}
function module.show()
end
local COUNTDOWN_TIME = 60
function module.init(Modules)
local frame = script.Parent.gameUI.deathScreen
local network = Modules.network
local tween = Modules.tween
local focus = Modules.focus
local utilities = Modules.utilities
local money = Modules.money
local levels = Modules.levels
if game.Lighting:FindFirstChild("deathEffect") then
game.Lighting.deathEffect:Destroy()
end
if game.Lighting:FindFirstChild("deathBlur") then
game.Lighting.deathBlur:Destroy()
end
local function updateLabel()
local gold = network:invoke("getCacheValueByNameTag", "gold")
local exp = network:invoke("getCacheValueByNameTag", "exp")
local level = network:invoke("getCacheValueByNameTag", "level")
local expForNextLevel = levels.getEXPToNextLevel(level)
local expLost = math.min(exp, expForNextLevel*0.2)
frame.curve.exp.Text = "-" .. utilities.formatNumber(expLost) .. " XP"
money.setLabelAmount(frame.curve.cost, -gold*0.1)
end
spawn(function()
updateLabel()
end)
local pendingDeath = false
network:invoke("ambienceSetIsDead", false)
function module.accept()
if pendingDeath then
network:fireServer("deathGuiAccepted")
pendingDeath = false
end
end
function module.show()
pendingDeath = true
focus.close()
local color = Instance.new("ColorCorrectionEffect")
color.Saturation = 0
color.Name = "deathEffect"
color.Parent = game.Lighting
tween(color, {"Saturation", "Contrast"}, {-1, 0.1}, 0.1)
local blur = Instance.new("BlurEffect")
blur.Size = 0
blur.Enabled = true
blur.Name = "deathBlur"
blur.Parent = game.Lighting
tween(blur, {"Size"}, 10, 4)
network:invoke("ambienceSetIsDead", true)
delay(4, function()
if frame and script:FindFirstChild("chorus") then
script.chorus:Play()
end
frame.gradient.ImageTransparency = 1
frame.gradient.BackgroundTransparency = 1
tween(frame.gradient, {"ImageTransparency"}, 0, 1)
-- tween(frame.gradient, {"BackgroundTransparency"}, 0.4, 2)
frame.Visible = true
--[[
for i=COUNTDOWN_TIME,0,-1 do
frame.timer.Text = tostring(i)
wait(1)
end
]]
frame.curve.timer.progress.Size = UDim2.new(0,0,1,0)
tween(frame.curve.timer.progress, {"Size"}, UDim2.new(1,0,1,0), COUNTDOWN_TIME, Enum.EasingStyle.Linear)
wait(COUNTDOWN_TIME)
module.accept()
end)
end
frame.curve.respawn.Activated:connect(module.accept)
network:connect("deathGuiRequested", "OnClientEvent", module.show)
local function onDataChange(key, value)
if key == "gold" or key == "level" or key == "exp" then
updateLabel()
end
end
network:connect("propogationRequestToSelf", "Event", onDataChange)
end
return module
================================================
FILE: src/StarterGui/dialogue.lua
================================================
local module = {}
local superRootDialogueData
--[[
dialogueData {}
string id
string dialogue
table responses
[string playerResponse] =
dialogueHandler {}
instance dialogUI
instance currentSpeaker
dialogueData rootDialogueData
dialogueData currentDialogueData
dialogueData previousDialogueData
event::onPlayerDialogueProceed(string currentId)
event::onDialogueFinishShowing(string currentId)
event::onPlayerSelectResponse(string currentId, string selectionId)
method::showDialogue([string startingId])
method::setSpeaker(instance speakerModel, bool tweenCameraToSpeaker = false[, cframe cameraOffset])
local shopDialogue = dialogue:createDialog({
id = "startTalkingToShopkeeper"
dialogue = "Welcome to my shop, how may I help you?";
responses = {
["What are you selling?"] = {
dialogue = "Come take a look!";
};
["Goodbye"] = {};
};
})
--]]
local dialogueFrameUI = script.Parent.gameUI.dialogueFrame
local uiCreator
local textService = game:GetService("TextService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local itemLookup = require(replicatedStorage.itemData)
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local mapping = modules.load("mapping")
local levels = modules.load("levels")
local util = modules.load("utilities")
local questUtil = modules.load("client_quest_util")
local localization = modules.load("localization")
local acceptButtonConnection
local globalOnClose
function module.init(Modules)
local utilities = {}
utilities.network = network
utilities.utilities = util
utilities.levels = levels
utilities.quest_util = questUtil
utilities.mapping = mapping
-- ensure dialogue handler is added to modules table
uiCreator = Modules.uiCreator
local function inputUpdate()
dialogueFrameUI.UIScale.Scale = Modules.input.menuScale or 1
if Modules.input.mode.Value == "mobile" then
dialogueFrameUI.Position = UDim2.new(0.5, 0,1, -20)
else
dialogueFrameUI.Position = UDim2.new(0.5, 0,1, -140)
end
end
local responseOptionTemplate = dialogueFrameUI:WaitForChild("responseOption")
local dialogueHandler = {}
dialogueHandler.events = {}
local function getDialogueDataById(dialogData, dialogDataId, extraData)
if dialogData.id == dialogDataId then
return dialogData
end
print("$",dialogData.id,dialogDataId,extraData)
if dialogData.options then
local dialogOptions = dialogData.options
if type(dialogOptions) == "function" then
dialogOptions = dialogOptions(utilities, extraData)
end
if type(dialogOptions) == "table" and #dialogOptions > 0 then
for _, v in pairs(dialogData.options) do
local dialog = getDialogueDataById(v, dialogDataId, extraData)
if dialog then
return dialog
end
end
end
end
return nil
end
function dialogueHandler:moveToId(dialogDataId, extraData)
local newDialogueData = getDialogueDataById(superRootDialogueData, dialogDataId, extraData)
if newDialogueData then
-- stop any ongoing dialogue
dialogueHandler:stopDialogue()
-- set dialogueData to new dialogueData
dialogueHandler:setDialogue(newDialogueData, extraData)
-- show new dialogueData root
dialogueHandler:startDialogue()
end
end
network:create("dialogueMoveToId", "BindableFunction", "OnInvoke", function(id, extraData)
dialogueHandler:moveToId(id, extraData)
end)
-- update me
local function getPlayerQuestStateByQuestId(questId)
local quests = network:invoke("getCacheValueByNameTag", "quests")
for _, playerQuestData in pairs(quests.active) do
if playerQuestData.id == questId then
local objectiveSteps = 0
local objectiveStepsDone = 0
-- hacky fix for now but at least it wont break
if playerQuestData.currentObjective > #playerQuestData.objectives then
return mapping.questState.completed
end
for _, playerStepData in pairs(playerQuestData.objectives[playerQuestData.currentObjective].steps) do
objectiveSteps = objectiveSteps + 1
if playerStepData.requirement.amount <= playerStepData.completion.amount then
objectiveStepsDone = objectiveStepsDone + 1
end
end
if objectiveStepsDone > 0 and objectiveStepsDone == objectiveSteps and playerQuestData.objectives[playerQuestData.currentObjective].started then
return mapping.questState.objectiveDone
else
if playerQuestData.objectives[playerQuestData.currentObjective].started then
return mapping.questState.active
else
return mapping.questState.unassigned
end
-- return mapping.questState.active
end
end
end
for _, completePlayerQuestData in pairs(quests.completed) do
if completePlayerQuestData.id == questId then
return mapping.questState.completed
end
end
return mapping.questState.unassigned
end
local function getPlayerQuestObjectiveByQuestId(questId)
local quests = network:invoke("getCacheValueByNameTag", "quests")
for _, playerQuestData in pairs(quests.active) do
if playerQuestData.id == questId then
return playerQuestData.currentObjective
end
end
return 1
end
local function checkIfObjectiveStartedByQuestId(questId)
local quests = network:invoke("getCacheValueByNameTag", "quests")
for _, playerQuestData in pairs(quests.active) do
if playerQuestData.id == questId then
return playerQuestData.objectives[playerQuestData.currentObjective].started
--return playerQuestData.objectives[playerQuestData.currentObjective].started
end
end
return true
end
function dialogueHandler:clearEvents()
for _, v in pairs(dialogueHandler.events) do
v:disconnect()
end
dialogueHandler.events = {}
end
function dialogueHandler:navigateCurrentDialogueData(response)
local dialogue = self.CurrentDialogueData
assert(dialogue and dialogue.options and dialogue.options[response], "invalid dialogue option")
self.isPlayingDialogue = false
self:clearEvents()
self:setDialogue(dialogue.options[response])
-- update the new dialogue to match current data
self:startDialogue()
end
function dialogueHandler:stopDialogue()
self.isPlayingDialogue = false
self:clearEvents()
dialogueFrameUI.Visible = true
Modules.focus.toggle(dialogueFrameUI)
-- hide the UI
-- dialogueFrameUI.Visible = false
end
function dialogueHandler:setDialogue(dialogueData, extraData)
self.rootDialogueData = dialogueData
self.currentDialogueData = dialogueData
self.extraData = extraData
self.previousDialogueData = nil
end
function dialogueHandler:acceptQuestRewardsButtonActivated(npcName)
local success = network:invokeServer("playerRequest_submitQuest", dialogueHandler.questData.id, self.questData.objectives[self.currentQuestObjective].handerNpcName)
if success then
if dialogueHandler.questData.objectives[self.currentQuestObjective].localOnFinish then
dialogueHandler.questData.objectives[self.currentQuestObjective].localOnFinish(utilities, self.extraData)
end
utilities.utilities.playSound("questTurnedIn")
end
module.endDialogue()
Modules.interaction.stopInteract()
end
dialogueFrameUI.cancel.Activated:Connect(function()
module.endDialogue()
Modules.interaction.stopInteract()
end)
function dialogueHandler:startDialogue(dialogueNumber, questResponseType)
if not self.currentDialogueData then return end
dialogueFrameUI.UIScale.Scale = 1
if self.currentDialogueData.onClose then
globalOnClose = self.currentDialogueData.onClose
end
local body = self.currentDialogueData.Parent and self.currentDialogueData.Parent.Parent
if body and body:FindFirstChild("AnimationController") then
network:invoke("lockCameraTarget", body)
end
if self.currentDialogueData.sound and self.speaker and self.speaker.PrimaryPart and (dialogueNumber == nil or dialogueNumber <= 1) then
utilities.utilities.playSound(self.currentDialogueData.sound, self.speaker.PrimaryPart)
end
-- show the UI
local yOffset_dialogue = 30
dialogueNumber = (dialogueNumber and dialogueNumber > 1) and tostring(dialogueNumber) or ""
local trueDialogueNumber = dialogueNumber == "" and 1 or tonumber(dialogueNumber)
-- update speaker title
if self.currentDialogueData.speakerName or self.speaker then
local speakerText = (self.speaker and self.speaker.Name or self.currentDialogueData.speakerName) or "Someone messed up."
local speakerTextSize = textService:GetTextSize(speakerText, dialogueFrameUI.titleFrame.title.TextSize, dialogueFrameUI.titleFrame.title.Font, Vector2.new())
dialogueFrameUI.titleFrame.title.Text = speakerText
dialogueFrameUI.titleFrame.Size = UDim2.new(0, speakerTextSize.X + 20, 0, 32)
end
-- clear text
dialogueFrameUI.contents.dialogue:ClearAllChildren()
--for i, v in pairs(self.currentDialogueData) do
--end
-- update text
local isOnLastDialogue = true
local objective
if self.currentQuestObjective then
objective = self.currentQuestObjective
end
-- "dialogue" .. dialogueNumber .. (self.questState and "_" .. mapping.getMappingByValue("questState", self.questState) .. (questResponseType and "_" .. questResponseType or "") or "")
--"dialogue" .. dialogueNumber .. (self.questState and "_" .. mapping.getMappingByValue("questState", self.questState) .. ( (questResponseType and "_" .. questResponseType and (objective and "_" .. objective or "")) or (objective and "_" .. objective) or "") or "")
local target = "dialogue" .. dialogueNumber .. (self.questState and "_" .. mapping.getMappingByValue("questState", self.questState) .. (questResponseType and "_" .. questResponseType or "") .. (objective and "_"..objective or "") or "")
local targetDialogue = self.currentDialogueData[target]
-- CONVERT FUNCTION TO TEXT
if type(targetDialogue) == "function" then
targetDialogue = targetDialogue(utilities, self.extraData)
end
if typeof(targetDialogue) == "string" then
targetDialogue = localization.translate(targetDialogue, dialogueFrameUI.contents.dialogue)
targetDialogue = localization.convertToVesteriaDialogueTable(targetDialogue)
end
if targetDialogue then
local dialogueData = targetDialogue
-- dialogueText, yOffset
local _, yOffset = uiCreator.createTextFragmentLabels(dialogueFrameUI.contents.dialogue, dialogueData)
dialogueFrameUI.contents.dialogue.Size = UDim2.new(1, 0, 0, yOffset + 18)
yOffset_dialogue = yOffset_dialogue + yOffset + 18
if self.currentDialogueData["dialogue" .. trueDialogueNumber + 1] then
isOnLastDialogue = false
end
end
-- clear responses
dialogueFrameUI.contents.options:ClearAllChildren()
yOffset_dialogue = yOffset_dialogue + 10
dialogueFrameUI.contents.options.Position = UDim2.new(0, 0, 0, yOffset_dialogue)
local responseButtonCount = 0
local xOffset_options, yOffset_options = 0, 0
-- easy function to create responseButtons
local function createResponseButton(textToDisplay, dialogueData, questState, questData, buttonColor3, callback, curQuestObjective, isQuestAcceptB)
local responseOptionSize = textService:GetTextSize(textToDisplay, responseOptionTemplate.inner.TextSize, responseOptionTemplate.inner.Font, Vector2.new())
local responseOption = responseOptionTemplate:Clone()
if textToDisplay == "X" then
responseOptionSize = Vector2.new(10,0)
responseOption.inner.Text = "X"
responseOption.inner.Font = Enum.Font.SourceSansBold
end
if typeof(textToDisplay) == "string" then
textToDisplay = localization.translate(textToDisplay, dialogueFrameUI.contents.options)
end
if xOffset_options + (responseOptionSize.X + 20) > dialogueFrameUI.contents.options.AbsoluteSize.X then
xOffset_options = 0
yOffset_options = yOffset_options + 42
end
responseOption.inner.Text = textToDisplay
responseOption.Size = UDim2.new(0, responseOptionSize.X + 30, 0, 42)
responseOption.Position = UDim2.new(0, xOffset_options, 0, yOffset_options)
responseOption.Visible = true
responseOption.Parent = dialogueFrameUI.contents.options
local questResponseType
if dialogueData then
local objective = ""
if self.currentQuestObjective then
objective = "_"..self.currentQuestObjective
end
if dialogueData.responseButtonColor then
responseOption.ImageColor3 = dialogueData.responseButtonColor
elseif dialogueData.onSelected then
responseOption.ImageColor3 = Color3.fromRGB(255, 210, 29)
elseif textToDisplay == dialogueData["response_unassigned_accept"..objective] then
responseOption.ImageColor3 = Color3.fromRGB(150, 255, 150)
questResponseType = "accept"
elseif textToDisplay == dialogueData["response_unassigned_decline"..objective] then
responseOption.ImageColor3 = Color3.fromRGB(255, 150, 150)
questResponseType = "decline"
end
end
if buttonColor3 then
responseOption.ImageColor3 = buttonColor3
end
table.insert(self.events, callback and responseOption.Activated:connect(callback) or responseOption.Activated:connect(function()
if dialogueData then
if questState and questData and curQuestObjective then
self.questState = questState
self.questData = questData
self.currentQuestObjective = curQuestObjective
end
if dialogueData.onSelected then
dialogueData.onSelected()
end
if questResponseType == "accept" then
-- request server to add quest!
local success = network:invokeServer("playerRequest_grantQuestToPlayer", self.questData.id, self.questData.objectives[self.currentQuestObjective].giverNpcName)
if success then
network:fire("displayQuestInQuestLog", self.questData.id)
if self.questData.objectives[self.currentQuestObjective].clientOnAcceptQuest then
self.questData.objectives[self.currentQuestObjective].clientOnAcceptQuest(utilities, self.extraData)
end
end
end
-- stop any ongoing dialogue
dialogueHandler:stopDialogue()
-- set dialogueData to new dialogueData
dialogueHandler:setDialogue(dialogueData)
-- show new dialogueData root
dialogueHandler:startDialogue(nil, questResponseType)
else
if isQuestAcceptB then -- moved in here
local success = network:invokeServer("playerRequest_submitQuest", dialogueHandler.questData.id, self.questData.objectives[self.currentQuestObjective].handerNpcName)
if success then
if dialogueHandler.questData.objectives[self.currentQuestObjective].localOnFinish then
dialogueHandler.questData.objectives[self.currentQuestObjective].localOnFinish(utilities, self.extraData)
end
utilities.utilities.playSound("questTurnedIn")
end
module.endDialogue()
Modules.interaction.stopInteract()
end
module.endDialogue()
Modules.interaction.stopInteract()
end
end))
responseButtonCount = responseButtonCount + 1
xOffset_options = xOffset_options + responseOptionSize.X + 20 + 5
end
local dialogOptions = self.currentDialogueData.options
-- prevent the function from having cache behavior
if self.currentDialogueData.optionsFunction then
dialogOptions = self.currentDialogueData.optionsFunction(utilities, self.extraData)
end
if type(dialogOptions) == "function" then
self.currentDialogueData.optionsFunction = dialogOptions
dialogOptions = dialogOptions(utilities, self.extraData)
end
self.currentDialogueData.options = dialogOptions
dialogueFrameUI.bottom.Visible = false
-- update responses
-- accept rewards
if self.questState == mapping.questState.objectiveDone or self.questState == mapping.questState.handing then
-- show di!
if self.questData then
for _, obj in pairs(dialogueFrameUI.contents.rewards.contents:GetChildren()) do
if not obj:IsA("UIListLayout") then
obj:Destroy()
end
end
local curObj = self.currentQuestObjective
local rewards = self.questData.objectives[curObj].rewards
local goldMulti = self.questData.objectives[curObj].goldMulti
local expMulti = self.questData.objectives[curObj].expMulti
local level = self.questData.objectives[curObj].level
for i, inventoryTransferData_intermediate in pairs(rewards) do
local itemBaseData = itemLookup[inventoryTransferData_intermediate.id]
local amount = inventoryTransferData_intermediate.stacks
if itemBaseData then
local itemLine_quest = dialogueFrameUI.itemLine_quest:Clone()
itemLine_quest.AutoLocalize = false
local itemName = localization.translate(itemBaseData.name, dialogueFrameUI.contents.rewards)
itemLine_quest.Text = itemName .. (amount and " x"..amount or "")
itemLine_quest.preview.Image = itemBaseData.image
itemLine_quest.Parent = dialogueFrameUI.contents.rewards.contents
end
end
local expText = dialogueFrameUI.contents.rewards.chest.exp
expText.Visible = false
if (expMulti or 1) > 0 then
-- local itemLine_quest = dialogueFrameUI.itemLine_quest:Clone()
expText.Visible = true
expText.Text = "+ "..math.floor(levels.getQuestEXPFromLevel(level or 1) * (expMulti or 1)) .. " EXP"
-- itemLine_quest.preview.Image = ""
-- itemLine_quest.Parent = dialogueFrameUI.contents.rewards.contents
end
if (goldMulti or 1) > 0 then
local reward = levels.getQuestGoldFromLevel(level or 1) * (goldMulti or 1)
--[[
local itemLine_quest = dialogueFrameUI.itemLine_quest:Clone()
itemLine_quest.Text = levels.getQuestGoldFromLevel(self.questData.level or 1) * (self.questData.goldMulti or 1) .. " Gold"
itemLine_quest.preview.Image = ""
itemLine_quest.Parent = dialogueFrameUI.contents.rewards.contents
]]
local itemLine_quest = dialogueFrameUI.itemLine_money:clone()
Modules.money.setLabelAmount(itemLine_quest, reward)
itemLine_quest.Parent = dialogueFrameUI.contents.rewards.contents
end
local tYSize_rewards = 0
for i, obj in pairs(dialogueFrameUI.contents.rewards.contents:GetChildren()) do
if obj:IsA("GuiObject") and obj.Visible then
tYSize_rewards = tYSize_rewards + 29
end
end
dialogueFrameUI.contents.rewards.contents.Size = UDim2.new(1, -15, 0, tYSize_rewards)
dialogueFrameUI.contents.rewards.Size = UDim2.new(1, 0, 0, tYSize_rewards + 18 + 24)
end
dialogueFrameUI.contents.rewards.Visible = true
dialogueFrameUI.contents.taxi.Visible = false
--(textToDisplay, dialogueData, questState, questData)
createResponseButton("Accept Rewards", nil, nil, nil, Color3.fromRGB(85, 255, 76), nil, nil, true)
createResponseButton("X")
dialogueFrameUI.accept.Visible = true
if acceptButtonConnection then
acceptButtonConnection:Disconnect()
acceptButtonConnection = nil
end
-- ugly ugly hack -ber
acceptButtonConnection = dialogueFrameUI.accept.Activated:connect(function()
self:acceptQuestRewardsButtonActivated(self.questData.objectives[self.currentQuestObjective].handerNpcName)
end)
--dialogueFrameUI.cancel.Visible = true
elseif self.currentDialogueData.taxiMenu then
dialogueFrameUI.accept.Visible = false
dialogueFrameUI.cancel.Visible = false
-- createResponseButton("X")
dialogueFrameUI.contents.rewards.Visible = false
dialogueFrameUI.contents.taxi.Visible = true
else
dialogueFrameUI.accept.Visible = false
dialogueFrameUI.cancel.Visible = false
dialogueFrameUI.contents.rewards.Visible = false
dialogueFrameUI.contents.taxi.Visible = false
dialogueFrameUI.contents.rewards.contents.Size = UDim2.new(0, 0, 0, 0)
dialogueFrameUI.contents.rewards.Size = UDim2.new(0, 0, 0, 0)
if self.currentDialogueData.options then
-- iterate through all responses and create buttons
for i, dialogueData in pairs(self.currentDialogueData.options) do
local doSkipShowing = false
local isQuestDecisionPrompt = false
local textToDisplay
local questState
local questData
local currentQuestObjective
if dialogueData.questId then
questData = questLookup[dialogueData.questId]
if questData and questData.dialogueData then
dialogueData = questData.dialogueData
questState = getPlayerQuestStateByQuestId(questData.id)
currentQuestObjective = getPlayerQuestObjectiveByQuestId(questData.id)
local objectiveStarted = checkIfObjectiveStartedByQuestId(questData.id)
local objectiveName = questData.objectives[currentQuestObjective].objectiveName
-- for multiple npc handling
local flagForQuest = self.currentDialogueData.flagForQuest
if type(flagForQuest) == "function" then
flagForQuest = flagForQuest(utilities, self.extraData)
end
local currentObjectiveAndStarted = questUtil.getQuestObjectiveAndStarted(self.currentDialogueData.flagForQuest)
local canContinue = true
local objectiveChoiceTable = self.currentDialogueData.getObjectiveOptionsTable(utilities, self.extraData)
if currentObjectiveAndStarted < 0 then
if objectiveChoiceTable[currentObjectiveAndStarted *-1] == nil then
canContinue = false
end
else
if objectiveChoiceTable[currentObjectiveAndStarted] == nil then
canContinue = false
end
end
-- objective unassigned logic
if currentObjectiveAndStarted < 0 and canContinue then
local actualObjective = currentObjectiveAndStarted * -1
objectiveChoiceTable = objectiveChoiceTable[actualObjective]
for i, choice in pairs(objectiveChoiceTable) do
if choice.isStarterNPC ~= nil and not choice.isStarterNPC then
canContinue = false
end
end
elseif canContinue then
-- objective handing logic
objectiveChoiceTable = objectiveChoiceTable[currentObjectiveAndStarted]
for i, choice in pairs(objectiveChoiceTable) do
if choice.isHanderNPC ~= nil and not choice.isHanderNPC then
canContinue = false
end
end
end
if canContinue then
if questState == mapping.questState.completed then
doSkipShowing = true
elseif questState == mapping.questState.unassigned or not objectiveStarted then
textToDisplay = "[Quest] " .. objectiveName
elseif questState == mapping.questState.active then
textToDisplay = "[In-progress] " .. objectiveName
elseif questState == mapping.questState.handing or questState == mapping.questState.objectiveDone then
textToDisplay = "[Complete] " .. objectiveName
end
end
end
else
-- check if the nextObjective has been started
local forceDisplayQuest = false
local currentObjective
local fixed_response_unassinged = "response_unassigned"
local fixed_response_unassinged_accept = "response_unassigned_accept"
local fixed_response_unassinged_decline = "response_unassigned_decline"
local fixed_response_active = "response_active"
if self.questData and self.currentQuestObjective then
forceDisplayQuest = not checkIfObjectiveStartedByQuestId(self.questData.id)
currentObjective = self.currentQuestObjective
fixed_response_unassinged = fixed_response_unassinged.."_"..currentObjective
fixed_response_unassinged_accept = fixed_response_unassinged_accept.."_"..currentObjective
fixed_response_unassinged_decline = fixed_response_unassinged_decline.."_"..currentObjective
fixed_response_active = fixed_response_active.."_"..currentObjective
end
if self.questState then
if forceDisplayQuest then
if dialogueData[fixed_response_unassinged_accept] and dialogueData[fixed_response_unassinged_decline] then
isQuestDecisionPrompt = true
elseif dialogueData[fixed_response_unassinged] then
textToDisplay = dialogueData[fixed_response_unassinged]
else
doSkipShowing = true
end
elseif self.questState == mapping.questState.handing then -- obsolete
textToDisplay = dialogueData.response_handing
elseif self.questState == mapping.questState.active then
textToDisplay = dialogueData[fixed_response_active]
elseif self.questState == mapping.questState.unassigned then
if dialogueData[fixed_response_unassinged_accept] and dialogueData[fixed_response_unassinged_decline] then
isQuestDecisionPrompt = true
elseif dialogueData[fixed_response_unassinged] then
textToDisplay = dialogueData[fixed_response_unassinged]
else
doSkipShowing = true
end
elseif self.questState == mapping.questState.completed then
doSkipShowing = true
end
else
textToDisplay = dialogueData.response
end
end
if not doSkipShowing and (textToDisplay or isQuestDecisionPrompt) then
if isQuestDecisionPrompt then
local acceptText = "response_unassigned_accept"
local declineText = "response_unassigned_decline"
if self.currentQuestObjective then
acceptText = "response_unassigned_accept".."_"..self.currentQuestObjective
declineText = "response_unassigned_decline".."_"..self.currentQuestObjective
end
createResponseButton(dialogueData[acceptText], dialogueData, questState, questData, nil, nil, currentQuestObjective)
createResponseButton(dialogueData[declineText], dialogueData, questState, questData, nil, nil, currentQuestObjective)
else
createResponseButton(textToDisplay, dialogueData, questState, questData, nil, nil, currentQuestObjective)
end
end
end
end
end
dialogueFrameUI.contents.next.Visible = false
if not isOnLastDialogue then
dialogueFrameUI.contents.options.Visible = false
dialogueFrameUI.contents.next.Visible = true
dialogueFrameUI.contents.next.inner.Text = "→"
dialogueFrameUI.contents.next.tooltip.Value = "Next"
table.insert(self.events, dialogueFrameUI.contents.next.Activated:connect(function()
self:startDialogue(trueDialogueNumber + 1)
end))
elseif responseButtonCount > 0 then
dialogueFrameUI.contents.options.Visible = true
dialogueFrameUI.contents.next.Visible = false
if self.currentDialogueData.canExit and (not self.questState or (self.questState ~= mapping.questState.handing and self.questState ~= mapping.questState.objectiveDone)) then
createResponseButton("X")
end
elseif self.currentDialogueData.moveToId then
local newDialogueData = getDialogueDataById(superRootDialogueData, self.currentDialogueData.moveToId )
if newDialogueData then
dialogueFrameUI.contents.options.Visible = false
dialogueFrameUI.contents.next.Visible = true
dialogueFrameUI.contents.next.inner.Text = "→"
dialogueFrameUI.contents.next.tooltip.Value = "Next"
-- oh boy
table.insert(self.events, dialogueFrameUI.contents.next.Activated:connect(function()
-- stop any ongoing dialogue
dialogueHandler:stopDialogue()
-- set dialogueData to new dialogueData
dialogueHandler:setDialogue(newDialogueData)
-- show new dialogueData root
dialogueHandler:startDialogue(nil, questResponseType)
end))
else
dialogueFrameUI.contents.options.Visible = false
dialogueFrameUI.contents.next.Visible = true
dialogueFrameUI.contents.next.inner.Text = "X"
dialogueFrameUI.contents.next.tooltip.Value = "Exit"
table.insert(self.events, dialogueFrameUI.contents.next.Activated:connect(function()
module.endDialogue()
Modules.interaction.stopInteract()
end))
end
elseif not self.questState or self.questState ~= mapping.questState.handing then
dialogueFrameUI.contents.options.Visible = false
dialogueFrameUI.contents.next.Visible = true
dialogueFrameUI.contents.next.inner.Text = "X"
dialogueFrameUI.contents.next.tooltip.Value = "Exit"
table.insert(self.events, dialogueFrameUI.contents.next.Activated:connect(function()
module.endDialogue()
Modules.interaction.stopInteract()
end))
end
yOffset_dialogue = yOffset_dialogue + yOffset_options
-- update size!
dialogueFrameUI.contents.options.Size = UDim2.new(1, 0, 0, yOffset_options + 42)
dialogueFrameUI.bottom.Size = UDim2.new(1,0,0,yOffset_options + 52)
dialogueFrameUI.bottom.Visible = true
local tYSize = 0
for i, obj in pairs(dialogueFrameUI.contents:GetChildren()) do
if obj:IsA("GuiObject") and obj.Visible then
local size = obj.Size.Y.Offset
if size > 0 then
tYSize = tYSize + size + dialogueFrameUI.contents.UIListLayout.Padding.Offset
end
end
end
dialogueFrameUI.Size = UDim2.new(0, 400, 0, tYSize + 36)
-- dialogueFrameUI.Position = UDim2.new(0.5, 0, 1, - (150 + dialogueFrameUI.Size.Y.Scale/2))
dialogueFrameUI.Visible = false
inputUpdate()
Modules.focus.toggle(dialogueFrameUI)
if Modules.input.mode.Value == "xbox" and (game.GuiService.SelectedObject == nil or not game.GuiService.SelectedObject:IsDescendantOf(dialogueFrameUI)) then
game.GuiService.SelectedObject = Modules.focus.getBestButton(dialogueFrameUI.contents)
end
end
function dialogueHandler:setSpeaker(speaker)
-- body
self.speaker = speaker
end
function module.beginDialogue(triggerPart, dialogueData)
dialogueHandler.questState = nil
dialogueHandler.questData = nil
-- stop any ongoing dialogue
dialogueHandler:stopDialogue()
-- set dialogueData to new dialogueData
dialogueHandler:setDialogue(dialogueData)
-- set speaker if there
if triggerPart then
local rootPart = triggerPart.Parent:FindFirstChild("HumanoidRootPart")
if rootPart and rootPart.Parent:IsA("Model") then
-- network:invoke("lockCameraTarget", triggerPart.Parent.HumanoidRootPart)
local pos = rootPart.Position + rootPart.CFrame.lookVector * 3.5 + Vector3.new(0,1.75,0)
local lookat = rootPart.Position + Vector3.new(0,1.25,0)
local camCf = CFrame.new(pos, lookat)
network:invoke("lockCameraPosition",camCf--[[,0.5]])
end
dialogueHandler:setSpeaker(triggerPart.Parent)
end
superRootDialogueData = dialogueData
-- show new dialogueData root
network:invoke("setCharacterArrested", true)
dialogueHandler:startDialogue()
for i,child in pairs(dialogueFrameUI:GetDescendants()) do
if child:IsA("TextLabel") or child:IsA("TextButton") then
child.TextScaled = false
child.TextWrapped = false
end
end
--[[
dialogueFrameUI.UIScale.Scale = 0.8
Modules.tween(dialogueFrameUI.UIScale, {"Scale"}, 1, 0.5, Enum.EasingStyle.Bounce)
]]
end
network:create("beginDialogue","BindableFunction","OnInvoke",module.beginDialogue)
function module.endDialogue()
dialogueFrameUI.Visible = false
if globalOnClose then
globalOnClose(utilities, dialogueHandler.extraData)
globalOnClose = nil
end
network:invoke("lockCameraTarget", nil)
dialogueHandler:stopDialogue()
superRootDialogueData = nil
network:invoke("setCharacterArrested", false)
end
end
return module
================================================
FILE: src/StarterGui/dyePreview.lua
================================================
-- Input prompt by Locard, modified by berezaa
-- Imported from Miner's Haven
local module = {}
local runService = game:GetService("RunService")
local promptOut
local promptFrame = script.Parent.gameUI.dyePreview
local buttonCons = {}
local currentDecision
function module.forceClose()
if promptOut then
currentDecision = false
end
end
function module.isPrompting()
return promptOut
end
-- moved under module.init
function module.prompt(headerText)
end
module.PROMPT_EXPIRE_TIME = 30
function module.init(Modules)
local tween = Modules.tween
promptFrame.Visible = false
local promptQueue = {}
local currentPrompt
local function getCenterOfMassOfModel(model)
local validParts = {}
if model:FindFirstChild("manifest") then
table.insert(validParts, model.manifest)
end
for i,child in pairs(model:GetChildren()) do
if child:IsA("BasePart") then
child.Color = Color3.new(1,1,1)
child.Transparency = 0.5
end
for e,childchild in pairs(child:GetChildren()) do
if childchild:IsA("BasePart") then
table.insert(validParts, childchild)
end
end
end
local totalVotedPosition = Vector3.new()
local totalVotes = 0
for i,part in pairs(validParts) do
if part:IsA("BasePart") then
local center = part.Position
local mass = part:GetMass()
totalVotedPosition = totalVotedPosition + (center * mass)
totalVotes = totalVotes + mass
end
end
return totalVotedPosition / totalVotes
end
local function displayPrompt(prompt)
currentPrompt = prompt
promptFrame.curve.ImageColor3 = Color3.fromRGB(30, 30, 30)
promptFrame.Visible = true
promptFrame.curve.yes.Visible = true
promptFrame.curve.no.Visible = true
promptFrame.curve.Visible = true
promptFrame.curve.title.Text = prompt.text
-- promptFrame.curve.Position = UDim2.new(-1,0,0,0)
-- tween(promptFrame.curve, {"Position"}, UDim2.new(0,0,0,0), 0.6)
local itemBaseData = prompt.itemBaseData
local dyeItemData = prompt.dyeItemData
promptFrame.curve.before:ClearAllChildren()
promptFrame.curve.after:ClearAllChildren()
local module = itemBaseData.module
local container
if module:FindFirstChild("manifest") then
container = Instance.new("Model")
local primaryPart = module.manifest:Clone()
primaryPart.Parent = container
container.PrimaryPart = primaryPart
elseif module:FindFirstChild("container") then
container = module.container:Clone()
end
container.Parent = promptFrame.curve.before
local modelExtents = container:GetExtentsSize()
local centerOfMass = getCenterOfMassOfModel(container)
local camera = Instance.new("Camera")
camera.CFrame = CFrame.new( centerOfMass + modelExtents * Vector3.new(0.7,0,-0.7), centerOfMass)
camera.Parent = promptFrame.curve.before
promptFrame.curve.before.CurrentCamera = camera
local afterContainer = container:Clone()
afterContainer.Parent = promptFrame.curve.after
local itemInventorySlotData = {id = itemBaseData.id}
if dyeItemData.applyScroll(game.Players.LocalPlayer, itemInventorySlotData, true) then
local dyeColor = itemInventorySlotData.dye
if dyeColor then
for i,child in pairs(afterContainer:GetDescendants()) do
if child:IsA("BasePart") then
-- Color3.new(v.Color.r * dye.r/255, v.Color.g * dye.g/255, v.Color.b * dye.b/255)
child.Color = Color3.new(child.Color.r * dyeColor.r/255, child.Color.g * dyeColor.g/255, child.Color.b * dyeColor.b/255)
end
end
end
end
local camera = Instance.new("Camera")
camera.CFrame = CFrame.new( centerOfMass + modelExtents * Vector3.new(0.7,0,-0.7), centerOfMass)
camera.Parent = promptFrame.curve.after
promptFrame.curve.after.CurrentCamera = camera
--[[
applyScroll = function(player, itemInventorySlotData, successfullyScrolled)
local itemLookup = require(game:GetService("ReplicatedStorage"):WaitForChild("itemData"))
local itemBaseData = itemLookup[itemInventorySlotData.id]
if itemBaseData.category == "equipment" then
if successfullyScrolled then
--85, 85, 85
itemInventorySlotData.dye = {r=85, g=85, b=85}
return true
end
end
return false, "Only equipment can be dyed"
end;
--]]
end
local function addPrompt(prompt)
table.insert(promptQueue, prompt)
if #promptQueue == 1 or promptQueue[1] == prompt then
displayPrompt(prompt)
end
--[[
for i=1,3 do
local flare = promptFrame.flare:Clone()
flare.Name = "flareCopy"
flare.Parent = promptFrame
flare.Visible = true
flare.Size = UDim2.new(1,4,1,4)
flare.Position = UDim2.new(0,-2,0.5,0)
flare.AnchorPoint = Vector2.new(0,0.5)
local x = (180 - 40*i)
local y = (14 - 2*i)
local EndPosition = UDim2.new(0,-y/2,0.5,0)
local EndSize = UDim2.new(1,x,1,y)
tween(flare,{"Position","Size","ImageTransparency"},{EndPosition, EndSize, 1},0.5*i)
end
]]
end
local function makeUserResponse(userResponse)
if currentPrompt then
-- register the user's response
currentPrompt.userResponse = userResponse
-- remove the response from queue
for i,prompt in pairs(promptQueue) do
if prompt == currentPrompt then
table.remove(promptQueue, i)
break
end
end
currentPrompt = nil
-- fancy transition out
local transitionCurve = promptFrame.curve:Clone()
transitionCurve.Name = "transition"
transitionCurve.ZIndex = transitionCurve.ZIndex + 1
transitionCurve.Parent = promptFrame
tween(transitionCurve, {"Position"}, UDim2.new(-1,-10,0,0), 0.6)
game.Debris:AddItem(transitionCurve, 0.6)
spawn(function()
wait(0.6)
if not currentPrompt then
promptFrame.Visible = false
end
end)
promptFrame.curve.Visible = false
-- show the next response if applicable
local nextPrompt = promptQueue[1]
if nextPrompt then
displayPrompt(nextPrompt)
end
end
end
function module.prompt(dyeItemData, itemBaseData)
local promptStartTime = tick()
local prompt = {text = "Are you sure you want to apply "..dyeItemData.name.."?"; timestamp = promptStartTime;dyeItemData = dyeItemData; itemBaseData = itemBaseData;}
addPrompt(prompt)
repeat wait() until prompt.userResponse ~= nil or tick() - promptStartTime >= module.PROMPT_EXPIRE_TIME
-- remove the response from queue (if expired)
if tick() - promptStartTime >= module.PROMPT_EXPIRE_TIME then
if prompt == currentPrompt then
makeUserResponse(false)
else
for i,prompt in pairs(promptQueue) do
if prompt == currentPrompt then
table.remove(promptQueue, i)
break
end
end
end
end
return prompt.userResponse or false
end
promptFrame.curve.no.MouseButton1Click:Connect(function()
promptFrame.curve.ImageColor3 = Color3.fromRGB(30, 7, 8)
promptFrame.curve.yes.Visible = false
promptFrame.curve.no.Visible = false
makeUserResponse(false)
end)
promptFrame.curve.yes.MouseButton1Click:Connect(function()
promptFrame.curve.ImageColor3 = Color3.fromRGB(8, 30, 10)
promptFrame.curve.yes.Visible = false
promptFrame.curve.no.Visible = false
makeUserResponse(true)
end)
-- old stuff no one cares about:
function module.prompt_old(headerText)
if promptOut then
return false
end
promptFrame.Position = UDim2.new(0.5,0,0.5,0)
-- temp measure
local function selfIsSelected()
local obj = game:GetService("GuiService").SelectedObject
if obj then
return obj:IsDescendantOf(promptFrame)
else
return false
end
end
promptOut = true
local transitionOut
--First we initiate the prompt
promptFrame.curve.title.Text = headerText
local con0 = promptFrame.curve.no.MouseButton1Click:Connect(function()
--Modules.Menu.sounds.Click:Play()
if promptOut then
promptFrame.curve.ImageColor3 = Color3.fromRGB(30, 7, 8)
promptFrame.curve.yes.Visible = false
promptFrame.curve.no.Visible = false
currentDecision = false
end
end)
local con1 = promptFrame.curve.yes.MouseButton1Click:Connect(function()
--Modules.Menu.sounds.Click:Play()
if promptOut then
promptFrame.curve.ImageColor3 = Color3.fromRGB(8, 30, 10)
promptFrame.curve.yes.Visible = false
promptFrame.curve.no.Visible = false
currentDecision = true
end
end)
tween(promptFrame.curve, {"Position"}, UDim2.new(0,0,0,0), 0)
promptFrame.curve.ImageColor3 = Color3.fromRGB(30, 30, 30)
promptFrame.Visible = true
promptFrame.curve.yes.Visible = true
promptFrame.curve.no.Visible = true
for i=1,3 do
local flare = promptFrame.flare:Clone()
flare.Name = "flareCopy"
flare.Parent = promptFrame
flare.Visible = true
flare.Size = UDim2.new(1,4,1,4)
flare.Position = UDim2.new(0,-2,0.5,0)
flare.AnchorPoint = Vector2.new(0,0.5)
local x = (180 - 40*i)
local y = (14 - 2*i)
local EndPosition = UDim2.new(0,-y/2,0.5,0)
local EndSize = UDim2.new(1,x,1,y)
tween(flare,{"Position","Size","ImageTransparency"},{EndPosition, EndSize, 1},0.5*i)
end
--Transition the stuff into the screen
--spawn(function()
-- use my tween function nerd
--promptFrame.Absorb.Visible = true
--Modules.Menu.tween(promptFrame,{"BackgroundTransparency"}, .5, 0.7, Enum.EasingStyle.Quint)
--Modules.Menu.tween(promptFrame.InputPrompt, {"Position"}, UDim2.new(0.5,0,0.5,0), 0.7, Enum.EasingStyle.Quint)
--[[
local startT = tick()
for i = 1,60*deltaT do
if transitionOut then
break
end
local now = tick() - startT
local a = now/deltaT
local inputFrameY = quint(-.5,.5,a)
local bgTransparency = quint(1,.6,a)
promptFrame.BackgroundTransparency = bgTransparency
promptFrame.InputPrompt.Position = UDim2.new(0,0,inputFrameY,0)
runService.Heartbeat:Wait()
end
]]
--end)
--Yield the thread until an answer pops up
repeat
--[[
local Xbox = Modules.Input.mode.Value == "Xbox"
if Xbox and not selfIsSelected() then
Modules.Focus.stealFocus(promptFrame.InputPrompt)
end
]]
runService.Heartbeat:Wait()
until currentDecision ~= nil
local thisDecision = currentDecision
currentDecision = nil
--Disconnect the buttons
con0:Disconnect()
con1:Disconnect()
--make prompt ready
promptOut = false
tween(promptFrame.curve, {"Position"}, UDim2.new(-1,-10,0,0), 0.6)
spawn(function()
wait(0.6)
if not promptOut then
promptFrame.Visible = false
end
end)
-- spawn(function()
-- promptFrame.Absorb.Visible = false
-- Modules.Menu.tween(promptFrame,{"BackgroundTransparency"}, 1, 0.7, Enum.EasingStyle.Quint)
-- Modules.Menu.tween(promptFrame.InputPrompt, {"Position"}, UDim2.new(0.5,0,0,-250), 0.7, Enum.EasingStyle.Quint)
--[[
local startT = tick()
for i = 1,60*deltaT do
if promptOut then
break
end
local now = tick() - startT
local a = now/deltaT
local inputFrameY = quint(.5,-.5,a)
local bgTransparency = quint(.6,1,a)
promptFrame.BackgroundTransparency = bgTransparency
promptFrame.InputPrompt.Position = UDim2.new(0,0,inputFrameY,0)
runService.Heartbeat:Wait()
end
]]
-- end)
return thisDecision
end
end
return module
================================================
FILE: src/StarterGui/enchant.lua
================================================
--Enchantment ui by berezaa
local module = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local mapping = modules.load("mapping")
local enchantment = modules.load("enchantment")
local abilityLookup = require(replicatedStorage.abilityLookup)
function module.dragItem(inventorySlotData)
end
local currentBall
function module.init(Modules)
local frame = script.Parent.gameUI.menu_enchant
local tween = Modules.tween
local localization = Modules.localization
function module.close()
frame.Visible = false
Modules.playerMenu.closeSelected()
-- disgusting
if frame.Parent.Parent.Visible then
Modules.focus.toggle(frame.Parent.Parent)
end
end
local function emit(n, p, s)
n = n or 1
p = p or 1
for _ = 1, n do
local s = s or math.random(1,3) == 2 and math.random(2,6)/2 or 1
local sprite = frame.sprite:Clone()
local start = UDim2.new(math.random(),0,1,0)
sprite.Position = start
local lifetime = math.random(40,150)/100
sprite.Parent = frame.curve.sprites
sprite.ImageTransparency = 1
sprite.Size = UDim2.new(0,10,0,10)
sprite.Visible = true
local endPosition = UDim2.new(start.X.Scale, math.random(-50,50) * lifetime * p, start.Y.Scale, -math.random(20,200) * lifetime * p)
lifetime = lifetime * s
game.Debris:AddItem(sprite, lifetime + 0.1)
tween(sprite, {"Position"}, endPosition, lifetime)
tween(sprite, {"Size"}, UDim2.new(0,20 * s,0,20 * s), lifetime/2)
spawn(function()
wait(lifetime/2)
tween(sprite, {"Size"}, UDim2.new(0,10,0,10), lifetime/2)
end)
local transparency = math.random(30,40)/100
tween(sprite, {"ImageTransparency"}, { 1 - (1-transparency) / s^2 }, lifetime/4)
spawn(function()
wait(lifetime * 3/4)
tween(sprite, {"ImageTransparency"}, {1}, lifetime/4)
end)
end
end
spawn(function()
while wait(1) do
for i=1,15 do
delay(math.random(), function() emit() end)
end
end
end)
local enchantFrame = frame
local debounce = false
local currentAbilitySlotData
local enchantItemSlotData
local locationView
function module.reset()
for i, enchantButton in pairs(enchantFrame.enchantments.contents:GetChildren()) do
if enchantButton:IsA("GuiObject") then
enchantButton:Destroy()
end
end
tween(enchantFrame.button, {"ImageColor3"}, {Color3.fromRGB(113, 113, 113)}, 0.5)
enchantFrame.cost.Visible = false
enchantFrame.input.equipItemButton.Image = "rbxassetid://2528902611"
enchantFrame.input.equipItemButton.ImageTransparency = 0.6
enchantFrame.input.equipItemButton.ImageColor3 = Color3.new(1,1,1)
enchantFrame.input.shine.Visible = false
enchantFrame.input.frame.Visible = false
-- enchantFrame.input.ImageTransparency = 0.4
enchantFrame.output.equipItemButton.ImageTransparency = 1
enchantFrame.output.frame.Visible = false
enchantFrame.output.shine.Visible = false
enchantFrame.output.ImageTransparency = 0.4
currentAbilitySlotData = nil
enchantItemSlotData = nil
locationView = nil
end
local currentButton
local playerData
function module.open(ball)
currentBall = ball
module.reset()
Modules.playerMenu.selectMenu(frame, Modules[script.Name])
network:fire("localSignal_enchantOpened")
end
local enchantmentPairing = {}
local selectedEnchantment
local function fill(itemButton, abilitySlotData)
currentButton = itemButton
enchantmentPairing = {}
selectedEnchantment = nil
playerData = network:invoke("getLocalPlayerDataCache")
local abilityBaseData = abilityLookup[abilitySlotData.id](playerData)
-- OPTIONS ! :D
for i, enchantButton in pairs(enchantFrame.enchantments.contents:GetChildren()) do
if enchantButton:IsA("GuiObject") then
enchantButton:Destroy()
end
end
itemButton.Image = abilityBaseData.image
itemButton.ImageColor3 = Color3.new(1,1,1)
itemButton.Parent.frame.Visible = true
itemButton.Parent.shine.Visible = true
itemButton.Parent.ImageTransparency = 0
itemButton.ImageTransparency = 0
local metadata = abilityBaseData.metadata
if metadata then
-- your standard run of the mill upgrade
if metadata.upgradeCost and metadata.maxRank then
if abilitySlotData.rank < metadata.maxRank then
local enchantButton = enchantFrame.enchantments.sampleItem:Clone()
enchantButton.item.itemThumbnail.Image = abilityBaseData.image
local abilityName = abilityBaseData.name and localization.translate(abilityBaseData.name) or "???"
enchantButton.itemName.Text = abilityName .. " +" .. abilitySlotData.rank
enchantButton.locked.Visible = false
enchantButton.LayoutOrder = 1
enchantButton.Parent = enchantFrame.enchantments.contents
enchantButton.abilityPoints.amount.Text = metadata.upgradeCost .. " AP"
enchantButton.Visible = true
enchantmentPairing[enchantButton] = {id = abilitySlotData.id; request = "upgrade"}
local function selected()
if not enchantButton.locked.Visible then
network:invoke("populateItemHoverFrameWithAbility",
abilityBaseData, abilitySlotData.rank,
abilityBaseData, abilitySlotData.rank + 1
)
end
end
enchantButton.MouseEnter:connect(selected)
enchantButton.SelectionGained:connect(selected)
enchantButton.item.itemThumbnail.Active = false
local function unselected()
network:invoke("populateItemHoverFrame")
end
enchantButton.MouseLeave:connect(unselected)
enchantButton.SelectionLost:connect(unselected)
end
end
-- variants
if metadata.variants then
if abilitySlotData.variant == nil or metadata.variants[abilitySlotData.variant].default then
for variantName, variant in pairs(metadata.variants) do
if variant.cost and not variant.default then
local abilityExecutionData = {variant = variantName}
local variantBaseData = abilityLookup[abilitySlotData.id](nil, abilityExecutionData)
if variantBaseData then
local enchantButton = enchantFrame.enchantments.sampleItem:Clone()
enchantButton.item.itemThumbnail.Image = variantBaseData.image
local abilityName = variantBaseData.name and localization.translate(variantBaseData.name) or "???"
enchantButton.itemName.Text = abilityName .. (abilitySlotData.rank > 1 and (" +" .. (abilitySlotData.rank - 1)) or "")
enchantButton.abilityPoints.amount.Text = variant.cost .. " AP"
if variant.requirement and variant.requirement(playerData) then
enchantButton.locked.Visible = false
enchantButton.LayoutOrder = 3
else
enchantButton.locked.Visible = true
enchantButton.LayoutOrder = 5
end
enchantButton.Parent = enchantFrame.enchantments.contents
enchantButton.Visible = true
enchantmentPairing[enchantButton] = {id = abilitySlotData.id; request = "variant"; variant = variantName}
enchantButton.item.itemThumbnail.Active = false
local function selected()
if not enchantButton.locked.Visible then
network:invoke("populateItemHoverFrameWithAbility",
abilityBaseData, abilitySlotData.rank,
variantBaseData, abilitySlotData.rank
)
end
end
enchantButton.MouseEnter:connect(selected)
enchantButton.SelectionGained:connect(selected)
local function unselected()
network:invoke("populateItemHoverFrame")
end
enchantButton.MouseLeave:connect(unselected)
enchantButton.SelectionLost:connect(unselected)
end
end
end
end
end
end
for i, enchantButton in pairs(enchantFrame.enchantments.contents:GetChildren()) do
if enchantButton:IsA("GuiObject") then
enchantButton.Activated:connect(function()
selectedEnchantment = enchantButton
tween(enchantFrame.button, {"ImageColor3"}, {Color3.fromRGB(87, 211, 217)}, 0.5)
end)
end
end
local titleColor
--[[
if abilitySlotData then
titleColor = Modules.itemAcquistion.getTitleColorForInventorySlotData(abilitySlotData)
end
]]
titleColor = titleColor or Color3.new(1,1,1)
itemButton.Parent.frame.ImageColor3 = titleColor or Color3.fromRGB(106, 105, 107)
itemButton.Parent.shine.ImageColor3 = titleColor or Color3.fromRGB(179, 178, 185)
itemButton.Parent.shine.ImageTransparency = titleColor and 0.47 or 0.63
end
enchantFrame.button.Activated:Connect(function()
if not currentAbilitySlotData then
return false
end
if not selectedEnchantment then
return false
end
local request = enchantmentPairing[selectedEnchantment]
if not debounce then
debounce = true
local success, reason = network:invokeServer("playerRequest_enchantAbility", currentBall, request)
if success then
selectedEnchantment = nil
enchantFrame.curve.shine.ImageTransparency = 0
enchantFrame.curve.shine.ImageColor3 = Color3.fromRGB(0, 230, 255)
emit(70,1.5)
tween(enchantFrame.curve.shine, {"ImageTransparency", "ImageColor3"}, {0.5, Color3.fromRGB(14, 123, 125)}, 1)
utilities.playSound("itemEnchanted")
-- fill(currentButton, enchantItemSlotData)
-- module.dragItem(enchantItemSlotData, locationView)
-- module.reset()
tween(enchantFrame.button, {"ImageColor3"}, {Color3.fromRGB(113, 113, 113)}, 0.5)
else
warn("No enchanting allowed"..reason)
end
debounce = false
end
end)
function module.dragItem(inventorySlotData, view)
module.reset()
enchantFrame.input.equipItemButton.ImageTransparency = 0
enchantFrame.input.equipItemButton.ImageColor3 = Color3.new(1,1,1)
locationView = view
currentAbilitySlotData = inventorySlotData
fill(enchantFrame.input.equipItemButton, currentAbilitySlotData)
if enchantItemSlotData then
end
end
local function itemHover()
if currentAbilitySlotData then
playerData = network:invoke("getLocalPlayerDataCache")
local abilityBaseData = abilityLookup[currentAbilitySlotData.id](playerData)
network:invoke("populateItemHoverFrameWithAbility", abilityBaseData, currentAbilitySlotData.rank)
end
end
local function mouseLeave()
network:invoke("populateItemHoverFrame")
end
local function abilityDataUpdated(abilities)
if currentAbilitySlotData then
for i, abilitySlotData in pairs(abilities) do
if abilitySlotData.id == currentAbilitySlotData.id then
currentAbilitySlotData = abilitySlotData
fill(enchantFrame.input.equipItemButton, abilitySlotData)
break
end
end
end
end
local function onPropogationRequestToSelf(propogationNameTag, propogationData)
if propogationNameTag == "abilities" then
abilityDataUpdated(propogationData)
end
end
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
enchantFrame.input.equipItemButton.MouseEnter:connect(itemHover)
enchantFrame.input.equipItemButton.SelectionGained:connect(itemHover)
enchantFrame.input.equipItemButton.MouseLeave:connect(mouseLeave)
enchantFrame.output.equipItemButton.MouseLeave:connect(mouseLeave)
end
return module
================================================
FILE: src/StarterGui/equipment.lua
================================================
local module = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local mapping = modules.load("mapping")
local enchantment = modules.load("enchantment")
local itemData = require(replicatedStorage:WaitForChild("itemData"))
local itemAttributes = require(replicatedStorage:WaitForChild("itemAttributes"))
local menu = script.Parent.gameUI.menu_equipment
local content = menu.content
local viewport = menu.character.ViewportFrame
local player = game.Players.LocalPlayer
function module.show()
menu.Visible = not menu.Visible
end
function module.hide()
menu.Visible = false
end
menu.close.Activated:connect(module.hide)
local equipmentSlotPairing = {}
local lastSelected
function module.init(Modules)
local function getInventoryCountLookupTableByItemId()
local lookupTable = {}
local inventoryLastGot = network:invoke("getCacheValueByNameTag", "inventory")
for _, inventorySlotData in pairs(inventoryLastGot) do
if lookupTable[inventorySlotData.id] then
lookupTable[inventorySlotData.id] = lookupTable[inventorySlotData.id] + (inventorySlotData.stacks or 1)
else
lookupTable[inventorySlotData.id] = inventorySlotData.stacks or 1
end
end
return lookupTable
end
local function onInventoryItemMouseEnter(inventoryItem)
lastSelected = inventoryItem
local inventorySlotData = equipmentSlotPairing[inventoryItem]
if inventorySlotData then
local itemBaseData = itemData[inventorySlotData.id]
if itemBaseData then
network:invoke("populateItemHoverFrame", itemBaseData, "equipment", inventorySlotData)
end
end
end
local function onInventoryItemMouseLeave(inventoryItem)
if lastSelected == inventoryItem then
-- clears last selected
network:invoke("populateItemHoverFrame")
end
end
local function getEquipmentDataByEquipmentPosition(equipment, equipmentPosition)
for _, equipmentData in pairs(equipment) do
if equipmentData.position == equipmentPosition then
return equipmentData
end
end
end
local function int__updateEquipmentFrame(equipmentDataCollection)
equipmentDataCollection = equipmentDataCollection or network:invoke("getCacheValueByNameTag", "equipment")
-- reset the previous slot pairing
equipmentSlotPairing = {}
-- update current equipment slots
for _, equipmentSlotData in pairs(equipmentDataCollection) do
local positionName = mapping.getMappingByValue("equipmentPosition", equipmentSlotData.position)
if positionName and equipmentSlotData.id and content:FindFirstChild(positionName) then
local itemBaseData = itemData[equipmentSlotData.id]
local itemButton = content[positionName].equipItemButton
if itemBaseData then
itemButton.Image = itemBaseData.image
itemButton.ImageColor3 = Color3.new(1,1,1)
if equipmentSlotData.dye then
itemButton.ImageColor3 = Color3.fromRGB(equipmentSlotData.dye.r, equipmentSlotData.dye.g, equipmentSlotData.dye.b)
end
equipmentSlotPairing[itemButton] = equipmentSlotData
itemButton.Parent.frame.Visible = true
if itemButton.Parent:FindFirstChild("icon") then
itemButton.Parent.icon.Visible = false
end
itemButton.Parent.ImageTransparency = 0
-- itemButton.Parent.shadow.ImageTransparency = 0
itemButton.Parent.title.TextTransparency = 0
itemButton.Parent.title.TextStrokeTransparency = 0.2
local titleColor, itemTier
if equipmentSlotData then
titleColor, itemTier = Modules.itemAcquistion.getTitleColorForInventorySlotData(equipmentSlotData)
end
local inventoryItem = itemButton.Parent
inventoryItem.attribute.Visible = false
if equipmentSlotData.attribute then
local attributeData = itemAttributes[equipmentSlotData.attribute]
if attributeData and attributeData.color then
inventoryItem.attribute.ImageColor3 = attributeData.color
inventoryItem.attribute.Visible = true
end
end
inventoryItem.stars.Visible = false
local upgrades = equipmentSlotData.successfulUpgrades
if upgrades then
for i, child in pairs(inventoryItem.stars:GetChildren()) do
if child:IsA("ImageLabel") then
child.ImageColor3 = titleColor or Color3.new(1,1,1)
child.Visible = false
elseif child:IsA("TextLabel") then
child.TextColor3 = titleColor or Color3.new(1,1,1)
child.Visible = false
end
end
inventoryItem.stars.Visible = true
if upgrades <= 3 then
for i,star in pairs(inventoryItem.stars:GetChildren()) do
local score = tonumber(star.Name)
if score then
star.Visible = score <= upgrades
end
end
inventoryItem.stars.exact.Visible = false
else
inventoryItem.stars["1"].Visible = true
inventoryItem.stars.exact.Visible = true
inventoryItem.stars.exact.Text = upgrades
end
inventoryItem.stars.Visible = true
end
itemButton.Parent.shine.Visible = titleColor ~= nil and itemTier and itemTier > 1
Modules.fx.setFlash(itemButton.Parent.frame, itemButton.Parent.shine.Visible)
itemButton.Parent.frame.ImageColor3 = (itemTier and itemTier > 1 and titleColor) or Color3.fromRGB(106, 105, 107)
itemButton.Parent.shine.ImageColor3 = titleColor or Color3.fromRGB(179, 178, 185)
if equipmentSlotData.position == mapping.equipmentPosition.weapon then
local arrowEqpData = getEquipmentDataByEquipmentPosition(equipmentDataCollection, mapping.equipmentPosition.arrow)
local weaponEqpData = getEquipmentDataByEquipmentPosition(equipmentDataCollection, mapping.equipmentPosition.weapon)
if weaponEqpData and itemData[weaponEqpData.id].equipmentType == "bow" then
if arrowEqpData then
local lut = getInventoryCountLookupTableByItemId()
content.weapon.ammo.amount.Text = tostring(lut[arrowEqpData.id] or 0)
content.weapon.ammo.icon.Image = itemData[arrowEqpData.id].image
content.weapon.ammo.Visible = true
else
content.weapon.ammo.Visible = false
end
else
content.weapon.ammo.Visible = false
end
end
end
end
end
-- reset untouched slots to blank image
for _, equipmentSlotUI in pairs(content:GetChildren()) do
if equipmentSlotUI:FindFirstChild("frame") and not equipmentSlotPairing[equipmentSlotUI.equipItemButton] then
equipmentSlotUI.equipItemButton.Image = ""
local itemButton = equipmentSlotUI
itemButton.frame.Visible = false
itemButton.shine.Visible = false
if itemButton:FindFirstChild("icon") then
itemButton.icon.Visible = true
end
itemButton.stars.Visible = false
itemButton.attribute.Visible = false
itemButton.ImageTransparency = 0
-- itemButton.shadow.ImageTransparency = 0.4
itemButton.title.TextTransparency = 0.6
itemButton.title.TextStrokeTransparency = 0.6
end
end
if viewport:FindFirstChild("entity") then
viewport.entity:Destroy()
end
if viewport:FindFirstChild("entity2") then
viewport.entity2:Destroy()
end
local camera = viewport.CurrentCamera
if camera == nil then
camera = Instance.new("Camera")
camera.Parent = viewport
viewport.CurrentCamera = camera
end
local client = game.Players.LocalPlayer
local character = client.Character
local mask = viewport.characterMask
local characterAppearanceData = {}
characterAppearanceData.equipment = network:invoke("getCacheValueByNameTag", "equipment")
characterAppearanceData.accessories = network:invoke("getCacheValueByNameTag", "accessories")
local characterRender = network:invoke("createRenderCharacterContainerFromCharacterAppearanceData",mask, characterAppearanceData or {}, client)
characterRender.Parent = workspace.CurrentCamera
local animationController = characterRender.entity:WaitForChild("AnimationController")
local currentEquipment = network:invoke("getCurrentlyEquippedForRenderCharacter", characterRender.entity)
local weaponType do
if currentEquipment["1"] then
weaponType = currentEquipment["1"].baseData.equipmentType
end
end
local track = network:invoke("getMovementAnimationForCharacter", animationController, "idling", weaponType, nil)
if track then
spawn(function()
if characterRender then
local entity = characterRender.entity
-- entity.Parent = viewport
-- characterRender:Destroy()
local focus = CFrame.new(entity.PrimaryPart.Position + entity.PrimaryPart.CFrame.lookVector * 6.5, entity.PrimaryPart.Position) * CFrame.new(-1,0,0)
camera.CFrame = CFrame.new(focus.p + Vector3.new(0,1.5,0), entity.PrimaryPart.Position + Vector3.new(0,0.5,0))
end
if typeof(track) == "Instance" then
track:Play()
elseif typeof(track) == "table" then
for _, obj in pairs(track) do
obj:Play()
end
end
while true do
wait(0.1)
if typeof(track) == "Instance" then
if track.Length > 0 then
break
end
elseif typeof(track) == "table" then
local isGood = true
for ii, obj in pairs(track) do
if track.Length == 0 then
isGood = false
end
end
if isGood then
break
end
end
end
if characterRender then
local entity = characterRender.entity
entity.Parent = viewport
characterRender:Destroy()
--[[
local focus = CFrame.new(entity.PrimaryPart.Position + entity.PrimaryPart.CFrame.lookVector * 6.3, entity.PrimaryPart.Position) * CFrame.new(-3,0,0)
camera.CFrame = CFrame.new(focus.p + Vector3.new(0,1.5,0), entity.PrimaryPart.Position + Vector3.new(0,0.5,0))
]]
end
end)
else
local track = animationController:LoadAnimation(mask.idle)
track.Looped = true
track.Priority = Enum.AnimationPriority.Idle
track:Play()
end
-- local track = animationController:LoadAnimation(mask.idle)
-- track.Looped = true
-- track.Priority = Enum.AnimationPriority.Idle
-- track:Play()
end
local function onGetEquipmentSlotDataByEquipmentSlotUI(equipmentSlotUI)
return equipmentSlotPairing[equipmentSlotUI]
end
local levels = Modules.levels
local tween = Modules.tween
local function onPropogationRequestToSelf(propogationNameTag, propogationValue)
if propogationNameTag == "equipment" then
int__updateEquipmentFrame(propogationValue)
end
end
module.update = function(...)
int__updateEquipmentFrame(...)
end
local function main()
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:create("getEquipmentSlotDataByEquipmentSlotUI", "BindableFunction", "OnInvoke", onGetEquipmentSlotDataByEquipmentSlotUI)
for _, item in pairs(menu.content:GetChildren()) do
if item:FindFirstChild("equipItemButton") then
item.equipItemButton.MouseEnter:connect(function() onInventoryItemMouseEnter(item.equipItemButton) end)
item.equipItemButton.MouseLeave:connect(function() onInventoryItemMouseLeave(item.equipItemButton) end)
item.equipItemButton.SelectionGained:connect(function() onInventoryItemMouseEnter(item.equipItemButton) end)
item.equipItemButton.SelectionLost:connect(function() onInventoryItemMouseLeave(item.equipItemButton) end)
item.equipItemButton.Activated:connect(function()
if Modules.inventory.isEnchantingEquipment then
Modules.inventory.enchantItem(item.equipItemButton)
end
end)
end
end
end
main()
-- postInit
spawn(function()
module.update()
print(1)
network:connect("signal_isEnchantingEquipmentSet", "Event", function(value, inventorySlotData_enchantment)
for _, equipmentButton in pairs(menu.content:GetChildren()) do
if equipmentButton:FindFirstChild("equipItemButton") then
if value and inventorySlotData_enchantment then
local equipmentSlotData = equipmentSlotPairing[equipmentButton.equipItemButton]
if equipmentSlotData then
equipmentButton.blocked.Visible = false
local equipmentBaseData = itemData[equipmentSlotData.id]
local itemBaseData_enchantment = itemData[inventorySlotData_enchantment.id]
local cost = itemBaseData_enchantment.upgradeCost or 1
local max = equipmentBaseData.maxUpgrades
local canEnchant, indexToRemove = enchantment.enchantmentCanBeAppliedToItem(inventorySlotData_enchantment, equipmentSlotData)
local blocked = not canEnchant
equipmentButton.blocked.Visible = blocked
end
else
equipmentButton.blocked.Visible = false
end
end
end
end)
end)
end
return module
================================================
FILE: src/StarterGui/focus.lua
================================================
-- Focus handler by berezaa honestly kinda of an artifact but important for xbox selection
local module = {}
module.focused = nil
function module.toggle()
warn("focus.toggle not ready")
end
local function getBestButton(gui)
local hiv = Vector2.new(9999,9999)
local top
--[[
for i, child in pairs (gui:GetDescendants()) do
if child:IsA("GuiButton") and child.Visible then
local vec = child.AbsolutePosition - gui.AbsolutePosition
if vec.magnitude < hiv.magnitude then
top = child
hiv = vec
end
end
end
]]
local function checkChildren(object)
if (not object:IsA("GuiObject")) or object.Visible then
if object:IsA("GuiButton") then
local vec = object.AbsolutePosition - gui.AbsolutePosition
if vec.magnitude < hiv.magnitude then
top = object
hiv = vec
end
end
for i,child in pairs(object:GetChildren()) do
checkChildren(child)
end
end
end
checkChildren(gui)
return top
end
module.getBestButton = getBestButton
function module.init(Modules)
local network = Modules.network
local tween = Modules.tween
local depth = game.Lighting:FindFirstChild("DepthOfField")
-- local gradient = script.Parent.gradient
local function update()
local isMenuInFocus = not (module.focused == nil or module.focused.Visible == false)
network:fire("signal_menuFocusChanged", isMenuInFocus)
-- script.Parent.xboxMenuPrompt.Visible = isMenuInFocus
-- ??????????
end
function module.close()
if module.focused then
module.focused.Visible = false
module.focused = nil
-- tween(gradient, {"ImageTransparency"}, 1, 0.5)
tween(depth, {"FarIntensity", "FocusDistance", "InFocusRadius"}, {0.25, 200, 0}, 0)
end
if Modules.input.mode.Value == "xbox" then
game:GetService("GuiService").GuiNavigationEnabled = false
game:GetService("GuiService").SelectedObject = nil
pcall(function()
game:GetService("GuiService"):RemoveSelectionGroup("focus")
end)
end
update()
end
function module.cleanup()
if Modules.input.mode.Value == "xbox" then
game:GetService("GuiService").GuiNavigationEnabled = false
game:GetService("GuiService").SelectedObject = nil
pcall(function()
game:GetService("GuiService"):RemoveSelectionGroup("focus")
end)
end
end
function module.change(object)
if Modules.input.mode.Value == "xbox" then
game:GetService("GuiService").GuiNavigationEnabled = false
game:GetService("GuiService").SelectedObject = nil
pcall(function()
game:GetService("GuiService"):RemoveSelectionGroup("focus")
end)
object.Visible = true
game.GuiService.GuiNavigationEnabled = true
game.GuiService:AddSelectionParent("focus", object)
game.GuiService.SelectedObject = getBestButton(object)
-- tween(gradient, {"ImageTransparency"}, 0, 0.5)
tween(depth, {"FarIntensity", "FocusDistance", "InFocusRadius"}, {1, 4, 3}, 0)
end
end
function module.toggle(object)
Modules.input.setCurrentFocusFrame(nil)
if module.focused then
if Modules.input.mode.Value == "xbox" then
game:GetService("GuiService").GuiNavigationEnabled = false
game:GetService("GuiService").SelectedObject = nil
pcall(function()
game:GetService("GuiService"):RemoveSelectionGroup("focus")
end)
if object:IsDescendantOf(module.focused) then
module.focused.Visible = false
-- tween(gradient, {"ImageTransparency"}, 1, 0.5)
tween(depth, {"FarIntensity", "FocusDistance", "InFocusRadius"}, {0.25, 200, 0}, 0)
end
end
end
if object.Visible then
object.Visible = false
module.focused = nil
object.ZIndex = 2
-- tween(gradient, {"ImageTransparency"}, 1, 0.5)
tween(depth, {"FarIntensity", "FocusDistance", "InFocusRadius"}, {0.25, 200, 0}, 0)
else
object.Visible = true
if module.focused then
module.focused.ZIndex = 2
end
object.ZIndex = 3
module.focused = object
-- tween(gradient, {"ImageTransparency"}, 0, 0.5)
tween(depth, {"FarIntensity", "FocusDistance", "InFocusRadius"}, {1, 4, 3}, 0)
end
if module.focused then
if Modules.input.mode.Value == "xbox" then
game.GuiService:AddSelectionParent("focus", module.focused)
game.GuiService.SelectedObject = getBestButton(module.focused)
warn("$",game.GuiService.SelectedObject)
game.GuiService.GuiNavigationEnabled = true
end
end
update()
end
end
return module
================================================
FILE: src/StarterGui/fx.lua
================================================
-- module for displaying cool effects xD
-- author: the right, honorable just lord andrew alexander bereza
-- was going to make a cool "bought!" and "sold!" and "error!" display for shop
-- then i figured that might apply to more frames
-- now here we are xD
-- goal: eventually do cool stuff like fireworks here
local module = {}
function module.displayAbilityCooldown()
end
function module.statusRibbon()
end
function module.setFlash()
end
local effects = script.Parent.effects
module.colorTypes = {
success = Color3.fromRGB(97, 180, 79);
fail = Color3.fromRGB(180, 58, 60);
default = Color3.fromRGB(180,180,0);
gold = Color3.fromRGB(255, 188, 52);
}
local colorTypes = module.colorTypes
function module.init(Modules)
local tween = Modules.tween
-- oh tween module, no one gets me quite like you do
local network = Modules.network
function module.setFlash(frame, value)
if frame:FindFirstChild("flash") then
frame.flash:Destroy()
end
if value then
local flash = effects.flash:Clone()
flash.Parent = frame
flash.Visible = true
local duration = 0.6
-- duration = duration * (frame.AbsoluteSize.X/60)
local max = math.max(frame.AbsoluteSize.X, frame.AbsoluteSize.Y)
flash.ImageLabel.Size = UDim2.new(1, 16, 1, 16) --UDim2.new(0, 100 * max/60, 0, 100 * max/60)
spawn(function()
while frame.Parent and flash.Parent do
flash.ImageLabel.Position = UDim2.new(0,0,0,0)
flash.ImageLabel.ImageTransparency = 1
tween(flash.ImageLabel,{"Position"},{UDim2.new(1,0,1,0)},duration, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut)
tween(flash.ImageLabel,{"ImageTransparency"},{0}, duration/2, nil, Enum.EasingDirection.Out )
wait(duration/2)
if frame.Parent and flash.Parent then
tween(flash.ImageLabel,{"ImageTransparency"},{1},duration/2, nil, Enum.EasingDirection.In )
end
wait(4-duration)
end
end)
end
end
function module.displayAbilityCooldown(abilityFrame, cooldown)
if abilityFrame then
if abilityFrame:FindFirstChild("cooldown") then
abilityFrame.cooldown:Destroy()
end
local indicator = effects.cooldown:Clone()
indicator.Parent = abilityFrame
indicator.bar.value.Size = UDim2.new(1,0,1,0)
tween(indicator.bar.value,{"Size"}, UDim2.new(1,0,0,0), cooldown, Enum.EasingStyle.Linear)
game.Debris:AddItem(indicator, cooldown)
end
end
function module.ring(ringInfo, absolutePosition)
local ring = ringInfo or {}
local color = ring.color or Color3.new(1,1,1)
local duration = ring.duration or 0.5
local scale = ring.scale or 5
local ringObject = effects.ring:Clone()
ringObject.Parent = script.Parent.gameUI
ringObject.ImageTransparency = 0
ringObject.AnchorPoint = Vector2.new(0.5,0.5)
ringObject.Position = UDim2.new(0,absolutePosition.X,0,absolutePosition.Y - game.GuiService:GetGuiInset().Y)
ringObject.Visible = true
ringObject.ImageColor3 = color
local sizeGoal = ringObject.AbsoluteSize * scale
tween(ringObject,{"Size","ImageTransparency"},{UDim2.new(0,sizeGoal.X,0,sizeGoal.Y),1},duration)
game.Debris:AddItem(ringObject,duration)
end
network:create("displayAbilityCooldown","BindableFunction","OnInvoke",module.displayAbilityCooldown)
local endtime
function module.consumableCooldown(duration)
local durationEndTime = tick() + duration
endtime = durationEndTime
end
function module.statusRibbon(parent, text, colorType, duration, target)
colorType = colorType or "default"
local color = colorTypes[colorType]
duration = duration or 3
-- someone told me instancing is better than cloning
local ribbon = Instance.new("Frame")
ribbon.BorderSizePixel = 0
ribbon.BackgroundTransparency = 1
ribbon.ZIndex = 7
local ysize = parent.AbsoluteSize.Y
ysize = ysize > 30 and 30 or ysize
ribbon.Size = UDim2.new(1,0,0,ysize)
ribbon.Name = "fxStatusRibbon"
ribbon.ClipsDescendants = true
-- took me two tries to spell this property right
-- todo maybe make this better
target = target or UDim2.new(0,0,0,28)
ribbon.Position = target
if target.Y.Scale >= 0.49 and target.Y.Scale <= 0.51 then
ribbon.AnchorPoint = Vector2.new(0,0.5)
end
local textLabel = Instance.new("TextLabel")
textLabel.BackgroundTransparency = 1
textLabel.Size = UDim2.new(1,0,0.8,0)
textLabel.AnchorPoint = Vector2.new(0,0.5)
textLabel.Position = UDim2.new(1,0,0.5,0)
textLabel.TextColor3 = Color3.new(1,1,1)
textLabel.TextScaled = true
textLabel.Font = Enum.Font.SourceSans
textLabel.Parent = ribbon
textLabel.Text = text
ribbon.BackgroundColor3 = color
ribbon.Parent = parent
game.Debris:AddItem(ribbon, duration + 1)
-- dont ask about the fractions. I like it this way
spawn(function()
tween(ribbon,{"BackgroundTransparency"},0.1,duration * 1/8)
tween(textLabel,{"Position"},UDim2.new(0,0,0.5,0),duration * 2/8)
wait(duration * 6/8)
tween(textLabel,{"Position"},UDim2.new(-1,0,0.5,0),duration * 2/8)
wait(duration * 1/8)
tween(ribbon,{"BackgroundTransparency"},1,duration * 1/8)
wait(duration * 1/8)
if ribbon then
ribbon.Visible = false
end
end)
return ribbon
end
end
return module
================================================
FILE: src/StarterGui/guild.lua
================================================
local module = {}
-- Guild menu and local guild logic for inviting/other actions
-- Written by berezaa
local menu = script.Parent.gameUI.guild
function module.open()
menu.Visible = true
end
local httpService = game:GetService("HttpService")
local guildRankValues = {
member = 1;
officer = 2;
general = 3;
leader = 4;
}
function module.init(Modules)
local network = Modules.network
function module.open()
if not menu.Visible then
menu.UIScale.Scale = (Modules.input.menuScale or 1) * 0.75
Modules.tween(menu.UIScale, {"Scale"}, (Modules.input.menuScale or 1), 0.5, Enum.EasingStyle.Bounce)
end
Modules.focus.toggle(menu)
end
menu.close.Activated:connect(function()
module.open()
end)
module.guildData = {}
local function getGuildData(guid)
if guid == "" then
return nil
end
local guildDataFolder = game.ReplicatedStorage:FindFirstChild("guildDataFolder")
if guildDataFolder then
local guildDataValue = guildDataFolder:WaitForChild(guid, 3)
if guildDataValue then
local guildData = httpService:JSONDecode(guildDataValue.Value)
return guildData
end
end
end
local function getGuildPlayerData(player, guid)
local guildData = getGuildData(guid)
if guildData == nil then
return false, "Guild data not found."
end
local members = guildData.members
local guildPlayerData = members[tostring(player.userId)]
if guildPlayerData == nil then
return false, "Not a member of guild."
end
return guildPlayerData
end
local function updatePlayerGuildData()
local guid = game.Players.LocalPlayer.guildId.Value
local guildData = getGuildData(guid)
module.guildData = guildData
local guildPlayerData = getGuildPlayerData(game.Players.LocalPlayer, guid)
menu.Parent.inspectPlayer.content.buttons.guild.Visible = false
if guildData and guildPlayerData then
local canInvite = guildPlayerData.rank == "leader" or guildPlayerData.rank == "officer" or guildPlayerData.rank == "general"
menu.Parent.inspectPlayer.content.buttons.guild.Visible = canInvite
menu.curve.Visible = true
menu.Parent.right.buttons.openGuild.Visible = true
menu.curve.intro.title.Text = guildData.name
menu.curve.intro.notice.value.Text = guildData.notice or "There is no notice at this time."
local guildLeader = "no one!"
for userIdString, playerInfo in pairs(guildData.members) do
if playerInfo.rank == "leader" then
guildLeader = playerInfo.name
end
end
menu.curve.intro.leader.Text = "led by "..guildLeader
for i,guildMemberButton in pairs(menu.curve.side.members.list:GetChildren()) do
if guildMemberButton:IsA("GuiObject") then
guildMemberButton:Destroy()
end
end
local memberCount = 0
for memberString, memberData in pairs(guildData.members) do
local guildMemberButton = menu.curve.side.members.example:Clone()
guildMemberButton.content.username.Text = memberData.name
local level = guildMemberButton.content.level.value
level.Text = "Lvl. "..memberData.level
local xSize = game.TextService:GetTextSize(level.Text, level.TextSize, level.Font, Vector2.new()).X + 16
level.Parent.Size = UDim2.new(0, xSize, 0, 20)
local class = memberData.class
if class:lower() == "adventurer" then
guildMemberButton.content.level.emblem.Visible = false
else
guildMemberButton.content.level.emblem.Visible = true
guildMemberButton.content.level.emblem.Image = "rbxgameasset://Images/emblem_"..class:lower()
end
local guildMemberRankValue = guildRankValues[memberData.rank] or 0
guildMemberButton.LayoutOrder = 5-guildMemberRankValue
guildMemberButton.content.rank.Image = "rbxgameasset://Images/rank_"..memberData.rank:lower()
guildMemberButton.Parent = menu.curve.side.members.list
guildMemberButton.Visible = true
guildMemberButton.Name = memberString
memberCount = memberCount + 1
guildMemberButton.Activated:connect(function()
if guildMemberButton.actions.Visible then
guildMemberButton.actions.Visible = false
else
for i,otherButton in pairs(menu.curve.side.members.list:GetChildren()) do
if otherButton:IsA("GuiObject") then
otherButton.actions.Visible = false
otherButton.ZIndex = 1
end
end
guildMemberButton.actions.Visible = true
guildMemberButton.ZIndex = 2
end
end)
local exile = guildMemberButton.actions.exile
local clientRankValue = guildRankValues[getGuildPlayerData(game.Players.LocalPlayer, guid).rank]
if clientRankValue <= guildMemberRankValue then
exile.ImageColor3 = Color3.new(0.3,0.3,0.3)
exile.Active = false
else
exile.Activated:connect(function()
if exile.Active then
exile.Active = false
if Modules.prompting_Fullscreen.prompt("Are you sure you wish to EXILE "..memberData.name.." from your guild?") then
exile.icon.Visible = false
exile.fail.Visible = false
exile.success.Visible = false
local success, reason = network:invokeServer("playerRequest_exileUserIdFromGuild", tonumber(guildMemberButton.Name))
if not success then
network:fire("alert", {text = reason; textColor3 = Color3.fromRGB(255, 100, 100);})
exile.fail.Visible = true
wait(0.3)
end
end
if exile and exile.Parent then
exile.Active = true
exile.success.Visible = false
exile.fail.Visible = false
exile.icon.Visible = true
end
end
end)
end
for rankName, rankValue in pairs(guildRankValues) do
local button = guildMemberButton.actions:FindFirstChild(rankName)
if button then
if clientRankValue <= guildMemberRankValue or clientRankValue <= rankValue or rankValue == guildMemberRankValue then
button.ImageColor3 = Color3.new(0.3, 0.3, 0.3)
button.Active = false
else
local userId = tonumber(guildMemberButton.Name)
button.Activated:connect(function()
if button.Active then
button.Active = false
if Modules.prompting_Fullscreen.prompt("Are you sure you wish to rank "..memberData.name.." to "..rankName:upper().."?") then
button.icon.Visible = false
button.fail.Visible = false
button.success.Visible = false
local success, reason = network:invokeServer("playerRequest_changeUserIdRankValue", userId, rankValue)
if not success then
network:fire("alert", {text = reason; textColor3 = Color3.fromRGB(255, 100, 100);})
button.fail.Visible = true
wait(0.3)
end
end
if button and button.Parent then
button.Active = true
button.success.Visible = false
button.fail.Visible = false
button.icon.Visible = true
end
end
end)
end
end
end
end
menu.curve.side.members.title.Text = memberCount .. " " .. (memberCount == 1 and "member" or "members")
else
menu.curve.Visible = false
menu.Parent.right.buttons.openGuild.Visible = false
end
end
spawn(updatePlayerGuildData)
game.Players.LocalPlayer.guildId.Changed:connect(updatePlayerGuildData)
network:connect("signal_guildDataUpdated", "OnClientEvent", updatePlayerGuildData)
-- Invited to join a guild.
network:connect("serverPrompt_playerInvitedToServer", "OnClientInvoke", function(invitingPlayer, guid)
local guildDataFolder = game.ReplicatedStorage:FindFirstChild("guildDataFolder")
if guildDataFolder then
local guildDataValue = guildDataFolder:FindFirstChild(guid)
if guildDataValue then
local guildData = httpService:JSONDecode(guildDataValue.Value)
if guildData.name then
return Modules.prompting.prompt(invitingPlayer.Name.." has invited you to join their Guild, " .. guildData.name .. ".")
end
end
end
return false
end)
menu.curve.intro.leave.Activated:connect(function()
if Modules.prompting_Fullscreen.prompt("Are you sure you wish to LEAVE the guild?") then
local success, reason = network:invokeServer("playerRequest_leaveMyGuild", false)
if not success then
if reason == "confirmAbandon" then
if Modules.prompting_Fullscreen.prompt("Will you ABANDON your guild? It will be dissolved PERMANENTLY, and you will get NO REFUND.") then
success, reason = network:invokeServer("playerRequest_leaveMyGuild", true)
end
end
if not success then
network:fire("alert", {text = reason; textColor3 = Color3.fromRGB(255, 100, 100);})
end
end
end
end)
end
return module
================================================
FILE: src/StarterGui/hotbarHandler.lua
================================================
local HttpService = game:GetService("HttpService")
local module = {}
module.priority = 12
local hotbarFrame = script.Parent.gameUI.bottomRight.hotbarFrame
module.xboxHotbarKeys = {
{keyCode = Enum.KeyCode.ButtonX; image = "rbxassetid://2528905407"};
{keyCode = Enum.KeyCode.ButtonY; image = "rbxassetid://2528905431"};
{keyCode = Enum.KeyCode.ButtonB; image = "rbxassetid://2528905016"};
{keyCode = Enum.KeyCode.ButtonL1; image = "rbxassetid://2528905196"};
{keyCode = Enum.KeyCode.ButtonR1; image = "rbxassetid://2528905316"};
{keyCode = Enum.KeyCode.DPadUp; image = "rbxassetid://2528905167"};
{keyCode = Enum.KeyCode.DPadRight; image = "rbxassetid://2528905134"};
{keyCode = Enum.KeyCode.DPadDown; image = "rbxassetid://2528905076"};
{keyCode = Enum.KeyCode.DPadLeft; image = "rbxassetid://2528905102"};
{keyCode = Enum.KeyCode.ButtonSelect; image = "rbxassetid://2528905387"};
}
local selected = false
module.captureFocus = function()
warn("hotbarHandler.captureFocus not ready!")
end
module.releaseFocus = function()
warn("hotbarHandler.releaseFocus not ready!")
end
function module.init(Modules)
local network = Modules.network
local utilities = Modules.utilities
local mapping = Modules.mapping
local uiCreator = Modules.uiCreator
local tween = Modules.tween
local projectile = Modules.projectile
local placeSetup = Modules.placeSetup
local entitiesFolder = placeSetup.awaitPlaceFolder("entities")
-- the whild ber, in a 3 am frenzy, decides this is the best course of action to fix that one goddamn bug
--spawn(function()
local replicatedStorage = game.ReplicatedStorage
local itemData = require(replicatedStorage.itemData)
local abilityLookup = require(replicatedStorage.abilityLookup)
-- equipment, consumable, miscellaneous
local lastHotbarDataReceived = nil
local lastInventoryDataReceived = nil
local hotbarSlotPairing = {}
for _, child in pairs(hotbarFrame.content:GetChildren()) do
if child:IsA("ImageButton") then
child:Destroy()
end
end
local function getInventoryCountLookupTableByItemId()
local lookupTable = {}
for _, inventorySlotData in pairs(lastInventoryDataReceived) do
if lookupTable[inventorySlotData.id] then
lookupTable[inventorySlotData.id] = lookupTable[inventorySlotData.id] + (inventorySlotData.stacks or 1)
else
lookupTable[inventorySlotData.id] = inventorySlotData.stacks or 1
end
end
return lookupTable
end
local function abilityLevelFromAbilityData(abilityData, abilityId)
for _, ability in pairs(abilityData) do
if ability.id == abilityId then
return ability.rank
end
end
end
local hotbarButtonClicked = Instance.new("BindableEvent")
-- none of this should be in hotbarHandler
-- but here it is anyways, this is the price of victory
-- Davidii
local function doAbilityTargetingImpl(abilityId, initialKeyCode)
-- we only do this if we're on a touch screen
-- todo: local setting
if not game:GetService("UserInputService").TouchEnabled then return true end
if not abilityId then return false end
local playerData = network:invoke("getLocalPlayerDataCache")
local abilityData = abilityLookup[abilityId](playerData)
if not abilityData then return false end
local player = game.Players.LocalPlayer
if not player then return false end
local char = player.Character
if not char then return false end
local manifest = char.PrimaryPart
if not manifest then return false end
local entityContainer = network:invoke("getMyClientCharacterContainer")
if not entityContainer then return false end
local abilityRank = abilityLevelFromAbilityData(network:invoke("getCacheValueByNameTag", "abilities"), abilityId)
local statistics = Modules.ability_utilities.getAbilityStatisticsForRank(abilityData, abilityRank)
-- targeting data tells us what to do
-- if we don't have any, just say we targeted successfully
local targetingData = abilityData.targetingData
if not targetingData then
return true
end
-- some stuff can cancel targeting, this bool helps us handle that
local canceled = false
local function getTargetingValue(key)
local value = targetingData[key]
if typeof(value) == "string" then
if statistics[value] then
value = statistics[value]
elseif abilityData[value] then
value = abilityData[value]
end
elseif typeof(value) == "function" then
value = value(network:invoke("getAbilityExecutionData", abilityId))
end
return value
end
local function waitForTargetingConfirmation(updateFunction)
local cas = game:GetService("ContextActionService")
local heartbeat = game:GetService("RunService").Heartbeat
local waiting = true
local hotbarKeyCodes = {} do
for _, keyCode in pairs(network:invoke("getHotbarKeyCodes")) do
if keyCode ~= initialKeyCode then
table.insert(hotbarKeyCodes, keyCode)
end
end
end
-- sink all hotbar keys, mouse, and touch
cas:BindAction(
"confirmTargeting",
function(name, state, input)
if state ~= Enum.UserInputState.Begin then return end
waiting = false
end,
false,
Enum.UserInputType.MouseButton1,
Enum.UserInputType.Touch,
unpack(hotbarKeyCodes)
)
cas:BindAction(
"cancelTargeting",
function(name, state, input)
if state ~= Enum.UserInputState.Begin then return end
canceled = true
waiting = false
end,
false,
-- default to a non-existent keycode if we don't have one
initialKeyCode or Enum.KeyCode.Unknown
)
local hotbarButtonClickedConn
hotbarButtonClickedConn = hotbarButtonClicked.Event:Connect(function()
canceled = true
waiting = false
hotbarButtonClickedConn:Disconnect()
end)
print("WEAREATTHEWAITINGLOOP")
while waiting do
updateFunction(network:invoke("getAbilityExecutionData", abilityId))
heartbeat:Wait()
end
cas:UnbindAction("confirmTargeting")
cas:UnbindAction("cancelTargeting")
hotbarButtonClickedConn:Disconnect()
end
local function createTargetingPart()
local part = Instance.new("Part")
part.Anchored = true
part.CanCollide = false
part.TopSurface = Enum.SurfaceType.Smooth
part.BottomSurface = Enum.SurfaceType.Smooth
part.Material = Enum.Material.ForceField
part.Color = Color3.new(0.75, 0.75, 0.75)
part.Transparency = 0.5
part.CastShadow = false
return part
end
local customFunctionData, customFunctionGuid
if targetingData.onStarted then
customFunctionGuid = HttpService:GenerateGUID()
local abilityExecutionData = network:invoke("getAbilityExecutionData", abilityId)
customFunctionData = targetingData.onStarted(
entityContainer,
abilityExecutionData
)
network:fireServer("abilityTargetingSequenceStarted", abilityId, customFunctionGuid, manifest, abilityExecutionData)
end
----
-- direct sphere
-- a sphere of radius radius at maximum range range
-- no projectile involved
----
if targetingData.targetingType == "directSphere" then
local range = getTargetingValue("range")
local sphere = createTargetingPart()
sphere.Shape = Enum.PartType.Ball
sphere.Size = Vector3.new(2, 2, 2) * getTargetingValue("radius")
sphere.Parent = entitiesFolder
waitForTargetingConfirmation(function(executionData)
local there = executionData["mouse-target-position"]
local here = manifest.Position
local delta = there - here
local distance = delta.Magnitude
if distance > range then
there = here + (delta / distance) * range
end
sphere.Position = there
end)
sphere:Destroy()
----
-- direct cylinder
-- like sphere except we show a cylinder instead
-- revolutionary i know
----
elseif targetingData.targetingType == "directCylinder" then
local range = getTargetingValue("range")
local cylinder = createTargetingPart()
cylinder.Shape = Enum.PartType.Cylinder
cylinder.Size = Vector3.new(2, 2, 2) * getTargetingValue("radius")
cylinder.Parent = entitiesFolder
waitForTargetingConfirmation(function(executionData)
local there = executionData["mouse-target-position"]
local here = manifest.Position
local delta = there - here
local distance = delta.Magnitude
if distance > range then
there = here + (delta / distance) * range
end
cylinder.CFrame = CFrame.new(there) * CFrame.Angles(0, 0, math.pi / 2)
end)
cylinder:Destroy()
----
-- line
-- an area of a certain length and width
-- issues forth from the character
-- kinda specific? but hey
----
elseif targetingData.targetingType == "line" then
local width = getTargetingValue("width")
local length = getTargetingValue("length")
local box = createTargetingPart()
box.Size = Vector3.new(width, width, length)
box.Parent = entitiesFolder
waitForTargetingConfirmation(function(executionData)
local here = manifest.Position
local there = executionData["mouse-target-position"]
local delta = (there - here) * Vector3.new(1, 0, 1)
box.CFrame = CFrame.new(here, here + delta) * CFrame.new(0, 0, -length / 2)
end)
box:Destroy()
----
-- projectile
-- a projectile with a certain gravity and speed
-- single target, no area of effect
----
elseif targetingData.targetingType == "projectile" then
local entity = entityContainer:FindFirstChild("entity")
if not entity then return false end
local projectionPart =
(customFunctionData and customFunctionData.projectionPart) or
entity:FindFirstChild("RightHand")
if not projectionPart then return false end
local speed = getTargetingValue("projectileSpeed")
local gravity = getTargetingValue("projectileGravity")
local function getPosition()
if projectionPart:IsA("Attachment") then
return projectionPart.WorldPosition
else
return projectionPart.Position
end
end
local function getDirection(targetPosition)
return projectile.getUnitVelocityToImpact_predictive(getPosition(), speed, targetPosition, Vector3.new(), gravity)
end
local beam, attach0, attach1 = projectile.showProjectilePath(getPosition(), Vector3.new(), 3, gravity)
waitForTargetingConfirmation(function(executionData)
local direction = getDirection(executionData["mouse-target-position"])
if direction then
projectile.updateProjectilePath(beam, attach0, attach1, getPosition(), direction * speed, 3, gravity)
end
end)
beam:Destroy()
attach0:Destroy()
attach1:Destroy()
end
if targetingData.onEnded then
local abilityExecutionData = network:invoke("getAbilityExecutionData", abilityId)
targetingData.onEnded(
entityContainer,
abilityExecutionData,
customFunctionData
)
network:fireServer("abilityTargetingSequenceEnded", abilityId, customFunctionGuid, manifest, abilityExecutionData)
end
if canceled then
return false
end
return true
end
local abilityTargetingActive = false
local function doAbilityTargeting(...)
if abilityTargetingActive then return false end
-- swap the above line for this for behavior that
-- simply ensures thread safety instead of cancelling
-- superfluous requests:
-- while abilityTargetingActive do wait() end
abilityTargetingActive = true
local retVals = {doAbilityTargetingImpl(...)}
abilityTargetingActive = false
return unpack(retVals)
end
local hotbarButtonDebounce = true
local function onHotbarButtonDoubleClicked(hotbarButtonItem, ...)
print("hotbar double clicked")
print(hotbarButtonDebounce)
if not hotbarButtonDebounce then return end
hotbarButtonDebounce = false
delay(0.05, function()
hotbarButtonDebounce = true
end)
-- let other parts of the code know that another button's been clicked
hotbarButtonClicked:Fire()
local hotbarSlotData = hotbarSlotPairing[hotbarButtonItem]
if hotbarSlotData then
local correspondingBG = hotbarFrame.decor:FindFirstChild(hotbarButtonItem.Name:gsub("hotbarButton",""))
if correspondingBG then
correspondingBG.ImageColor3 = Color3.fromRGB(0, 255, 255)
tween(correspondingBG,{"ImageColor3"},Color3.fromRGB(72, 72, 76),0.5)
end
if hotbarSlotData.dataType == mapping.dataType.item then
local inventorySlotData = network:invoke("getInventorySlotDataWithItemId", hotbarSlotData.id)
if inventorySlotData then
network:invoke("activateItemRequestLocal", inventorySlotData)
end
elseif hotbarSlotData.dataType == mapping.dataType.ability then
network:invoke("abilityUseRequest", hotbarSlotData.id)
end
end
end
local cooldownEndTime
local function showConsumableCooldown(duration)
local durationEndTime = tick() + duration
cooldownEndTime = durationEndTime
for hotbarButtonItem, hotbarSlotData in pairs(hotbarSlotPairing) do
if hotbarSlotData and hotbarSlotData.dataType == mapping.dataType.item then
if hotbarButtonItem:FindFirstChild("cooldown") then
hotbarButtonItem.cooldown.Visible = true
end
end
end
spawn(function()
wait(duration)
if cooldownEndTime == durationEndTime then
for hotbarButtonItem, hotbarSlotData in pairs(hotbarSlotPairing) do
if hotbarSlotData and hotbarSlotData.dataType == mapping.dataType.item then
if hotbarButtonItem:FindFirstChild("cooldown") then
hotbarButtonItem.cooldown.Visible = false
end
end
end
end
end)
end
network:create("showConsumableCooldown", "BindableFunction", "OnInvoke", showConsumableCooldown)
local buttons = {}
local function onGetHotbarSlotDataByHotbarSlotUI(hotbarSlotUI)
if hotbarSlotUI then
return hotbarSlotPairing[hotbarSlotUI]
end
return nil
end
hotbarFrame.xboxBinding.Visible = false
hotbarFrame.xboxBindPrompt.Visible = false
local function getHotbarSlotDataByHotbarSlotPosition(hotbarSlotPosition)
if not lastHotbarDataReceived then return end
for _, hotbarSlotData in pairs(lastHotbarDataReceived) do
if hotbarSlotData.position == hotbarSlotPosition then
return hotbarSlotData
end
end
return nil
end
local valueCache = {}
-- function to set a property, remembering the original value of the property
-- if called with nil value, restores to default value
local function setValue(object, property, value)
valueCache[object] = valueCache[object] or {}
if valueCache[object][property] == nil then
valueCache[object][property] = object[property]
end
if value then
object[property] = value
else
object[property] = valueCache[object][property]
end
end
local function setValues(valueTable)
for _, entry in pairs(valueTable) do
setValue(entry[1], entry[2], entry[3])
end
end
local function inputUpdate()
hotbarFrame.selectedDecor.Visible = Modules.input.mode.Value == "xbox"
if Modules.input.mode.Value == "mobile" then
setValues({
{hotbarFrame.Parent.statusBars, "Position", UDim2.new(0.25,0,1,-70)};
{hotbarFrame.Parent.Parent.statusEffects, "Position", UDim2.new(0,5,1,-110)};
{hotbarFrame.content, "Position", UDim2.new(1,-220,1,-216)};
{hotbarFrame.content, "Size", UDim2.new(0,200,0,190)};
{hotbarFrame.decor, "Position", UDim2.new(1,-220,1,-220)};
{hotbarFrame.decor, "Size", UDim2.new(0,200,0,200)};
{hotbarFrame, "Size", UDim2.new(1.2,0,0,60)};
{hotbarFrame.decor.UIGridLayout, "StartCorner", "BottomRight"};
{hotbarFrame.content.UIGridLayout, "StartCorner", "BottomRight"};
})
else
setValues({
{hotbarFrame.Parent.statusBars, "Position"};
{hotbarFrame.Parent.Parent.statusEffects, "Position"};
{hotbarFrame.content, "Position"};
{hotbarFrame.content, "Size"};
{hotbarFrame.decor, "Position"};
{hotbarFrame.decor, "Size"};
{hotbarFrame, "Size"};
{hotbarFrame.decor.UIGridLayout, "StartCorner"};
{hotbarFrame.content.UIGridLayout, "StartCorner"};
})
end
end
Modules.input.mode.changed:connect(inputUpdate)
inputUpdate()
hotbarFrame.selectedDecor.ImageTransparency = 1
for _, decor in pairs(hotbarFrame.selectedDecor:GetChildren()) do
decor.ImageTransparency = 1
end
module.focused = false
local lastButtonFrom
local promptButtonFrom
function module.showSelectionPrompt(buttonFrom)
promptButtonFrom = buttonFrom
hotbarFrame.xboxBindPrompt.contents.itemIcon.Image = buttonFrom.Image
hotbarFrame.xboxBindPrompt.Visible = true
end
function module.hideSelectionPrompt(buttonFrom)
if promptButtonFrom == buttonFrom then
hotbarFrame.xboxBindPrompt.Visible = false
end
end
local lastFocus
module.captureFocus = function()
if not module.focused then
module.focused = true
hotbarFrame.xboxBinding.Visible = false
for _, hotbarButton in pairs(hotbarFrame.content:GetChildren()) do
if hotbarButton:FindFirstChild("xboxPrompt") then
hotbarButton.xboxPrompt.Visible = true
end
end
local selectedObject = game.GuiService.SelectedObject
if selectedObject and selectedObject:FindFirstChild("bindable") then
lastButtonFrom = selectedObject
hotbarFrame.xboxBinding.contents.curveBG.itemIcon.Image = selectedObject.Image
hotbarFrame.xboxBinding.Visible = true
lastFocus = Modules.focus.focused
game.GuiService.GuiNavigationEnabled = false
for _, hotbarButton in pairs(hotbarFrame.content:GetChildren()) do
if hotbarButton:FindFirstChild("xboxPrompt") then
hotbarButton.xboxPrompt.Visible = true
end
end
else
lastButtonFrom = nil
tween(hotbarFrame.selectedDecor,{"ImageTransparency"},0,0.3)
for _, decor in pairs(hotbarFrame.selectedDecor:GetChildren()) do
local val = tonumber(decor.Name)
if val then
tween(decor,{"ImageTransparency"},val,1-val)
end
end
end
--hotbarFrame.gamepadPrompt.close.Visible = true
--hotbarFrame.gamepadPrompt.open.Visible = false
end
end
-- modules.uiCreator.processSwap(buttonFrom, buttonTo, isRightClickTrigger)
--[[
module.promptNewBinding = function(buttonFrom)
if not module.focused then
module.focused = true
lastButtonFrom = buttonFrom
hotbarFrame.xboxBinding.Visible = true
for i,hotbarButton in pairs(hotbarFrame.content:GetChildren()) do
if hotbarButton:FindFirstChild("xboxPrompt") then
hotbarButton.xboxPrompt.Visible = true
end
end
end
end
]]
module.releaseFocus = function(input)
local endFocus = input == nil or input.KeyCode == Enum.KeyCode.ButtonL2
if input then
for _, hotbarButton in pairs(hotbarFrame.content:GetChildren()) do
if hotbarButton:FindFirstChild("xboxKeyCode") then
if input.KeyCode.Name == hotbarButton.xboxKeyCode.Value then
if hotbarFrame.xboxBinding.Visible and lastButtonFrom then
Modules.uiCreator.processSwap(lastButtonFrom, hotbarButton, false)
endFocus = true
else
onHotbarButtonDoubleClicked(hotbarButton)
end
end
end
end
end
if endFocus then
hotbarFrame.xboxBinding.Visible = false
if lastFocus == Modules.focus.focused or game.GuiService.GuiNavigationEnabled == lastButtonFrom then
game.GuiService.GuiNavigationEnabled = true
end
for _, hotbarButton in pairs(hotbarFrame.content:GetChildren()) do
if hotbarButton:FindFirstChild("xboxPrompt") then
hotbarButton.xboxPrompt.Visible = false
end
end
if module.focused then
tween(hotbarFrame.selectedDecor,{"ImageTransparency"},1,0.3)
for _, decor in pairs(hotbarFrame.selectedDecor:GetChildren()) do
local val = tonumber(decor.Name)
if val then
tween(decor,{"ImageTransparency"},1,1-val)
end
end
end
--hotbarFrame.gamepadPrompt.close.Visible = false
--hotbarFrame.gamepadPrompt.open.Visible = true
module.focused = false
end
end
network:create("getHotbarSlotPairing", "BindableFunction", "OnInvoke", function()
local pairing = {}
for i=1,10 do
local button = hotbarFrame.content:FindFirstChild("hotbarButton"..i)
if button then
table.insert(pairing, {button = button; data = hotbarSlotPairing[button]})
end
end
return pairing
end)
local hotbarButtonConnections = {}
local function updateButtonItem(hotbarButtonItem, i, flareEffect)
local trans_i = i % 10
if hotbarButtonConnections[i] then
-- name, connection
for _, connection in pairs(hotbarButtonConnections[i]) do
connection:Disconnect()
end
else
hotbarButtonConnections[i] = {}
end
local buttonConnections = hotbarButtonConnections[i]
local hotbarSlotData = getHotbarSlotDataByHotbarSlotPosition(trans_i)
hotbarButtonItem.LayoutOrder = i
hotbarButtonItem.Name = "hotbarButton"..i
hotbarButtonItem.ImageTransparency = 1
hotbarButtonItem.Image = ""
-- xbox juiciness
local xboxInput = module.xboxHotbarKeys[i]
hotbarButtonItem.xboxPrompt.key.Image = xboxInput.image
hotbarButtonItem.xboxKeyCode.Value = xboxInput.keyCode.Name
hotbarButtonItem.duplicateCount.Visible = false
hotbarButtonItem.level.Visible = false
local inventoryCountLookupTable = getInventoryCountLookupTableByItemId()
for _, child in pairs(hotbarButtonItem:GetChildren()) do
if child:FindFirstChild("keyCode") then
child.ImageTransparency = 0
child.backdrop.ImageTransparency = 0
child.keyCode.TextTransparency = 0
child.keyCode.TextStrokeTransparency = 0
-- child.shadow.ImageTransparency = 0
end
end
if hotbarSlotData then
hotbarSlotPairing[hotbarButtonItem] = hotbarSlotData
local correspondingBG = hotbarFrame.decor:FindFirstChild(hotbarButtonItem.Name:gsub("hotbarButton",""))
if correspondingBG then
correspondingBG.ImageTransparency = 0
end
if hotbarSlotData.dataType == mapping.dataType.item then
hotbarButtonItem.duplicateCount.Text = tostring(inventoryCountLookupTable[hotbarSlotData.id] or 0)
local itemBaseData = itemData[hotbarSlotData.id]
if itemBaseData then
hotbarButtonItem.Image = itemBaseData.image
hotbarButtonItem.inputKey.Text = "[" .. tostring(hotbarSlotData.keyCode or trans_i) .. "]"
hotbarButtonItem.keyCode.Value = tostring(hotbarSlotData.keyCode or trans_i)
uiCreator.setIsDoubleClickFrame(hotbarButtonItem, 0.2, onHotbarButtonDoubleClicked)
-- add item tooltip
buttonConnections.hovered = hotbarButtonItem.MouseEnter:connect(function()
network:invoke("populateItemHoverFrame", itemBaseData, "equipment", hotbarSlotData)
end)
buttonConnections.unhovered = hotbarButtonItem.MouseLeave:connect(function()
network:invoke("populateItemHoverFrame")
end)
table.insert(buttons, hotbarButtonItem)
end
hotbarButtonItem.duplicateCount.Visible = true
hotbarButtonItem.ImageTransparency = 0
elseif hotbarSlotData.dataType == mapping.dataType.ability then
--JAYY LOOK HERE ABILIT STUFF
local abilityData = network:invoke("getCacheValueByNameTag", "abilities")
local level = abilityLevelFromAbilityData(abilityData, hotbarSlotData.id)
local playerData = network:invoke("getLocalPlayerDataCache")
local abilityBaseData = abilityLookup[hotbarSlotData.id]
if abilityBaseData then
local textSize = game.TextService:GetTextSize(hotbarButtonItem.level.Text, hotbarButtonItem.level.TextSize, hotbarButtonItem.level.Font, Vector2.new())
hotbarButtonItem.level.Size = UDim2.new(0, textSize.X + 2, 0, textSize.Y - 2)
-- hotbarButtonItem.level.Visible = true
--hotbarButtonItem.level.Text = utilities.romanNumerals[level]
--local tier = statistics.tier or 1
--hotbarButtonItem.level.TextColor3 = Modules.itemAcquistion.tierColors[tier]
hotbarButtonItem.Image = abilityBaseData.image
hotbarButtonItem.inputKey.Text = "[" .. tostring(hotbarSlotData.keyCode or trans_i) .. "]"
hotbarButtonItem.keyCode.Value = tostring(hotbarSlotData.keyCode or trans_i)
uiCreator.setIsDoubleClickFrame(hotbarButtonItem, 0.2, onHotbarButtonDoubleClicked)
table.insert(buttons, hotbarButtonItem)
end
hotbarButtonItem.duplicateCount.Visible = false
hotbarButtonItem.ImageTransparency = 0
end
else
for _, child in pairs(hotbarButtonItem:GetChildren()) do
if child:FindFirstChild("keyCode") then
child.ImageTransparency = 1
child.backdrop.ImageTransparency = 1
child.keyCode.TextTransparency = 1
child.keyCode.TextStrokeTransparency = 1
-- child.shadow.ImageTransparency = 1
end
end
local correspondingBG = hotbarFrame.decor:FindFirstChild(hotbarButtonItem.Name:gsub("hotbarButton",""))
if correspondingBG then
correspondingBG.ImageTransparency = 0.5
-- if correspondingBG:FindFirstChild("UIGradient") then
-- correspondingBG.UIGradient:Destroy()
-- end
-- correspondingBG.shadow.ImageTransparency = 0.5
end
end
end
local function updateHotbar(updateHotbarData)
if updateHotbarData then
lastHotbarDataReceived = updateHotbarData
end
if lastHotbarDataReceived then
hotbarSlotPairing = {}
for i = 1, 10 do
local hotbarButtonItem = hotbarFrame.content:FindFirstChild("hotbarButton"..i) or hotbarFrame.hotbarButton:Clone()
hotbarButtonItem.Name = "hotbarButton"..i
hotbarButtonItem.Parent = hotbarFrame.content
hotbarButtonItem.Visible = true
updateButtonItem(hotbarButtonItem, i)
uiCreator.drag.setIsDragDropFrame(hotbarButtonItem)
end
end
end
local Rand = Random.new()
local oldxp = 0
local function setup()
local value = network:invoke("getCacheValueByNameTag", "exp")
local level = network:invoke("getCacheValueByNameTag", "level")
local xp = value
local needed = math.floor(Modules.levels.getEXPToNextLevel(level))
oldxp = value
hotbarFrame.Frame.xp.title.Text = "XP: " .. utilities.formatNumber(xp) .. "/" .. utilities.formatNumber(needed)
hotbarFrame.Frame.xp.value.Size = UDim2.new(xp/needed,0,1,0)
hotbarFrame.Frame.xp.instant.Size = hotbarFrame.Frame.xp.value.Size
--[[
local gold = network:invoke("getCacheValueByNameTag", "gold")
hotbarFrame.header.gold.Text = "$"..gold
]]
end
setup()
hotbarFrame.Frame.xp.MouseEnter:connect(function()
hotbarFrame.Frame.xp.title.Visible = true
selected = true
end)
hotbarFrame.Frame.xp.MouseLeave:connect(function()
hotbarFrame.Frame.xp.title.Visible = false
selected = false
end)
local lastStats
local function onPropogationRequestToSelf(propogationNameTag, propogationData)
if propogationNameTag == "hotbar" then
updateHotbar(propogationData)
elseif propogationNameTag == "inventory" then
lastInventoryDataReceived = propogationData
updateHotbar()
elseif propogationNameTag == "abilities" then
updateHotbar()
elseif propogationNameTag == "nonSerializeData" then
local stats = propogationData.statistics_final
if lastStats == nil then
lastStats = stats
updateHotbar()
else
for stat, value in pairs(lastStats) do
if stats[stat] ~= value then
lastStats = stats
updateHotbar()
break
end
end
end
elseif propogationNameTag == "exp" then
local key = propogationNameTag
local value = propogationData
local level = network:invoke("getCacheValueByNameTag", "level")
local xp = value
local needed = math.floor(Modules.levels.getEXPToNextLevel(level))
hotbarFrame.Frame.xp.title.Text = "XP: " .. utilities.formatNumber(xp) .. "/" .. utilities.formatNumber(needed)
-- ahh crying internally
local change = xp - oldxp
local notice = hotbarFrame.Frame.noticeTemplate:Clone()
notice.Name = "Notice"
notice.TextTransparency = 1
notice.TextStrokeTransparency = 1
notice.Parent = hotbarFrame.Frame
notice.Visible = true
notice.Text = "+"..utilities.formatNumber(change).." EXP"
notice.Position = UDim2.new(0.5,Rand:NextInteger(-50,50),0,Rand:NextInteger(-30,40))
Modules.tween(notice,{"Position"},notice.Position + UDim2.new(0,0,0,-100),3)
Modules.tween(notice,{"TextTransparency","TextStrokeTransparency"},{0,0.7},1.5)
game.Debris:AddItem(notice, 10)
local sampleParticle = hotbarFrame.Frame.xp.value.value.tip.sample
for _ = 1, 6 do
local particle = sampleParticle:Clone()
particle.Rotation = math.random(1,90)
particle.Parent = sampleParticle.Parent
particle.Visible = true
tween(particle,{"Rotation", "Position", "Size", "BackgroundTransparency"},
{particle.Rotation + math.random(100,200), UDim2.new(0, math.random(3,25),0.5,math.random(-20,20)), UDim2.new(0,16,0,16), 1},math.random(60,130)/100)
game.Debris:AddItem(particle,1.5)
end
if xp < oldxp then
Modules.tween(hotbarFrame.Frame.xp.value,{"Size"},{UDim2.new(1,0,1,0)},0.5)
hotbarFrame.Frame.xp.instant.Size = UDim2.new(1,0,1,0)
spawn(function()
wait(0.25)
hotbarFrame.Frame.xp.value.Size = UDim2.new(1,0,0,0)
local goal = UDim2.new(xp/needed,0,1,0)
Modules.tween(hotbarFrame.Frame.xp.value,{"Size"},{goal},0.5)
hotbarFrame.Frame.xp.instant.Size = goal
end)
else
Modules.tween(hotbarFrame.Frame.xp.value,{"Size"},{UDim2.new(xp/needed,0,1,0)},1)
hotbarFrame.Frame.xp.instant.Size = UDim2.new(xp/needed,0,1,0)
end
hotbarFrame.Frame.xp.Visible = true
oldxp = value
spawn(function()
wait(0.5)
Modules.tween(notice,{"TextTransparency","TextStrokeTransparency"},{1,1},1.5)
wait(3)
if oldxp == value and not selected then
hotbarFrame.Frame.xp.Visible = false
end
end)
end
end
local function main()
lastInventoryDataReceived = network:invoke("getCacheValueByNameTag", "inventory")
local hotbarBinds = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"}
-- setup input
for i=1,10 do
local ei = i % 10
network:invoke("addInputAction","hotbarButton"..i,function(inputObject)
local button = hotbarFrame.content:FindFirstChild("hotbarButton"..i)
if button then
local hotbarSlotData = hotbarSlotPairing[button]
if hotbarSlotData then
hotbarSlotData.keyCode = inputObject.KeyCode
end
onHotbarButtonDoubleClicked(button)
end
end, hotbarBinds[i],10)
end
updateHotbar(network:invoke("getCacheValueByNameTag", "hotbar"))
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:create("getHotbarSlotDataByHotbarSlotUI", "BindableFunction", "OnInvoke", onGetHotbarSlotDataByHotbarSlotUI)
end
main()
module.releaseFocus()
-- end)
end
return module
================================================
FILE: src/StarterGui/input.lua
================================================
-- centralized input dohikey
-- berezaa
-- TO ADD AN ACTION TO KEYBIND:
-- network:invoke("addInputAction", UNIQUE_ACTION_NAME, FUNCTION_TO_CALL, DEFAULT_KEYBIND)
-- Note: for DEFAULT_KEYBIND, use a shortcut from module.shortcuts, not the Enum.KeyCode.Name
local USI = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local module = {}
local keybinds = {}
local inputObjects = {}
local actions = {}
module.actions = actions
local gameUi = script.Parent.gameUI
local mode = script.Parent.mode
module.mode = mode
-- track all gui objects that belong to a specific (or multiple) platforms
local platformSpecificGuiObjects = {}
module.shortcuts = {
Unknown = "???";
-- Numbers
One = "1"; KeypadOne = "1"; Two = "2"; KeypadTwo = "2";
Three = "3"; KeypadThree = "3"; Four = "4"; KeypadFour = "4";
Five = "5"; KeypadFive = "5"; Six = "6"; KeypadSix = "6";
Seven = "7"; KeypadSeven = "7"; Eight = "8"; KeypadEight = "8";
Nine = "9"; KeypadNine = "9"; Zero = "0"; KeypadZero = "0";
-- Everything else
Backspace = "BkS"; Clear = "Clr"; Return = "Rtn"; Hash = "#"; Dollar = "$"; Percent = "%";
Ampersand = "&"; Quote = "\""; LeftParenthesis = "("; RightParenthesis = ")"; Asterisk = "*"; Plus = "+";
Comma = ","; Minus = "-"; Period = "."; Slash = "/"; Colon = ":"; Semicolon = ";";
LessThan = "<"; GreaterThan = ">"; Question = "?"; At = "@"; LeftBracket = "["; RightBracket = "]";
Caret = "^"; Underscore = "_"; Backquote = "`"; LeftCurly = "{"; RightCurly = "}"; Pipe = "|";
Tilde = "~"; Delete = "Del"; Insert = "Ins"; Home = "Hm"; PageUp = "PgUp"; PageDown = "PgDn";
NumLock = "NmLk"; CapsLock = "CpLk"; ScrollLock = "ScLk"; RightShift = "Rshft"; LeftShift = "Lshft"; RightControl = "Rctrl";
LeftControl = "Lctrl"; RightAlt = "Ralt"; LeftAlt = "Lalt"; RightMeta = "Rmta"; LeftMeta = "Lmta"; RightSuper = "Rspr";
LeftSuper = "Lspr"; Break = "Brk"; Power = "Pwr";
}
local shortcuts = module.shortcuts
local setupComplete
local settings
local function preferencesUpdated()
if setupComplete then
for _, inputObject in pairs(inputObjects) do
local actionName = inputObject.Name
local action = actions[actionName]
if inputObject and inputObject:IsA("GuiObject") and inputObject:FindFirstChild("keyCode") then
if action and action.bindedTo then
inputObject.keyCode.Text = action.bindedTo
else
inputObject.keyCode.Text = " "
end
end
end
if settings then
settings.refreshKeybinds()
end
end
end
function module.addAction(name, target, default, priority)
priority = priority or 5
local action = {["target"] = target; ["default"] = default; ["priority"] = priority;}
-- check for an existing binding
for input,actionName in pairs(keybinds) do
if name == actionName then
action["bindedTo"] = input
action["priority"] = priority
-- check for an inputObject
preferencesUpdated()
break
end
end
actions[name] = action
end
local add = module.addAction
local function addInputObject(object)
if object:IsA("GuiObject") and object:IsDescendantOf(game.Players.LocalPlayer) then
local actionName = object.Parent.Name
object.Name = actionName
if object:IsA("ImageLabel") or object:IsA("ImageButton") then
local colorValue = Instance.new("Color3Value")
colorValue.Name = "originalColor"
colorValue.Value = object.ImageColor3
colorValue.Parent = object
end
table.insert(inputObjects,object)
--inputObjects[actionName] = object
-- check for an existing action
local action = actions[actionName]
if object:FindFirstChild("keyCode") then
if action and action.bindedTo then
object.keyCode.Text = action.bindedTo
else
object.keyCode.Text = " "
end
end
end
end
for _, object in pairs(game.CollectionService:GetTagged("inputObject")) do
addInputObject(object)
end
game.CollectionService:GetInstanceAddedSignal("inputObject"):connect(addInputObject)
local network
spawn(function()
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
network = modules.load("network")
network:create("addInputAction","BindableFunction","OnInvoke",add)
end)
module.menuButtons = {}
for _, button in pairs(gameUi.right.buttons:GetChildren()) do
if button:IsA("GuiButton") then
module.menuButtons[button.Name] = button
end
end
-- make all buttons play a cute click sound
local function buttonSetup(button)
if button:IsA("GuiButton") then
button.MouseButton1Click:connect(function()
local clickSound = Instance.new("Sound")
clickSound.Name = "clickSound"
clickSound.SoundId = "rbxassetid://997701190"
clickSound.Parent = button
clickSound.Volume = 0.1
clickSound:Play()
game.Debris:AddItem(clickSound,3)
end)
end
end
for _, guiButton in pairs(gameUi:GetDescendants()) do
buttonSetup(guiButton)
end
gameUi.DescendantAdded:connect(function(guiButton)
buttonSetup(guiButton)
end)
local currentFocusFrame
-- do something cool
function module.setCurrentFocusFrame(focusFrame)
if currentFocusFrame and currentFocusFrame ~= focusFrame then
currentFocusFrame.Visible = false
end
currentFocusFrame = focusFrame
end
local function updateModeDisplay()
end
module.menuScale = 1
local buttonsFrame = gameUi.right.buttons
function module.init(Modules)
local tween = Modules.tween
local control = Modules.control
local network = Modules.network
local currentlySelectedButtonTooltip
local function processGuiObject(guiObject)
if guiObject:IsA("GuiObject") then
if (guiObject:FindFirstChild("xbox") or guiObject:FindFirstChild("pc") or guiObject:FindFirstChild("mobile")) then
table.insert(platformSpecificGuiObjects, guiObject)
end
if guiObject:FindFirstChild("bindable") then
guiObject.SelectionGained:connect(function()
Modules.hotbarHandler.showSelectionPrompt(guiObject)
end)
guiObject.SelectionLost:connect(function()
Modules.hotbarHandler.hideSelectionPrompt(guiObject)
end)
end
-- "populateItemHoverFrameWithTextData"
if guiObject:FindFirstChild("tooltip") then
guiObject.MouseEnter:connect(function()
currentlySelectedButtonTooltip = guiObject
-- if guiObject.Active then
network:invoke("populateItemHoverFrameWithTextData", {text = guiObject.tooltip.Value; source = guiObject})
-- end
end)
guiObject.MouseLeave:connect(function()
if currentlySelectedButtonTooltip == guiObject then
currentlySelectedButtonTooltip = nil
end
network:invoke("populateItemHoverFrameWithTextData", {source = guiObject})
end)
guiObject.tooltip.Changed:connect(function()
if currentlySelectedButtonTooltip == guiObject then
network:invoke("populateItemHoverFrameWithTextData", {text = guiObject.tooltip.Value; source = guiObject})
end
end)
end
if guiObject:IsA("ImageButton") and (guiObject.Parent == buttonsFrame or guiObject.Image == "rbxassetid://29202694692" or guiObject.Image == "rbxassetid://2920343923" or guiObject.Image == "rbxassetid://3437374574shadow" or guiObject.Image == "rbxassetid://3437374574" or guiObject.Image == "rbxassetid://3437766345" ) then
local function selectionGained()
if guiObject.Active then
if guiObject.Parent == buttonsFrame then
-- tween(guiObject.icon.UIScale, {"Scale"}, 1.2, 0.3)
-- tween(guiObject.icon, {"ImageTransparency"}, 0, 0.3)
else
--[[
local selection = guiObject:FindFirstChild("selectionGlow")
if selection == nil then
if guiObject.Image == "rbxassetid://3437374574shadow" or guiObject.Image == "rbxassetid://3437766345" then
selection = script.selectionGlow_new:Clone()
selection.Name = "selectionGlow"
else
selection = script.selectionGlow:Clone()
end
selection.Parent = guiObject
if guiObject.Parent == buttonsFrame then
selection.ImageColor3 = guiObject.icon.ImageColor3
end
end
tween(selection, {"ImageTransparency"}, 0.45, 0.3)
]]
end
end
end
local function selectionLost()
if guiObject.Parent == buttonsFrame then
-- tween(guiObject.icon.UIScale, {"Scale"}, 1, 0.3)
-- tween(guiObject.icon, {"ImageTransparency"}, 1, 0.3)
else
local selection = guiObject:FindFirstChild("selectionGlow")
if selection then
if guiObject.Image == "rbxassetid://3437766345" or "rbxassetid://3445513431" then
tween(selection, {"ImageTransparency"}, 1, 0)
else
tween(selection, {"ImageTransparency"}, 1, 0.2)
end
end
end
end
local function activated(inputObject)
if inputObject.UserInputType == Enum.UserInputType.MouseButton1 or inputObject.UserInputType == Enum.UserInputType.Gamepad1 or inputObject.UserInputType == Enum.UserInputType.Touch then
if guiObject.Active then
selectionLost()
if guiObject.Image == "rbxassetid://3437766345" then
guiObject.Image = "rbxassetid://3445513431"
local padding
local existingPadding = guiObject:FindFirstChild("UIPadding")
if existingPadding == nil then
padding = script.Parent.effects.clickPadding:Clone()
padding.Parent = guiObject
else
existingPadding.PaddingTop = UDim.new(existingPadding.PaddingTop.Scale, existingPadding.PaddingTop.Offset + 4)
end
repeat wait() until inputObject.UserInputState == Enum.UserInputState.Cancel or inputObject.UserInputState == Enum.UserInputState.End
wait(0.1)
guiObject.Image = "rbxassetid://3437766345"
if padding then
padding:Destroy()
elseif existingPadding then
existingPadding.PaddingTop = UDim.new(existingPadding.PaddingTop.Scale, existingPadding.PaddingTop.Offset - 4)
end
elseif guiObject.Image ~= "rbxassetid://3445513431" then
-- old buttons
local dark = guiObject:FindFirstChild("activationDark")
if dark == nil then
dark = script.Parent.effects.activationDark:Clone()
dark.Parent = guiObject
end
tween(dark, {"ImageTransparency"}, 0.5, 0.15)
spawn(function()
wait(0.2)
tween(dark, {"ImageTransparency"}, 1, 0.3)
end)
end
end
end
end
guiObject.MouseEnter:connect(selectionGained)
guiObject.SelectionGained:connect(selectionGained)
guiObject.MouseLeave:connect(selectionLost)
guiObject.SelectionLost:connect(selectionLost)
guiObject.InputBegan:connect(activated)
end
end
end
for _, guiObject in pairs(gameUi.Parent:GetDescendants()) do
processGuiObject(guiObject)
end
script.Parent.DescendantAdded:connect(processGuiObject)
local function setInputObjectsVisible(visible)
for _, inputObject in pairs(inputObjects) do
if inputObject then
inputObject.Visible = visible
end
end
end
-- display relevant information for that input mode
function updateModeDisplay()
setInputObjectsVisible(mode.Value == "pc")
for i,guiObject in pairs(platformSpecificGuiObjects) do
guiObject.Visible = guiObject:FindFirstChild(mode.Value) ~= nil
end
if mode.Value == "mobile" then
module.menuScale = 0.7
gameUi.leftBar.UIScale.Scale = 0.65
-- gameUi.leftBar.Position = UDim2.new(0, 5,1, -50)
gameUi.bottomRight.UIScale.Scale = 0.65
gameUi.bottomRight.Size = UDim2.new(1, 0,1.625, 0)
if gameUi.bottomRight.hotbarFrame.content:FindFirstChild("hotbarButton10") then
gameUi.bottomRight.hotbarFrame.content:FindFirstChild("hotbarButton10").Visible = false
end
if gameUi.bottomRight.hotbarFrame.decor:FindFirstChild("10") then
gameUi.bottomRight.hotbarFrame.decor:FindFirstChild("10").Visible = false
end
gameUi.bottomRight.AnchorPoint = Vector2.new(1,1)
gameUi.bottomRight.Position = UDim2.new(1,0,1,0)
else
module.menuScale = 1
gameUi.leftBar.UIScale.Scale = 1
gameUi.leftBar.Size = UDim2.new(0, 100,1, -250)
--- gameUi.leftBar.Position = UDim2.new(0, 5,1, -110)
-- gameUi.leftBar.Position = UDim2.new(0, 5,1, -200)
gameUi.bottomRight.UIScale.Scale = 1
gameUi.bottomRight.Size = UDim2.new(1, 0, 1, 0)
gameUi.bottomRight.AnchorPoint = Vector2.new(0.5,1)
gameUi.bottomRight.Position = UDim2.new(0.5,0,1,0)
gameUi.bottomRight.Size = UDim2.new(1, 0, 1, 0)
if gameUi.bottomRight.hotbarFrame.content:FindFirstChild("hotbarButton10") then
gameUi.bottomRight.hotbarFrame.content:FindFirstChild("hotbarButton10").Visible = true
end
if gameUi.bottomRight.hotbarFrame.decor:FindFirstChild("10") then
gameUi.bottomRight.hotbarFrame.decor:FindFirstChild("10").Visible = true
end
if mode.Value == "xbox" or game.GuiService:IsTenFootInterface() then
module.menuScale = 1.2
gameUi.bottomRight.UIScale.Scale = 1.2
end
end
network:fireServer("signal_inputChanged", mode.Value)
end
if USI.TouchEnabled and not USI.MouseEnabled then
mode.Value = "mobile"
end
mode.Changed:connect(updateModeDisplay)
updateModeDisplay()
-- old postInit
spawn(function()
local remapping = false
game.GuiService.AutoSelectGuiEnabled = false
game.GuiService.CoreGuiNavigationEnabled = false
local function changeKeybindAction(keybind, actionName)
-- Make sure the action is valid
local action = actions[actionName]
if action == nil then
return warn("Action",actionName,"not found")
end
if not setupComplete then
return warn("Input module not set up yet")
end
if remapping then
return false
end
remapping = true
-- Ask before overriding an existing action
local existingBindActionName = keybinds[keybind]
local existingAction
if existingBindActionName then
existingAction = actions[existingBindActionName]
if existingAction then
if not Modules.prompting_Fullscreen.prompt("This will override an existing action ("..existingBindActionName.."). Are you sure?") then
remapping = false
return false
end
end
end
-- Execute order 66
local success = network:invokeServer("playerRequestSetKeyAction",keybind,actionName)
if success then
local oldkeybind = action.bindedTo
if oldkeybind then
keybinds[oldkeybind] = nil
end
keybinds[keybind] = actionName
action.bindedTo = keybind
if existingAction then
existingAction.bindedTo = nil
end
preferencesUpdated()
remapping = false
return true
else
remapping = false
warn("Server rejected keybind change")
end
end
local function setup()
local userSettings = network:invoke("getCacheValueByNameTag", "userSettings")
keybinds = {}
if userSettings and userSettings.keybinds then
keybinds = userSettings.keybinds
end
for key,actionName in pairs(keybinds) do
local action = actions[actionName]
if action then
action.bindedTo = key
end
end
-- apply default keys (but don't override!)
for actionName,action in pairs(actions) do
local default = action.default
if default and action["bindedTo"] == nil and keybinds[default] == nil then
-- apply keybind (no need to ping the server)
keybinds[default] = actionName
action["bindedTo"] = default
end
end
setupComplete = true
preferencesUpdated()
updateModeDisplay()
end
--local hotbarBinds = {"1", "2", "Q", "E", "R", "G", "V", "3", "4", "5"}
-- add hard-coded actions
add("openEquipment",Modules.equipment.show,"Q",3)
add("openInventory",Modules.inventory.show,"E",3)
add("openAbilities",Modules.abilities.show,"R",3)
add("openSettings",Modules.settings.show,"G",3)
-- add("openQuestLog",Modules.questLog.open,"L",3)
-- add("openMonsterBook",Modules.monsterBook.open,"B",3)
-- add("openGuild",Modules.guild.open,"P",3)
add("interact",Modules.interaction.interact,"C",4)
-- add("cameraLock",function() network:invoke("toggleCameraLock") end, "Tab", 4)
add("emote1",function() network:invoke("playerRequest_performEmote", "dance") end, "N", 7)
add("emote2",function() network:invoke("playerRequest_performEmote", "sit") end, "M", 7)
add("swapWeapons", function()
if not network:invoke("getIsPlayerCastingAbility") then
network:fireServer("playerRequest_swapWeapons")
end
end, "`", 8)
--add("defaultPoint",function() network:invoke("playerRequest_performEmote", "point") end, "P", 7)
USI.InputChanged:connect(function(input, absorbed)
if input.UserInputType == Enum.UserInputType.Keyboard or input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.MouseButton2 then
mode.Value = "pc"
elseif input.UserInputType == Enum.UserInputType.Gamepad1 then
mode.Value = "xbox"
elseif input.UserInputType == Enum.UserInputType.Touch then
mode.Value = "mobile"
end
if mode.Value ~= "mobile" then
-- network:invoke("setMobileMovementDirection", nil)
network:fire("mobileMovementDirectionChanged", nil)
network:fire("mobileCameraRotationChanged", nil)
end
end)
--mobile stuff
local touchJoystick = gameUi:WaitForChild("touchJoystick")
touchJoystick.Visible = false
local touchJoystickActive = false
local touchJoystickDirection
local cameraMovementActive = false
USI.TouchStarted:connect(function(touch, processed)
if not processed then
local startPos = touch.Position
if startPos.x < 300 and startPos.y > workspace.CurrentCamera.ViewportSize.y * 0.6 then
if not touchJoystickActive then
touchJoystick.Position = UDim2.new(0, startPos.x, 0, startPos.y)
touchJoystickActive = true
touchJoystick.Visible = true
-- while the same finger is still down
while touch.UserInputState ~= Enum.UserInputState.End and touch.UserInputState ~= Enum.UserInputState.Cancel do
local pos = touch.Position
local difference = pos - startPos
control.doSprint(difference.magnitude > 80)
if difference.magnitude > 35 then
difference = difference.unit * 35
end
touchJoystick.stick.Position = UDim2.new(0.5, difference.X, 0.5, difference.Y)
-- network:invoke("setMobileMovementDirection", difference.unit)
network:fire("mobileMovementDirectionChanged", difference.unit)
RunService.RenderStepped:wait()
end
-- network:invoke("setMobileMovementDirection", Vector2.new())
network:fire("mobileMovementDirectionChanged", nil)
control.doSprint(false)
touchJoystickActive = false
touchJoystick.Visible = false
end
else
if not cameraMovementActive then
cameraMovementActive = true
while touch.UserInputState ~= Enum.UserInputState.End and touch.UserInputState ~= Enum.UserInputState.Cancel do
local pos = touch.Position
local difference = pos - startPos
network:fire("mobileCameraRotationChanged", difference * 0.3)
startPos = pos
RunService.RenderStepped:wait()
end
cameraMovementActive = false
network:fire("mobileCameraRotationChanged", Vector2.new())
end
end
end
end)
USI.InputEnded:connect(function(input, absorbed)
if input.KeyCode == Enum.KeyCode.ButtonL2 and Modules.hotbarHandler.focused then
Modules.hotbarHandler.releaseFocus(input)
end
end)
USI.InputBegan:connect(function(input, absorbed)
if input.UserInputType == Enum.UserInputType.Keyboard or input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.MouseButton2 then
mode.Value = "pc"
elseif input.UserInputType == Enum.UserInputType.Gamepad1 then
mode.Value = "xbox"
elseif input.UserInputType == Enum.UserInputType.Touch then
mode.Value = "mobile"
end
if not absorbed then
if mode.Value == "xbox" then
if Modules.hotbarHandler.focused then
Modules.hotbarHandler.releaseFocus(input)
else
if input.KeyCode == Enum.KeyCode.ButtonB then
if Modules.interaction.currentInteraction then
Modules.interaction.stopInteract()
else
Modules.focus.close()
end
game.GuiService.SelectedObject = nil
print("$ nil a")
elseif input.KeyCode == Enum.KeyCode.ButtonX then
if Modules.itemAcquistion.closestItem then
Modules.itemAcquistion.pickupInputGained(input)
else
Modules.interaction.interact()
end
elseif input.KeyCode == Enum.KeyCode.ButtonY then
if game.GuiService.SelectedObject and game.GuiService.SelectedObject:FindFirstChild("bindable") then
else
-- Modules.playerMenu.open()
end
elseif input.KeyCode == Enum.KeyCode.ButtonSelect then
Modules.settings.open()
-- elseif input.KeyCode == Enum.KeyCode.DPadRight then
-- Modules.skillBooks.open()
elseif input.KeyCode == Enum.KeyCode.ButtonL2 then
Modules.hotbarHandler.captureFocus()
end
end
elseif mode.Value == "pc" then
local key = shortcuts[input.KeyCode.Name] or input.KeyCode.Name
-- remapping active
local remapTarget = Modules.settings.remapTarget
if remapTarget then
local actionName = Modules.settings.remapTarget.Name
local action = actions[actionName]
if action then
local success = changeKeybindAction(key, actionName)
if success then
end
return false
end
end
local actionName = keybinds[key]
if actionName then
local action = actions[actionName]
if action and (not action.active) and type(action.target) == "function" then
action.active = true
-- cool visual effect
for i,inputObject in pairs(inputObjects) do
if inputObject.Name == actionName then
if inputObject and inputObject:IsA("ImageLabel") then
local color = Color3.fromRGB(0, 255, 255)
inputObject.ImageColor3 = color
if inputObject:FindFirstChild("keyCode") then
inputObject.keyCode.TextColor3 = color
end
end
end
end
action.target(input) -- pass the input object
wait()
action.active = false
wait(0.15)
-- reset visual effect
if not action.active then
for i,inputObject in pairs(inputObjects) do
if inputObject.Name == actionName then
if inputObject and inputObject:IsA("ImageLabel") then
local color = inputObject:FindFirstChild("originalColor") and inputObject.originalColor.Value or Color3.fromRGB(40, 40, 40)
inputObject.ImageColor3 = color
if inputObject:FindFirstChild("keyCode") then
inputObject.keyCode.TextColor3 = Color3.new(1,1,1)
end
end
end
end
end
end
end
end
end
end)
for i,button in pairs(buttonsFrame:GetChildren()) do
if button:IsA("GuiButton") then
button.MouseButton1Click:connect(function()
local action = actions[button.Name]
if action and action.target then
action.target()
end
end)
end
end
end)
end
return module
================================================
FILE: src/StarterGui/inspectPlayer.lua
================================================
-- player interaction - inspect page
-- berezaa
local module = {}
local activePlayer
local ui = script.Parent.gameUI.inspectPlayer
local slotData = {}
function module.init(Modules)
local tween = Modules.tween
local network = Modules.network
local configuration = Modules.configuration
local replicatedStorage = game:GetService("ReplicatedStorage")
local itemLookup = require(replicatedStorage:WaitForChild("itemData"))
local itemAttributes = require(replicatedStorage:WaitForChild("itemAttributes"))
local slots = {}
local currentPartyInfo
local function updatePartyInfo(partyInfo)
if partyInfo == nil then
partyInfo = network:invokeServer("playerRequest_getMyPartyData")
end
if partyInfo then
for _, partyMemberInfo in pairs(partyInfo.members) do
local player = partyMemberInfo.player
if player == game.Players.LocalPlayer then
partyInfo.isClientPartyLeader = partyMemberInfo.isLeader
end
end
end
currentPartyInfo = partyInfo
end
local function isPlayerInParty(player)
if currentPartyInfo and currentPartyInfo.members then
for _, entry in pairs(currentPartyInfo.members) do
if entry.player == player then
return true
end
end
end
end
network:connect("signal_myPartyDataChanged", "OnClientEvent", function(partyInfo)
updatePartyInfo(partyInfo)
if ui.Visible and activePlayer then
module.open(activePlayer, true)
end
end)
spawn(function()
updatePartyInfo()
end)
for _, slot in pairs(ui.content.equipment:GetChildren()) do
if slot:IsA("ImageButton") or slot:IsA("ImageLabel") then
slot.item.Image = ""
slot.frame.Visible = false
slot.shine.Visible = false
slot.ImageTransparency = 0.5
slot.LayoutOrder = 99
slotData[slot] = {}
table.insert(slots, slot)
local function show()
network:invoke("populateItemHoverFrame", itemLookup[slotData[slot].id], "inspect", slotData[slot])
end
local function hide()
network:invoke("populateItemHoverFrame")
end
slot.item.MouseEnter:connect(show)
slot.item.SelectionGained:connect(show)
slot.item.MouseLeave:connect(hide)
slot.item.SelectionLost:connect(hide)
end
end
function module.close()
if ui.Visible then
Modules.focus.toggle(ui)
end
network:invoke("populateItemHoverFrame")
ui.Visible = false
activePlayer = nil
end
ui.close.Activated:connect(module.close)
function module.open(player, isUpdate)
if ui.Visible and player == activePlayer and not isUpdate then
module.close()
return
end
for i,button in pairs(ui.content.buttons:GetChildren()) do
if button:FindFirstChild("fail") then
button.fail.Visible = false
end
if button:FindFirstChild("success") then
button.success.Visible = false
end
if button:FindFirstChild("icon") then
button.icon.Visible = true
end
end
local partyButton = ui.content.buttons:FindFirstChild("request party")
local isPartyMember = isPlayerInParty(player)
if isPartyMember then
if player == game.Players.LocalPlayer then
if currentPartyInfo then
partyButton.ImageColor3 = Color3.fromRGB(247, 0, 4)
partyButton.tooltip.Value = "Leave Party"
partyButton.Active = true
else
partyButton.ImageColor3 = Color3.fromRGB(95, 95, 95)
partyButton.tooltip.Value = "Not in Party"
partyButton.Active = false
end
elseif currentPartyInfo and currentPartyInfo.isClientPartyLeader then
partyButton.ImageColor3 = Color3.fromRGB(247, 0, 4)
partyButton.tooltip.Value = "Kick from Party"
partyButton.Active = true
else
partyButton.ImageColor3 = Color3.fromRGB(95, 95, 95)
partyButton.tooltip.Value = "Already in Party"
partyButton.Active = false
end
else
partyButton.ImageColor3 = Color3.fromRGB(80, 247, 222)
partyButton.tooltip.Value = "Invite to Party"
partyButton.Active = true
end
if not ui.Visible then
Modules.focus.toggle(ui)
end
ui.UIScale.Scale = (Modules.input.menuScale or 1) * 0.75
Modules.tween(ui.UIScale, {"Scale"}, (Modules.input.menuScale or 1), 0.5, Enum.EasingStyle.Bounce)
activePlayer = player
ui.content.info.username.Text = player.Name
local class = player:FindFirstChild("class") and player.class.Value:lower() or "unknown"
local emblemVisible
if class:lower() ~= "adventurer" then
ui.content.info.username.emblem.Image = "rbxgameasset://Images/emblem_"..class:lower()
ui.content.info.username.emblem.Visible = true
emblemVisible = true
else
ui.content.info.username.emblem.Visible = false
end
for i,statText in pairs(ui.content.stats:GetChildren()) do
if statText:IsA("TextLabel") then
local stat = player:FindFirstChild(statText.Name)
if stat then
statText.Text = statText.Name:upper()..": "..tostring(stat.Value)
else
statText.Text = statText.Name:upper()..": ???"
end
local textBounds = game:GetService("TextService"):GetTextSize(statText.Text, statText.TextSize, statText.Font, Vector2.new()).X
statText.Size = UDim2.new(0, textBounds + 5, 1, 0)
end
end
local level = player:FindFirstChild("level") and player.level.Value or 0
ui.content.level.Text = "Lvl. ".. level
local label = ui.content.info.level.value
label.Text = "Lvl. "..level
local xSize = game.TextService:GetTextSize(label.Text, label.TextSize, label.Font, Vector2.new()).X + 16
ui.content.info.level.Size = UDim2.new(0, xSize, 0, 26)
local referrals = player:FindFirstChild("referrals") and player.referrals.Value or 0
if referrals > 0 then
local label = ui.content.info.referrals.value
label.Text = tostring(referrals)
local xSize = game.TextService:GetTextSize(label.Text, label.TextSize, label.Font, Vector2.new()).X + 41
ui.content.info.referrals.Size = UDim2.new(0, xSize, 0, 26)
ui.content.info.referrals.Visible = true
else
ui.content.info.referrals.Visible = false
end
local extend = (emblemVisible and 22) or 0
local textSize = game:GetService("TextService"):GetTextSize(player.Name, ui.content.info.username.TextSize, ui.content.info.username.Font, Vector2.new()).X
ui.content.info.username.Size = UDim2.new(0, textSize + 5 + (extend), 0, 30)
for i,slot in pairs(slots) do
slot.item.Image = ""
slot.frame.Visible = false
slot.shine.Visible = false
slot.ImageTransparency = 0.5
slot.LayoutOrder = 99
slot.stars.Visible = false
slot.attribute.Visible = false
Modules.fx.setFlash(slot.frame, false)
end
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart:FindFirstChild("appearance") then
local data = game:GetService("HttpService"):JSONDecode(player.Character.PrimaryPart.appearance.Value)
if data then
if data.equipment then
for i, equipment in pairs(data.equipment) do
local slot = slots[i]
local realItem = itemLookup[equipment.id]
slot.stars.Visible = false
slot.attribute.Visible = false
if realItem then
slot.item.Image = realItem.image
slot.item.ImageColor3 = Color3.new(1,1,1)
slot.frame.Visible = true
slot.shine.Visible = true
slot.ImageTransparency = 0
if equipment.attribute then
local attributeData = itemAttributes[equipment.attribute]
if attributeData and attributeData.color then
slot.attribute.ImageColor3 = attributeData.color
slot.attribute.Visible = true
end
end
if equipment.dye then
slot.item.ImageColor3 = Color3.fromRGB(equipment.dye.r, equipment.dye.g, equipment.dye.b)
end
local titleColor, itemTier = Modules.itemAcquistion.getTitleColorForInventorySlotData(equipment)
slot.frame.ImageColor3 = (itemTier and itemTier > 1 and titleColor) or Color3.fromRGB(106, 105, 107)
slot.shine.ImageColor3 = titleColor or Color3.fromRGB(179, 178, 185)
slot.shine.Visible = titleColor ~= nil and itemTier > 1
Modules.fx.setFlash(slot.frame, slot.shine.Visible)
slotData[slot] = equipment
slot.LayoutOrder = equipment.position
slot.stars.Visible = false
local upgrades = equipment.successfulUpgrades
if upgrades then
for i,child in pairs(slot.stars:GetChildren()) do
if child:IsA("ImageLabel") then
child.ImageColor3 = titleColor or Color3.new(1,1,1)
child.Visible = false
elseif child:IsA("TextLabel") then
child.TextColor3 = titleColor or Color3.new(1,1,1)
child.Visible = false
end
end
if upgrades <= 3 then
for i,star in pairs(slot.stars:GetChildren()) do
local score = tonumber(star.Name)
if score then
star.Visible = score <= upgrades
end
end
slot.stars.exact.Visible = false
else
slot.stars["1"].Visible = true
slot.stars.exact.Visible = true
slot.stars.exact.Text = upgrades
end
slot.stars.Visible = true
end
end
end
end
-- yeet right here
if ui.character.ViewportFrame:FindFirstChild("entity") then
ui.character.ViewportFrame.entity:Destroy()
end
if ui.character.ViewportFrame:FindFirstChild("entity2") then
ui.character.ViewportFrame.entity2:Destroy()
end
local camera = ui.character.ViewportFrame.CurrentCamera
if camera == nil then
camera = Instance.new("Camera")
camera.Parent = ui.character.ViewportFrame
ui.character.ViewportFrame.CurrentCamera = camera
end
local client = player
local character = player.Character
local mask = ui.character.ViewportFrame.characterMask
local characterAppearanceData = {}
characterAppearanceData.equipment = data.equipment or {}
characterAppearanceData.accessories = data.accessories or {}
local characterRender = network:invoke("createRenderCharacterContainerFromCharacterAppearanceData",mask, characterAppearanceData or {}, client)
characterRender.Parent = workspace.CurrentCamera
local animationController = characterRender.entity:WaitForChild("AnimationController")
--[[
local track = animationController:LoadAnimation(mask.idle)
track.Looped = true
track.Priority = Enum.AnimationPriority.Idle
track:Play()
]]
local currentEquipment = network:invoke("getCurrentlyEquippedForRenderCharacter", characterRender.entity)
local weaponType do
if currentEquipment["1"] then
weaponType = currentEquipment["1"].baseData.equipmentType
end
end
local track = network:invoke("getMovementAnimationForCharacter", animationController, "idling", weaponType, nil)
if track then
if typeof(track) == "Instance" then
track:Play()
elseif typeof(track) == "table" then
for ii, obj in pairs(track) do
obj:Play()
end
end
spawn(function()
while true do
wait()
if typeof(track) == "Instance" then
if track.Length > 0 then
break
end
elseif typeof(track) == "table" then
local isGood = true
for ii, obj in pairs(track) do
if track.Length == 0 then
isGood = false
end
end
if isGood then
break
end
end
end
if characterRender then
if ui.character.ViewportFrame:FindFirstChild("entity") then
ui.character.ViewportFrame.entity:Destroy()
end
local entity = characterRender.entity
entity.Parent = ui.character.ViewportFrame
characterRender:Destroy()
local focus = CFrame.new(entity.PrimaryPart.Position + entity.PrimaryPart.CFrame.lookVector * 6.3, entity.PrimaryPart.Position) * CFrame.new(3,0,0)
camera.CFrame = CFrame.new(focus.p + Vector3.new(0,1.5,0), entity.PrimaryPart.Position + Vector3.new(0,0.5,0))
end
end)
else
local track = animationController:LoadAnimation(mask.idle)
track.Looped = true
track.Priority = Enum.AnimationPriority.Idle
track:Play()
end
--[[
spawn(function()
wait()
if characterRender then
local entity = characterRender.entity
entity.Parent = ui.character.ViewportFrame
characterRender:destroy()
local focus = CFrame.new(entity.PrimaryPart.Position + entity.PrimaryPart.CFrame.lookVector * 6.3, entity.PrimaryPart.Position) * CFrame.new(3,0,0)
camera.CFrame = CFrame.new(focus.p + Vector3.new(0,1.5,0), entity.PrimaryPart.Position + Vector3.new(0,0.5,0))
end
end)
]]
end
end
end
function module.tradeRequest()
if activePlayer and ui.Visible and ui.content.buttons["request trade"].icon.Visible then
if not configuration.getConfigurationValue("isTradingEnabled") then
Modules.notifications.alert({text = "Trading is temporarily disabled."}, 2)
end
local button = ui.content.buttons["request trade"]
button.icon.Visible = false
local success = network:invokeServer("playerRequest_requestTrade", activePlayer)
if success then
button.success.Visible = true
Modules.notifications.alert({text = "Sent "..activePlayer.Name.." a trade request."}, 2)
else
button.fail.Visible = true
end
spawn(function()
wait(1)
button.fail.Visible = false
button.success.Visible = false
button.icon.Visible = true
end)
end
end
ui.content.buttons["request trade"].Activated:Connect(module.tradeRequest)
network:invoke("addInputAction", "request trade", module.tradeRequest, "T", 6)
function module.guildRequest()
local button = ui.content.buttons["guild"]
if activePlayer and ui.Visible and button.Visible and button.icon.Visible then
local activePlayerName = activePlayer.Name
Modules.notifications.alert({text = "Invited "..activePlayerName.." to your guild."}, 2)
button.icon.Visible = false
local waiting = true
button.loading.Visible = true
spawn(function()
while waiting do
button.loading.Text = "."
wait(0.5)
button.loading.Text = ".."
wait(0.5)
button.loading.Text = "..."
wait(0.5)
end
button.loading.Visible = false
end)
local success, status = network:invokeServer("playerRequest_invitePlayerToGuild", activePlayer)
button.loading.Visible = false
waiting = false
if success then
button.success.Visible = true
Modules.notifications.alert({text = activePlayerName .. " accepted your guild invite!"}, 2)
else
Modules.notifications.alert({text = status or "The guild invite failed."}, 2)
button.fail.Visible = true
end
spawn(function()
wait(1)
button.fail.Visible = false
button.success.Visible = false
button.icon.Visible = true
end)
end
end
ui.content.buttons["guild"].Activated:connect(module.guildRequest)
function module.partyRequest()
if activePlayer and ui.Visible and ui.content.buttons["request party"].icon.Visible then
local button = ui.content.buttons["request party"]
button.icon.Visible = false
local target = activePlayer
local success, reason
if button.tooltip.Value == "Kick from Party" then
pcall(function()
success, reason = network:invokeServer("playerRequest_leaveParty", target)
end)
elseif button.tooltip.Value == "Leave Party" then
pcall(function()
success, reason = network:invokeServer("playerRequest_leaveParty")
end)
if success then
button.success.Visible = true
Modules.notifications.alert({text = "Left the party."}, 2)
elseif reason then
button.fail.Visible = true
Modules.notifications.alert({text = reason or "Error occured"}, 2)
end
else
pcall(function()
success, reason = network:invokeServer("playerRequest_invitePlayerToMyParty", target)
end)
if success then
button.success.Visible = true
Modules.notifications.alert({text = "Invited "..target.Name.." to the party."}, 2)
elseif reason then
button.fail.Visible = true
Modules.notifications.alert({text = reason or "Error occured"}, 2)
end
end
local waiting = true
waiting = false
spawn(function()
wait(1)
button.fail.Visible = false
button.success.Visible = false
button.icon.Visible = true
end)
end
end
ui.content.buttons["request party"].Activated:Connect(module.partyRequest)
network:invoke("addInputAction", "request party", module.partyRequest, "P", 6)
function module.duelRequest()
if activePlayer and ui.Visible and ui.content.buttons["request duel"].icon.Visible then
local button = ui.content.buttons["request duel"]
button.icon.Visible = false
local target = activePlayer
local success = network:invokeServer("playerRequest_requestChallenge", target)
if success then
button.success.Visible = true
Modules.notifications.alert({text = "Sent "..target.Name.." a duel challenge."}, 2)
else
button.fail.Visible = true
end
spawn(function()
wait(1)
button.fail.Visible = false
button.success.Visible = false
button.icon.Visible = true
end)
end
end
ui.content.buttons["request duel"].Activated:Connect(module.duelRequest)
network:invoke("addInputAction", "request duel", module.duelRequest, "U", 6)
end
return module
================================================
FILE: src/StarterGui/inspectPlayerPreview.lua
================================================
-- player interaction - inspect page
-- berezaa
local module = {}
local activePlayer
local ui = script.Parent.gameUI.inspectPlayerPreview
local slotData = {}
function module.init(Modules)
local tween = Modules.tween
local network = Modules.network
local configuration = Modules.configuration
local replicatedStorage = game:GetService("ReplicatedStorage")
local itemLookup = require(replicatedStorage:WaitForChild("itemData"))
local itemAttributes = require(replicatedStorage:WaitForChild("itemAttributes"))
local slots = {}
for i,slot in pairs(ui.content.equipment:GetChildren()) do
if slot:IsA("ImageButton") or slot:IsA("ImageLabel") then
slot.item.Image = ""
slot.frame.Visible = false
slot.shine.Visible = false
slot.ImageTransparency = 0.5
slot.LayoutOrder = 99
slotData[slot] = {}
table.insert(slots, slot)
local function show()
network:invoke("populateItemHoverFrame", itemLookup[slotData[slot].id], "inspect", slotData[slot])
end
local function hide()
network:invoke("populateItemHoverFrame")
end
slot.item.MouseEnter:connect(show)
slot.item.SelectionGained:connect(show)
slot.item.MouseLeave:connect(hide)
slot.item.SelectionLost:connect(hide)
end
end
local lastSelectedButton
function module.close()
ui.Visible = false
activePlayer = nil
end
function module.open(player, selectedButton)
lastSelectedButton = selectedButton
activePlayer = player
ui.content.info.username.Text = player.Name
local class = player:FindFirstChild("class") and player.class.Value:lower() or "unknown"
local emblemVisible
if class:lower() ~= "adventurer" then
ui.content.info.username.emblem.Image = "rbxgameasset://Images/emblem_"..class:lower()
ui.content.info.username.emblem.Visible = true
emblemVisible = true
else
ui.content.info.username.emblem.Visible = false
end
local level = player:FindFirstChild("level") and player.level.Value or 0
local label = ui.content.info.level.value
label.Text = "Lvl. "..level
local xSize = game.TextService:GetTextSize(label.Text, label.TextSize, label.Font, Vector2.new()).X + 16
ui.content.info.level.Size = UDim2.new(0, xSize, 0, 26)
local referrals = player:FindFirstChild("referrals") and player.referrals.Value or 0
if referrals > 0 then
local label = ui.content.info.referrals.value
label.Text = tostring(referrals)
local xSize = game.TextService:GetTextSize(label.Text, label.TextSize, label.Font, Vector2.new()).X + 41
ui.content.info.referrals.Size = UDim2.new(0, xSize, 0, 26)
ui.content.info.referrals.Visible = true
else
ui.content.info.referrals.Visible = false
end
local extend = (emblemVisible and 22) or 0
local textSize = game:GetService("TextService"):GetTextSize(player.Name, ui.content.info.username.TextSize, ui.content.info.username.Font, Vector2.new()).X
ui.content.info.username.Size = UDim2.new(0, textSize + 5 + (extend), 0, 30)
for i,slot in pairs(slots) do
slot.item.Image = ""
slot.frame.Visible = false
slot.shine.Visible = false
slot.ImageTransparency = 0.5
slot.LayoutOrder = 99
slot.stars.Visible = false
slot.attribute.Visible = false
Modules.fx.setFlash(slot.frame, false)
end
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart:FindFirstChild("appearance") then
local data = game:GetService("HttpService"):JSONDecode(player.Character.PrimaryPart.appearance.Value)
if data then
if data.equipment then
for i, equipment in pairs(data.equipment) do
local slot = slots[i]
local realItem = itemLookup[equipment.id]
slot.stars.Visible = false
slot.attribute.Visible = false
if realItem then
slot.item.Image = realItem.image
slot.item.ImageColor3 = Color3.new(1,1,1)
slot.frame.Visible = true
slot.shine.Visible = true
slot.ImageTransparency = 0
if equipment.attribute then
local attributeData = itemAttributes[equipment.attribute]
if attributeData and attributeData.color then
slot.attribute.ImageColor3 = attributeData.color
slot.attribute.Visible = true
end
end
if equipment.dye then
slot.item.ImageColor3 = Color3.fromRGB(equipment.dye.r, equipment.dye.g, equipment.dye.b)
end
local titleColor, itemTier = Modules.itemAcquistion.getTitleColorForInventorySlotData(equipment)
slot.frame.ImageColor3 = (itemTier and itemTier > 1 and titleColor) or Color3.fromRGB(106, 105, 107)
slot.shine.ImageColor3 = titleColor or Color3.fromRGB(179, 178, 185)
slot.shine.Visible = titleColor ~= nil and itemTier > 1
Modules.fx.setFlash(slot.frame, titleColor ~= nil and itemTier > 1 )
slotData[slot] = equipment
slot.LayoutOrder = equipment.position
slot.stars.Visible = false
local upgrades = equipment.successfulUpgrades
if upgrades then
for i,child in pairs(slot.stars:GetChildren()) do
if child:IsA("ImageLabel") then
child.ImageColor3 = titleColor or Color3.new(1,1,1)
child.Visible = false
elseif child:IsA("TextLabel") then
child.TextColor3 = titleColor or Color3.new(1,1,1)
child.Visible = false
end
end
if upgrades <= 3 then
for i,star in pairs(slot.stars:GetChildren()) do
local score = tonumber(star.Name)
if score then
star.Visible = score <= upgrades
end
end
slot.stars.exact.Visible = false
else
slot.stars["1"].Visible = true
slot.stars.exact.Visible = true
slot.stars.exact.Text = upgrades
end
slot.stars.Visible = true
end
end
end
end
end
end
ui.Visible = true
end
local player = game.Players.LocalPlayer
local Mouse = player:GetMouse()
local function reposition()
if ui.Visible then
local screensize = workspace.CurrentCamera.ViewportSize
local x, y
if Modules.input.mode.Value == "pc" then
local x = Mouse.X + 15
local y = Mouse.Y - 5
if x + ui.AbsoluteSize.X > screensize.X then
x = Mouse.X - 15 - ui.AbsoluteSize.X
end
local yDisplacement = (y + ui.AbsoluteSize.Y) - (screensize.Y - 36)
if yDisplacement > 0 then
y = y - yDisplacement
end
local targetPosition = UDim2.new(0, x, 0, y)
ui.Position = targetPosition
elseif Modules.input.mode.Value == "xbox" then
local frame = game.GuiService.SelectedObject
local x = frame.AbsolutePosition.X + frame.AbsoluteSize.X + 10
if x > screensize.X then
x = frame.AbsolutePosition.X - ui.AbsoluteSize.X - 10
end
local y = frame.AbsolutePosition.Y + frame.AbsoluteSize.Y/2 - ui.AbsoluteSize.Y/2
local targetPosition = UDim2.new(0, x, 0, y)
ui.Position = targetPosition
end
end
end
game:GetService("RunService").RenderStepped:connect(reposition)
end
return module
================================================
FILE: src/StarterGui/interactShell.lua
================================================
local module = {}
local ui = game.Players.LocalPlayer.PlayerGui.gameUI.interactShell
function module.show(prompt)
prompt.Parent = ui.shell
ui.Visible = true
end
function module.hide()
ui.Visible = false
end
module.hide()
return module
================================================
FILE: src/StarterGui/interaction.lua
================================================
-- ber's attempt to tackle interaction in one night
-- making stuff up as i go instead of planning in advance
local module = {}
local interactions = {}
module.interactions = interactions
local player = game:GetService("Players").LocalPlayer
local gui = player.PlayerGui.gameUI.interactFrame
local function isWorldPositionInFrame(worldPos)
local screenPos = workspace.CurrentCamera:WorldToScreenPoint(worldPos)
local max = workspace.CurrentCamera.ViewportSize
return screenPos.Z > 0 and screenPos.X > 0 and screenPos.Y > 0 and screenPos.X <= max.X and screenPos.Y <= max.Y
end
local effects = script.Parent.effects
function module.interact()
end
local isPlayerTarget
-- for talking animations
local idlesAllowedToTalk = {
["rbxassetid://2267538527"] = {useBody = true};
["rbxassetid://2345613400"] = {useBody = false}; -- arms crossed
["rbxassetid://2861532980"] = {useBody = true};
["rbxassetid://3539593106"] = {useBody = true};
["rbxassetid://3539592561"] = {useBody = true};
["rbxassetid://3539591914"] = {useBody = true};
["rbxassetid://3244862244"] = {useBody = false}; -- warrior sword pose
["rbxassetid://2510524077"] = {useBody = false}; -- sweeping
["rbxassetid://3165415763"] = {useBody = false}; -- fishing
}
-- or target.Parent:FindFirstChild("idle").AnimationId == "rbxassetid://3539591914" or target.Parent:FindFirstChild("idle").AnimationId == "rbxassetid://3244862244" or target.Parent:FindFirstChild("idle").AnimationId == "rbxassetid://3244862244"))
function module.init(Modules)
local network = Modules.network
local tween = Modules.tween
local collectionService = game:GetService("CollectionService")
for _, interaction in pairs(collectionService:GetTagged("interact")) do
table.insert(interactions,interaction)
end
collectionService:GetInstanceAddedSignal("interact"):connect(function(interaction)
table.insert(interactions,interaction)
end)
collectionService:GetInstanceRemovedSignal("interact"):connect(function(interaction)
for i,interact in pairs(interactions) do
if interact == interaction then
table.remove(interactions,i)
end
end
end)
module.currentInteraction = nil
local interactPrompt = gui.interact
function module.stopInteract()
interactPrompt.Parent = gui
local target = module.currentInteraction
if target and target.Parent then
if game.CollectionService:HasTag(target.Parent, "treasureChest") then
-- nothing lol xD
elseif target.Parent:FindFirstChild("clientHitboxToServerHitboxReference") then
Modules.playerInteract.hide()
elseif target:FindFirstChild("interactScript") then
local interactScript = require(target.interactScript)
interactScript.close(Modules)
elseif game.CollectionService:HasTag(target, "teleportPart") then
local partyData = network:invoke("getCurrentPartyInfo")
if partyData then
if partyData.isClientPartyLeader then
-- teleport invoke server cancel
network:invokeServer("playerRequest_cancelGroupTeleport")
end
end
elseif target.Parent.Name == "Shopkeeper" then
Modules.shop.close(true)
network:invoke("setCharacterArrested", false)
elseif target:FindFirstChild("dialogue") then
Modules.dialogue.endDialogue(target.dialogue)
Modules.shop.close(true)
end
end
module.currentInteraction = nil
network:invoke("lockCameraPosition",false)
network:invoke("setStopRenderingPlayers", false)
end
network:create("stopInteraction", "BindableFunction", "OnInvoke", module.stopInteract)
local lastNearest
local function step()
local character = game.Players.LocalPlayer.Character
if character and character.PrimaryPart then
local nearest
local nearDist = 7
local prompt = ""
if module.currentInteraction and module.currentInteraction:IsA("BasePart") then
local dist = (character.PrimaryPart.Position - module.currentInteraction.Position).magnitude
if game.CollectionService:HasTag(module.currentInteraction, "teleportPart") then
nearDist = 20
end
if module.currentInteraction:FindFirstChild("range") then
nearDist = module.currentInteraction.range.Value
end
if dist <= nearDist + 1.5 then
gui.Adornee = module.currentInteraction
gui.Enabled = true
prompt = "Leave"
if module.currentInteraction:FindFirstChild("interactScript") then
local interactScript = require(module.currentInteraction.interactScript)
prompt = interactScript.leavePrompt or prompt
elseif module.currentInteraction.Parent and module.currentInteraction.Parent:FindFirstChild("clientHitboxToServerHitboxReference") then
prompt = "Cancel"
--
elseif collectionService:HasTag(module.currentInteraction, "seat") then
prompt = "Get up"
--
end
Modules.interactShell.show(interactPrompt)
gui.Enabled = false
else
module.stopInteract()
gui.Adornee = nil
gui.Enabled = false
end
else
local nearestDist = 999
local nearestPriority = -1
for i, interaction in pairs(interactions) do
if interaction:IsA("BasePart") and interaction:IsDescendantOf(workspace) then
local priority = interaction:FindFirstChild("priority") and interaction.priority.Value or 1
-- give players lower priority
if interaction.Parent:FindFirstChild("clientHitboxToServerHitboxReference") then
priority = 0
end
local dist = (character.PrimaryPart.Position - interaction.Position).magnitude
if priority > nearestPriority then
local range = interaction:FindFirstChild("range") and interaction.range.Value or 8
if dist <= range then
nearest = interaction
nearestDist = dist
nearestPriority = priority
end
end
if priority >= nearestPriority and dist < nearestDist and isWorldPositionInFrame(interaction.Position) then
nearest = interaction
nearestDist = dist
end
end
end
if nearest then
local range = nearest:FindFirstChild("range") and nearest.range.Value or 8
if (nearestDist <= range or game.CollectionService:HasTag(nearest, "teleportPart") ) then
gui.Adornee = nearest
prompt = "Interact"
if game.CollectionService:HasTag(nearest.Parent, "treasureChest") then
prompt = "Open"
elseif nearest:FindFirstChild("interactScript") then
local interactScript = require(nearest.interactScript)
if interactScript.interactPrompt and type(interactScript.interactPrompt) == "string" then
prompt = interactScript.interactPrompt
prompt = prompt:sub(1,1):upper() .. prompt:sub(2):lower()
end
--[[
elseif nearest.Parent and nearest.Parent:FindFirstChild("clientHitboxToServerHitboxReference") then
local referenceValue = nearest.Parent:FindFirstChild("clientHitboxToServerHitboxReference")
if referenceValue.Value and referenceValue.Value.Parent then
local player = game.Players:GetPlayerFromCharacter(referenceValue.Value.Parent)
if not player and referenceValue.Value:FindFirstChild("mirrorValue") and referenceValue.Value.mirrorValue.Value and referenceValue.Value.mirrorValue.Value.Parent then
player = game.Players:GetPlayerFromCharacter(referenceValue.Value.mirrorValue.Value.Parent)
end
if player then
Modules.playerInteract.show(player)
isPlayerTarget = true
prompt = nil
end
end
]]
elseif game.CollectionService:HasTag(nearest, "teleportPart") then
prompt = "Travel"
elseif nearest.Parent.Name == "Shopkeeper" then
prompt = "Shop"
elseif nearest:FindFirstChild("PromptOverride") then
prompt = nearest:FindFirstChild("PromptOverride").Value
elseif nearest:FindFirstChild("dialogue") then
if nearest:FindFirstChild("OverridePrompt") then
prompt = nearest:FindFirstChild("OverridePrompt").Value
else
prompt = "Talk"
end
elseif collectionService:HasTag(nearest, "seat") then
prompt = "Sit"
end
if prompt then
-- waving animation
if nearest ~= lastNearest then
local target = nearest
if target.Parent and target.Parent:FindFirstChild("AnimationController") and target.Parent:FindFirstChild("greeting") then
local track = target.Parent.AnimationController:LoadAnimation(target.Parent.greeting)
if track then --and target.Parent:FindFirstChild("beingGreeted") == nil
local tag = Instance.new("BoolValue")
tag.Name = "beingGreeted"
tag.Parent = target.Parent
track.Looped = false
track.Priority = Enum.AnimationPriority.Action
track:Play()
local stopConnection
stopConnection = track.Stopped:connect(function()
stopConnection:disconnect()
if tag then
tag:Destroy()
end
end)
end
end
end
lastNearest = nearest
gui.Enabled = true
-- I shouldn't have to do this, but something is inexplicably setting this to false
interactPrompt.Visible = true
else
gui.Enabled = false
lastNearest = nil
end
if isPlayerTarget and not (nearest.Parent and nearest.Parent:FindFirstChild("clientHitboxToServerHitboxReference")) then
isPlayerTarget = false
Modules.playerInteract.hide()
lastNearest = nil
end
else
gui.Enabled = false
if isPlayerTarget then
Modules.playerInteract.hide()
end
lastNearest = nil
end
else
gui.Enabled = false
if isPlayerTarget then
Modules.playerInteract.hide()
end
lastNearest = nil
end
end
if prompt then
interactPrompt.value.Text = prompt
local contents = game.TextService:GetTextSize(
interactPrompt.value.Text,
interactPrompt.value.TextSize,
interactPrompt.value.Font,
Vector2.new()
)
interactPrompt.button.Size = UDim2.new(0, contents.X + 56, 0, 36)
end
end
end
game:GetService("RunService").Heartbeat:connect(step)
--game:GetService("RunService"):BindToRenderStep("interactionStep", Enum.RenderPriority.Camera.Value - 1, step)
function module.interact()
local target = gui.Adornee
if module.currentInteraction == target then
module.stopInteract()
else
module.currentInteraction = target
if (gui.Enabled or isPlayerTarget) and target then
if target:FindFirstChild("helloSound") then
Modules.utilities.playSound(target.helloSound.Value, target)
end
local track
local controller = target.Parent:FindFirstChild("AnimationController")
if target.Parent and controller and target.Parent:FindFirstChild("talk") then
track = target.Parent.AnimationController:LoadAnimation(target.Parent.talk)
elseif controller then
track = target.Parent.AnimationController:LoadAnimation(effects.defaulttalk)
end
if track and target.Parent:FindFirstChild("idle") and idlesAllowedToTalk[target.Parent:FindFirstChild("idle").AnimationId] then --and target.Parent:FindFirstChild("beingGreeted") == nil
if not idlesAllowedToTalk[target.Parent:FindFirstChild("idle").AnimationId].useBody then
track = target.Parent.AnimationController:LoadAnimation(effects.defaulttalk_noarm)
end
local fadeTime = .5
local tag = Instance.new("BoolValue")
tag.Name = "beingGreeted"
tag.Parent = target.Parent
track.Looped = false
track.Priority = Enum.AnimationPriority.Action
track:Play(fadeTime)
spawn(function()
wait(0.5)
local tracks = controller:GetPlayingAnimationTracks()
for i, existingTrack in pairs(tracks) do
if existingTrack.Animation and existingTrack.Animation.Name == "greeting" then
existingTrack:Stop()
end
end
end)
game.Debris:AddItem(tag, track.Length/track.Speed)
end
local dialogueObject = target:FindFirstChild("dialogue")
if game.CollectionService:HasTag(target.Parent, "treasureChest") then
module.stopInteract()
network:invoke("openTreasureChest_client", target.Parent)
elseif target:FindFirstChild("interactScript") then
local interactScript = require(target.interactScript)
-- going through all lengths to avoid damien's network module
interactScript.init(Modules)
if interactScript.instant then
module.stopInteract()
end
elseif target.Parent and target.Parent:FindFirstChild("clientHitboxToServerHitboxReference") then
local referenceValue = target.Parent:FindFirstChild("clientHitboxToServerHitboxReference")
if referenceValue.Value and referenceValue.Value.Parent then
local player = game.Players:GetPlayerFromCharacter(referenceValue.Value.Parent)
if not player and referenceValue.Value:FindFirstChild("mirrorValue") and referenceValue.Value.mirrorValue.Value and referenceValue.Value.mirrorValue.Value.Parent then
player = game.Players:GetPlayerFromCharacter(referenceValue.Value.mirrorValue.Value.Parent)
end
if player then
Modules.playerInteract.activate(player)
module.stopInteract()
end
end
elseif game.CollectionService:HasTag(target, "teleportPart") then
local partyData = network:invoke("getCurrentPartyInfo")
if partyData then
if partyData.isClientPartyLeader then
-- teleport invoke server
network:invokeServer("playerRequest_startGroupTeleport", target)
else
Modules.notifications.alert({
text = "Only the party leader can teleport the party!";
textColor = Color3.fromRGB(255,170,170)
}, 2)
module.stopInteract()
end
end
elseif target.Parent and target.Parent.Name == "Shopkeeper" then
-- local cf = target.CFrame:ToWorldSpace(camCf)\
Modules.shop.open(target)
elseif dialogueObject then
-- initiate dialogue
Modules.dialogue.beginDialogue(target, require(dialogueObject))
elseif collectionService:HasTag(target, "seat") then
network:invoke("seatPlayer", target)
end
end
end
end
interactPrompt.button.Activated:connect(function()
tween(interactPrompt.button, {"ImageColor3"}, {Color3.fromRGB(223, 223, 223)}, 0.5)
module.interact()
end)
interactPrompt.button.MouseEnter:connect(function()
tween(interactPrompt.button, {"ImageColor3"}, {Color3.fromRGB(111, 236, 255)}, 0.5)
end)
interactPrompt.button.MouseLeave:connect(function()
tween(interactPrompt.button, {"ImageColor3"}, {Color3.fromRGB(223, 223, 223)}, 0.5)
end)
end
return module
================================================
FILE: src/StarterGui/inventory.lua
================================================
local module = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local itemData = require(replicatedStorage:WaitForChild("itemData"))
local itemAttributes = require(replicatedStorage:WaitForChild("itemAttributes"))
local abilityLookup = require(game.ReplicatedStorage:WaitForChild("abilityLookup"))
local player = game.Players.LocalPlayer
local playerGui = player.PlayerGui
local menuUI = playerGui.gameUI.menu_inventory
local header = menuUI.header
local sortCategoryButtonContainer = header.sorts
local content = menuUI.content
local ui = menuUI.Parent
local scrollingMask = ui.scrollingMask
local tweenService = game:GetService("TweenService")
-- equipment, consumable, miscellaneous
local currentCategoryTab = "equipment"
local lastInventoryDataReceived = nil
local inventorySlotPairing = {}
local abilityPairing = {}
local tweenAnimations = {}
local itemUseDebounce = false
module.isEnchantingEquipment = false
menuUI.scrollPrompt.Visible = false
local inventorySlotData_enchantment = nil
local lastSelected
local playerData
function module.show()
menuUI.Visible = not menuUI.Visible
end
function module.hide()
menuUI.Visible = false
end
menuUI.close.Activated:connect(module.hide)
function module.init(Modules)
local animationInterface = Modules.animationInterface
local tradeFrame = ui.menu_trade
local enchantFrame = ui.menu_enchant
local shopFrame = ui.menu_shop
local storageFrame = ui.menu_storage
local equipFrame = ui.menu_equipment
local network = Modules.network
local utilities = Modules.utilities
local uiCreator = Modules.uiCreator
local tween = Modules.tween
local enchantment = Modules.enchantment
local localization = Modules.localization
local mapping = Modules.mapping
network:create("signal_isEnchantingEquipmentSet", "BindableEvent")
function module.setIsEnchantingEquipment(value, inventorySlotData_enchantment)
module.isEnchantingEquipment = value
network:fire("signal_isEnchantingEquipmentSet", value, inventorySlotData_enchantment)
end
local function burst(n)
local t = (n ^ (1/3)) / 3.5
for i=1,n do
spawn(function()
wait(t * math.random())
local icon = menuUI.currency.ethyr.icon:Clone()
icon.Name = "iconClone"
icon.Parent = menuUI.currency.ethyr
icon.ImageTransparency = 0
local initialX = math.random(-10,10)
local initialY = math.random(-10,10)
icon.Position = UDim2.new(0.5, initialX, 0.5, initialY)
icon.Visible = true
local s = math.random(60,110)/100
tween(icon, {"ImageTransparency", "Position"}, {1, UDim2.new(0.5, initialX + math.random(-100,100) * t, 0.5, initialY + math.random(-100,100) * t)}, s)
game.Debris:AddItem(icon, s + 0.1)
end)
end
end
game.MarketplaceService.PromptProductPurchaseFinished:Connect(function(playerId, assetId, isPurchased)
if playerId == game.Players.LocalPlayer.userId and isPurchased then
local burstEffect
local soundEffect
if assetId == 509934399 then
soundEffect = "ethyr1"
burstEffect = 7
elseif assetId == 509935760 then
soundEffect = "ethyr2"
burstEffect = 14
elseif assetId == 509935018 then
soundEffect = "ethyr3"
burstEffect = 26
elseif assetId == 539152241 then
soundEffect = "ethyr4"
burstEffect = 46
end
-- if burstEffect then
-- menuUI.currency.ethyr.Visible = true
--
-- burst(burstEffect)
-- end
if soundEffect then
utilities.playSound(soundEffect)
end
end
end)
local ethyrCostInfo = {costType = "ethyr", icon = "rbxassetid://31886504632", textColor = Color3.fromRGB(115, 255, 251)}
local globalData = network:invoke("getCacheValueByNameTag", "globalData")
menuUI.currency.ethyr.Visible = false
if globalData then
Modules.money.setLabelAmount(menuUI.currency.ethyr.amount, globalData.ethyr or 0, ethyrCostInfo)
menuUI.currency.ethyr.Size = UDim2.new(0, menuUI.currency.ethyr.amount.amount.AbsoluteSize.X + 95 + 10, 0, 36 + 10)
menuUI.currency.ethyr.Visible = (globalData.ethyr and globalData.ethyr > 0)
end
menuUI.currency.ethyr.buy.Activated:connect(function()
Modules.products.open()
end)
local oldEthyr = globalData.ethyr or 0
network:connect("propogationRequestToSelf", "Event", function(key, value)
if key == "globalData" then
local newGlobalData = network:invoke("getCacheValueByNameTag", "globalData")
local newEthyr = newGlobalData.ethyr or 0
if newEthyr > oldEthyr and newEthyr > 0 then
local ethyrDiff = newEthyr - oldEthyr
local burstAmt = math.clamp(ethyrDiff/10, 3, 50)
burst(burstAmt)
Modules.money.setLabelAmount(menuUI.currency.ethyr.amount, value.ethyr or 0, ethyrCostInfo)
menuUI.currency.ethyr.Size = UDim2.new(0, menuUI.currency.ethyr.amount.amount.AbsoluteSize.X + 95, 0, 36 + 10)
menuUI.currency.ethyr.Visible = (newEthyr > 0)
end
oldEthyr = newEthyr
end
end)
local function onInventoryItemMouseEnter(inventoryItem)
lastSelected = inventoryItem
local abilitySlotData = abilityPairing[inventoryItem]
if abilitySlotData then
local abilityBaseData = abilityLookup[abilitySlotData.id](playerData)
network:invoke("populateItemHoverFrameWithAbility", abilityBaseData, abilitySlotData.rank)
end
local inventorySlotData = inventorySlotPairing[inventoryItem]
if inventorySlotData then
local itemBaseData = itemData[inventorySlotData.id]
if itemBaseData then
network:invoke("populateItemHoverFrame", itemBaseData, "inventory", inventorySlotData)
end
end
end
local function onInventoryItemMouseLeave(inventoryItem)
if lastSelected == inventoryItem then
-- clears last selected
network:invoke("populateItemHoverFrame")
end
end
Modules.money.subscribeToPlayerMoney(menuUI.currency.money)
local inventoryItemTemplate = ui.menu_inventory:WaitForChild("inventoryItemTemplate")
local function onInventoryItemDoubleClicked(inventoryItem)
if abilityPairing[inventoryItem] then
local abilitySlotData = abilityPairing[inventoryItem]
if abilitySlotData then
if enchantFrame.Visible then
Modules.enchant.dragItem(abilitySlotData)
else
network:invoke("activateAbilityRequest", abilitySlotData.id)
end
end
elseif inventorySlotPairing[inventoryItem] then
local inventorySlotData = inventorySlotPairing[inventoryItem]
if tradeFrame.Visible then
Modules.trading.processDoubleClickFromInventory(inventorySlotData)
--elseif enchantFrame.Visible then
-- Modules.enchant.dragItem(inventorySlotData)
elseif storageFrame.Visible then
network:invokeServer("playerRequest_transferInventoryToStorage", inventorySlotData)
elseif shopFrame.Visible then
network:invoke("shop_setCurrentItem", inventorySlotData, true)
else
local itemBaseData = itemData[inventorySlotData.id]
print("doubleclick", itemBaseData and itemBaseData.name, itemBaseData and itemBaseData.category, itemBaseData and itemBaseData.equipmentPosition)
print(itemBaseData)
if itemBaseData then
if itemBaseData.category == "equipment" then
if itemBaseData.equipmentPosition == mapping.equipmentPosition.arrow then
-- this is an arrow.. we want to handle this uniquely
print("arrow hehe")
local success = network:invokeServer("transferInventoryToEquipment", "equipment", inventorySlotData.position, mapping.equipmentPosition.arrow)
else
--equipmentSlot
local targetName = Modules.mapping.getMappingByValue("equipmentPosition", itemBaseData.equipmentSlot)
if targetName then
local target = equipFrame.content:FindFirstChild(targetName)
if target and target:FindFirstChild("equipItemButton") then
Modules.uiCreator.processSwap(inventoryItem, target.equipItemButton)
end
end
end
elseif itemBaseData.category == "consumable" or itemBaseData.activationEffect ~= nil then
-- so sad, we have to do this though.
-- functions use each other :(
local success, reason = network:invoke("activateItemRequestLocal", inventorySlotData)
print(success, reason)
end
end
end
end
end
local function onInventoryItemMouseButton1Click_isEnchantingEquipment(inventoryItem)
if module.isEnchantingEquipment and inventorySlotData_enchantment then
local target
local mode
if inventoryItem:IsDescendantOf(menuUI) and inventorySlotPairing[inventoryItem] then
local inventorySlotData = inventorySlotPairing[inventoryItem]
if inventorySlotData then
target = inventorySlotData
end
elseif inventoryItem:IsDescendantOf(equipFrame) then
local equipmentSlotData = network:invoke("getEquipmentSlotDataByEquipmentSlotUI", inventoryItem)
if equipmentSlotData then
target = equipmentSlotData
mode = "equipment"
end
end
if inventoryItem.Parent.blocked.Visible then
return false
end
local continue = true
local definitiveEnchantmentData = inventorySlotData_enchantment
if definitiveEnchantmentData then
local dyeBaseData = itemData[definitiveEnchantmentData.id]
if target then
local equipmentBaseData = itemData[target.id]
if dyeBaseData.dye and not Modules.dyePreview.prompt(dyeBaseData, equipmentBaseData) then
continue = false
end
end
end
if target and continue then
local enchantmentData = definitiveEnchantmentData
local playerInput = {}
local itemBaseData_enchantment = itemData[enchantmentData.id]
if itemBaseData_enchantment and itemBaseData_enchantment.playerInputFunction then
playerInput = itemBaseData_enchantment.playerInputFunction()
end
local success, scrollApplied, newInventorySlotData, status = network:invokeServer("playerRequest_enchantEquipment", enchantmentData, target, mode, playerInput)
if status then
spawn(function()
wait(0.5)
-- game.StarterGui:SetCore("ChatMakeSystemMessage", status)
network:fire("alert", status)
end)
end
itemUseDebounce = false
-- module.isEnchantingEquipment = false
module.setIsEnchantingEquipment(false)
menuUI.scrollPrompt.Visible = false
inventorySlotData_enchantment = nil
scrollingMask.ImageTransparency = 1
network:invoke("populateItemHoverFrame")
if success and scrollApplied and newInventorySlotData then
spawn(function()
wait(0.5)
local ringInfo = {
color = Modules.itemAcquistion.getTitleColorForInventorySlotData(newInventorySlotData) or Color3.new(1,1,1);
}
Modules.fx.ring(ringInfo, inventoryItem.AbsolutePosition + inventoryItem.AbsoluteSize/2)
end)
end
-- disgusting
network:invoke("updateInventoryUI")
end
end
end
module.enchantItem = onInventoryItemMouseButton1Click_isEnchantingEquipment
local abilityData
local learnAbilitiesFrame = menuUI.learnAbilities
local learnableAbilityCount = 0
local function updateLearnableAbilities()
abilityData = abilityData or network:invoke("getCacheValueByNameTag", "abilities")
for _, button in pairs(learnAbilitiesFrame.contents:GetChildren()) do
if button:IsA("GuiObject") then
button:Destroy()
end
end
-- at this point shouldnt i just change how we store the data ;-;
local organizedAbilityData = {}
for _, abilitySlotData in pairs(abilityData) do
organizedAbilityData[abilitySlotData.id] = abilitySlotData
end
local learnableAbilities = {}
for abilityId, isAbility in pairs(abilityLookup:GetAbilityIds()) do
if isAbility then
local ability = abilityLookup[abilityId](playerData)
if ability.metadata and ability.metadata.requirement and ability.metadata.requirement(playerData) then
if organizedAbilityData[ability.id] == nil or organizedAbilityData[ability.id].rank == 0 then
table.insert(learnableAbilities, ability)
end
end
end
end
learnableAbilityCount = #learnableAbilities
for _, ability in pairs(learnableAbilities) do
local template = learnAbilitiesFrame.template:Clone()
local cost = ability.metadata.cost or 0
template.item.Image = ability.image
template.abilityPoints.amount.Text = cost .. " AP"
template.LayoutOrder = cost
template.Parent = learnAbilitiesFrame.contents
template.Visible = true
local function selected()
network:invoke("populateItemHoverFrameWithAbility", ability, 0)
lastSelected = template
end
local function unselected()
if lastSelected == template then
network:invoke("populateItemHoverFrame")
end
end
template.item.MouseEnter:connect(selected)
template.item.SelectionGained:connect(selected)
template.item.MouseLeave:connect(unselected)
template.item.SelectionLost:connect(unselected)
template.item.Activated:connect(function()
local str = localization.translate("Are you sure you want to spend %s to learn %s?", learnAbilitiesFrame)
local abilityName = ability.name and localization.translate(ability.name) or "???"
local playerConfirmed = network:invoke("promptActionFullscreen",string.format(str, cost .. " AP", abilityName))
if playerConfirmed then
local success, err = network:invokeServer("playerRequest_learnAbility", ability.id)
-- auto-bind new abilities
if success then
if not ability.passive then
local hotbarSlotPairing = network:invoke("getHotbarSlotPairing")
for i, hotbarSlot in pairs(hotbarSlotPairing) do
local hotbarButton = hotbarSlot.button
local hotbarData = hotbarSlot.data
if (hotbarData == nil or hotbarData.id == nil or hotbarData.id <= 0) then
local num = string.gsub(hotbarButton.Name,"[^.0-9]+","")
num = tonumber(num)
if num ~= 1 and num ~= 2 then -- 1 and 2 reserved for consumables
if num == 10 then num = 0 end
local bindsuccess = network:invokeServer("registerHotbarSlotData", mapping.dataType.ability, ability.id, tonumber(num))
if bindsuccess then
for i=1,4 do
local flare = hotbarButton.flare:Clone()
flare.Name = "flareCopy"
flare.Parent = hotbarButton
flare.Visible = true
flare.Size = UDim2.new(1,4,1,4)
flare.Position = UDim2.new(0.5,0,1,2)
flare.AnchorPoint = Vector2.new(0.5,1)
local y = (180 - 40*i)
local x = (14 - 2*i)
local EndPosition = UDim2.new(0.5,0,1,2)
local EndSize = UDim2.new(1,x,1,y)
tween(flare,{"Position","Size","ImageTransparency"},{EndPosition, EndSize, 1},0.5*i)
end
break
end
end
end
end
end
if game.ReplicatedStorage.assets.sounds:FindFirstChild("idolPickup") then
--game.ReplicatedStorage.sounds.idolPickup:Play()
utilities.playSound("idolPickup")
end
end
end
end)
end
local rows = math.ceil(#learnableAbilities/4)
local grid = learnAbilitiesFrame.contents.UIGridLayout
local ySize = grid.CellPadding.Y.Offset + grid.CellSize.Y.Offset
local xSize = grid.CellPadding.X.Offset + grid.CellSize.X.Offset
learnAbilitiesFrame.Size = UDim2.new(0, ((rows > 1 and xSize * 4 + 14) or xSize * #learnableAbilities), 0, math.min(rows * ySize, 170))
learnAbilitiesFrame.contents.CanvasSize = UDim2.new(0, 0, 0, rows * ySize)
learnAbilitiesFrame.contents.CanvasPosition = Vector2.new()
end
local function updateInventory(updateInventoryData)
if updateInventoryData then
lastInventoryDataReceived = updateInventoryData
end
if currentCategoryTab ~= "ability" then
learnAbilitiesFrame.Visible = false
end
local currentSelected = game.GuiService.SelectedObject
local selectionName, selectionParent
if currentSelected and currentSelected:IsDescendantOf(menuUI) then
selectionName = currentSelected.Name
selectionParent = currentSelected.Parent
end
if lastInventoryDataReceived then
inventorySlotPairing = {}
abilityPairing = {}
for i, inventoryItem in pairs(content:GetChildren()) do
if inventoryItem:FindFirstChild("item") then
inventoryItem:Destroy()
end
end
local currCells = 0
playerData = network:invoke("getLocalPlayerDataCache")
abilityData = abilityData or network:invoke("getCacheValueByNameTag", "abilities")
local level = network:invoke("getCacheValueByNameTag", "level") or 1
local contents = {}
local n = 0
if currentCategoryTab == "ability" then
for i, abilitySlotData in pairs(abilityData) do
if abilitySlotData.rank > 0 then
n = n + 1
contents[tostring(n)] = abilitySlotData
end
end
else
for i, inventorySlotData in pairs(lastInventoryDataReceived) do
local inventoryItemBaseData = itemData[inventorySlotData.id]
if inventoryItemBaseData then
if inventoryItemBaseData.category == currentCategoryTab then
contents[tostring(inventorySlotData.position)] = inventorySlotData
end
end
end
end
for i = 1, 20 do
local inventoryItem = ui.menu_inventory.inventoryItemTemplate:Clone()
inventoryItem.stars.Visible = false
inventoryItem.blocked.Visible = false
inventoryItem.shine.Visible = false
inventoryItem.locked.Visible = false
inventoryItem.attribute.Visible = false
inventoryItem.item.duplicateCount.Text = ""
local item = contents[tostring(i)]
if item then
if currentCategoryTab == "ability" then
-- ability
local abilitySlotData = item
local abilityBaseData = abilityLookup[abilitySlotData.id](playerData)
if not abilityBaseData.passive then
local tag = Instance.new("BoolValue")
tag.Name = "bindable"
tag.Parent = inventoryItem.item
end
inventoryItem.ImageTransparency = 0
inventoryItem.item.Image = abilityBaseData.image
inventoryItem.item.ImageColor3 = Color3.new(1,1,1)
if abilityBaseData.passive then
abilitySlotData.passive = true
end
abilityPairing[inventoryItem.item] = abilitySlotData
local titleColor, itemTier
if abilitySlotData then
if abilitySlotData.rank > 1 then
itemTier = 2
end
end
if abilityBaseData.statistics then
local abilityStats = Modules.ability_utilities.getAbilityStatisticsForRank(abilityBaseData, abilitySlotData.rank)
if abilityStats and abilityStats.tier and (itemTier == nil or abilityStats.tier > itemTier) then
itemTier = abilityStats.tier
end
end
if itemTier and itemTier > 1 then
titleColor = enchantment.tierColors[itemTier]
end
local upgrades = abilitySlotData.rank > 1 and abilitySlotData.rank - 1
if upgrades then
for i,child in pairs(inventoryItem.stars:GetChildren()) do
if child:IsA("ImageLabel") then
child.ImageColor3 = titleColor or Color3.new(1,1,1)
child.Visible = false
elseif child:IsA("TextLabel") then
child.TextColor3 = titleColor or Color3.new(1,1,1)
child.Visible = false
end
end
inventoryItem.stars.Visible = true
if upgrades <= 3 then
for i,star in pairs(inventoryItem.stars:GetChildren()) do
local score = tonumber(star.Name)
if score then
star.Visible = score <= upgrades
end
end
inventoryItem.stars.exact.Visible = false
else
inventoryItem.stars["1"].Visible = true
inventoryItem.stars.exact.Visible = true
inventoryItem.stars.exact.Text = upgrades
end
inventoryItem.stars.Visible = true
end
inventoryItem.shine.Visible = titleColor ~= nil and itemTier > 1
inventoryItem.shine.ImageColor3 = titleColor or Color3.fromRGB(179, 178, 185)
-- local gradient = script.abilityGradient:Clone()
-- gradient.Parent = inventoryItem.item
Modules.fx.setFlash(inventoryItem.frame, inventoryItem.shine.Visible)
inventoryItem.frame.ImageColor3 = (itemTier and itemTier > 1 and titleColor) or Color3.fromRGB(106, 105, 107)
uiCreator.drag.setIsDragDropFrame(inventoryItem.item)
uiCreator.setIsDoubleClickFrame(inventoryItem.item, 0.2, onInventoryItemDoubleClicked)
else
-- item
local inventorySlotData = item
local inventoryItemBaseData = itemData[inventorySlotData.id]
if inventoryItemBaseData.canBeBound then
local tag = Instance.new("BoolValue")
tag.Name = "bindable"
tag.Parent = inventoryItem.item
end
inventoryItem.ImageTransparency = 0
inventoryItem.item.Image = inventoryItemBaseData.image
inventoryItem.item.ImageColor3 = Color3.new(1,1,1)
if inventorySlotData.dye then
inventoryItem.item.ImageColor3 = Color3.fromRGB(inventorySlotData.dye.r, inventorySlotData.dye.g, inventorySlotData.dye.b)
end
if inventoryItemBaseData.minLevel then
local level = network:invoke("getCacheValueByNameTag", "level") or 1
inventoryItem.locked.Visible = level < inventoryItemBaseData.minLevel
end
inventoryItem.item.duplicateCount.Text = (inventorySlotData.stacks and inventoryItemBaseData.canStack and inventorySlotData.stacks > 1) and tostring(inventorySlotData.stacks) or ""
inventorySlotPairing[inventoryItem.item] = inventorySlotData
local titleColor, itemTier
if inventorySlotData then
titleColor, itemTier = Modules.itemAcquistion.getTitleColorForInventorySlotData(inventorySlotData)
end
if inventorySlotData.attribute then
local attributeData = itemAttributes[inventorySlotData.attribute]
if attributeData and attributeData.color then
inventoryItem.attribute.ImageColor3 = attributeData.color
inventoryItem.attribute.Visible = true
end
end
local upgrades = inventorySlotData.successfulUpgrades
if upgrades then
for i,child in pairs(inventoryItem.stars:GetChildren()) do
if child:IsA("ImageLabel") then
child.ImageColor3 = titleColor or Color3.new(1,1,1)
child.Visible = false
elseif child:IsA("TextLabel") then
child.TextColor3 = titleColor or Color3.new(1,1,1)
child.Visible = false
end
end
inventoryItem.stars.Visible = true
if upgrades <= 3 then
for i,star in pairs(inventoryItem.stars:GetChildren()) do
local score = tonumber(star.Name)
if score then
star.Visible = score <= upgrades
end
end
inventoryItem.stars.exact.Visible = false
else
inventoryItem.stars["1"].Visible = true
inventoryItem.stars.exact.Visible = true
inventoryItem.stars.exact.Text = upgrades
end
inventoryItem.stars.Visible = true
end
if module.isEnchantingEquipment and inventorySlotData_enchantment then
local itemBaseData_enchantment = itemData[inventorySlotData_enchantment.id]
local cost = itemBaseData_enchantment.upgradeCost or 1
local max = inventoryItemBaseData.maxUpgrades
local canEnchant, indexToRemove = enchantment.enchantmentCanBeAppliedToItem(inventorySlotData_enchantment, inventorySlotData)
local blocked = not canEnchant
inventoryItem.blocked.Visible = blocked
end
inventoryItem.shine.Visible = titleColor ~= nil and itemTier > 1
inventoryItem.shine.ImageColor3 = titleColor or Color3.fromRGB(179, 178, 185)
Modules.fx.setFlash(inventoryItem.frame, inventoryItem.shine.Visible)
inventoryItem.frame.ImageColor3 = (itemTier and itemTier > 1 and titleColor) or Color3.fromRGB(106, 105, 107)
if not module.isEnchantingEquipment then
uiCreator.drag.setIsDragDropFrame(inventoryItem.item)
uiCreator.setIsDoubleClickFrame(inventoryItem.item, 0.2, onInventoryItemDoubleClicked)
else
inventoryItem.item.MouseButton1Click:connect(function() onInventoryItemMouseButton1Click_isEnchantingEquipment(inventoryItem.item) end)
end
end
else
inventoryItem.item.Image = ""
inventoryItem.item.Visible = true
inventoryItem.frame.Visible = false
inventoryItem.shine.Visible = false
inventoryItem.ImageTransparency = 0.5
if currentCategoryTab == "ability" and i == n + 1 then
local upgrade = ui.menu_inventory.upgrade:Clone()
upgrade.Parent = inventoryItem
upgrade.Visible = true
-- todo: make sure there are actually abilities you can earn
local function updateUpgradeButton()
if learnAbilitiesFrame.Visible then
upgrade.ImageColor3 = Color3.fromRGB(243, 37, 52)
upgrade.text.Text = "x"
upgrade.Active = true
local position = inventoryItem.AbsolutePosition - menuUI.AbsolutePosition
learnAbilitiesFrame.Position = UDim2.new(0, position.X + inventoryItem.AbsoluteSize.X, 0, position.Y)
else
if unspentAbilityPoints > 0 then
upgrade.ImageColor3 = Color3.fromRGB(255, 182, 12)
upgrade.text.Text = "+"
upgrade.Active = true
else
upgrade.ImageColor3 = Color3.fromRGB(185, 185, 185)
upgrade.text.Text = "+"
upgrade.Active = false
end
end
updateLearnableAbilities()
upgrade.Visible = learnableAbilityCount > 0
end
learnAbilitiesFrame.Visible = false
updateUpgradeButton()
upgrade.Activated:connect(function()
if learnAbilitiesFrame.Visible then
learnAbilitiesFrame.Visible = false
else
updateLearnableAbilities()
learnAbilitiesFrame.Visible = true
end
updateUpgradeButton()
end)
end
end
inventoryItem.item.MouseEnter:connect(function() onInventoryItemMouseEnter(inventoryItem.item) end)
inventoryItem.item.SelectionGained:connect(function() onInventoryItemMouseEnter(inventoryItem.item) end)
inventoryItem.item.MouseLeave:connect(function() onInventoryItemMouseLeave(inventoryItem.item) end)
inventoryItem.item.SelectionLost:connect(function() onInventoryItemMouseLeave(inventoryItem.item) end)
inventoryItem.Name = tostring(i)
inventoryItem.LayoutOrder = i
inventoryItem.Parent = content
inventoryItem.Visible = true
end
if selectionParent and selectionName then
local newSelection = selectionParent:FindFirstChild(selectionName)
if newSelection then
game.GuiService.SelectedObject = newSelection
end
end
end
end
local canPlayerUseConsumable = true
local function onSetCanPlayerUseConsumable(value)
canPlayerUseConsumable = value
end
local CONSUMABLE_COOLDOWN_TIME = 4
local lastUseConsumable = 0
local isCurrentlyConsuming = false
local function activateItemRequest(inventorySlotData)
if itemUseDebounce then return false end
print(canPlayerUseConsumable)
if not canPlayerUseConsumable then return false end
if player.Character.PrimaryPart.health.Value <= 0 then return false end
if network:invoke("getCharacterMovementStates").isSprinting then return end
if network:invoke("isCharacterStunned") then return end
itemUseDebounce = true
local itemBaseData = itemData[inventorySlotData.id]
if itemBaseData then
if itemBaseData.askForConfirmationBeforeConsume then
local playerConfirmed = network:invoke("promptActionFullscreen","Are you sure you want to use '" .. itemBaseData.name .. "'?")
if not playerConfirmed then
itemUseDebounce = false
return false, "denied confirmation"
end
end
local consumeTimeReduction = math.clamp(1 - network:invoke("getCacheValueByNameTag", "nonSerializeData").statistics_final.consumeTimeReduction, 0, 1)
if itemBaseData.enchantsEquipment then
-- module.isEnchantingEquipment = true
module.setIsEnchantingEquipment(true, inventorySlotData)
inventorySlotData_enchantment = inventorySlotData
scrollingMask.ImageTransparency = 0
menuUI.scrollPrompt.Visible = true
menuUI.scrollPrompt.contents.itemIcon.Image = itemBaseData.image
scrollingMask.Image = itemBaseData.image
network:invoke("populateItemHoverFrame")
-- show equipment tab
tweenAnimations[currentCategoryTab].mouseLeaveTween:Play()
currentCategoryTab = "equipment"
tweenAnimations.equipment.mouseClickTween:Play()
updateInventory()
itemUseDebounce = false
return nil
elseif not isCurrentlyConsuming then
lastUseConsumable = tick()
local success, errorMessage
local playerInput = itemBaseData.playerInputFunction and itemBaseData.playerInputFunction() or {}
local itemConsumeTime = (itemBaseData.consumeTime or 1) * consumeTimeReduction
network:invoke("showConsumableCooldown", itemConsumeTime)
local function onAnimationStopped()
isCurrentlyConsuming = false
success, errorMessage = network:invokeServer("activateItemRequest", itemBaseData.category, inventorySlotData.position, nil, playerInput)
end
animationInterface:replicateClientAnimationSequence("axeAnimations", "strike1")
delay(itemConsumeTime + 1.5, function()
onAnimationStopped()
end)
itemUseDebounce = false
return success, errorMessage
end
end
itemUseDebounce = false
return false
end
network:create("activateItemRequestLocal", "BindableFunction", "OnInvoke", activateItemRequest)
network:create("getIsCurrentlyConsuming", "BindableFunction", "OnInvoke", function()
return isCurrentlyConsuming
end)
local function onPropogationRequestToSelf(propogationNameTag, propogationData)
if propogationNameTag == "inventory" then
updateInventory(propogationData)
elseif propogationNameTag == "abilities" then
print("abilities updated!")
abilityData = propogationData
updateInventory()
end
end
local function onStopChannels_cancelConsuming(stateCancel)
local myClientPlayerCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientPlayerCharacterContainer then
local animations = animationInterface:getAnimationsForAnimationController(myClientPlayerCharacterContainer.entity.AnimationController)
-- wait to start playing
if animations and animations.movementAnimations and animations.movementAnimations.consume_loop.IsPlaying then
animations.movementAnimations.consume_loop:Stop()
end
end
end
-- todo: yuck absorb this into input module
game:GetService("UserInputService").InputBegan:connect(function(inputObject, absorbed)
if menuUI.Visible then
if inputObject.KeyCode == Enum.KeyCode.ButtonL1 then
if currentCategoryTab == "consumable" then
currentCategoryTab = "equipment"
elseif currentCategoryTab == "miscellaneous" then
currentCategoryTab = "consumable"
elseif currentCategoryTab == "equipment" then
currentCategoryTab = "miscellaneous"
end
game.GuiService.SelectedObject = sortCategoryButtonContainer:FindFirstChild(currentCategoryTab)
updateInventory()
elseif inputObject.KeyCode == Enum.KeyCode.ButtonR1 then
if currentCategoryTab == "consumable" then
currentCategoryTab = "miscellaneous"
elseif currentCategoryTab == "miscellaneous" then
currentCategoryTab = "equipment"
elseif currentCategoryTab == "equipment" then
currentCategoryTab = "consumable"
end
game.GuiService.SelectedObject = sortCategoryButtonContainer:FindFirstChild(currentCategoryTab)
updateInventory()
for i, button in pairs(sortCategoryButtonContainer:GetChildren()) do
if button.Name == currentCategoryTab then
button.ImageColor3 = Color3.fromRGB(84, 190, 255)
elseif button:IsA("GuiObject") then
button.ImageColor3 = Color3.fromRGB(202, 202, 202)
end
end
end
end
end)
network:create("localSignal_shopOpened", "BindableEvent", "Event", function()
if currentCategoryTab == "ability" then
currentCategoryTab = "miscellaneous"
updateInventory()
for i, button in pairs(sortCategoryButtonContainer:GetChildren()) do
if button.Name == currentCategoryTab then
button.ImageColor3 = Color3.fromRGB(84, 190, 255)
elseif button:IsA("GuiObject") then
button.ImageColor3 = Color3.fromRGB(202, 202, 202)
end
end
end
end)
network:create("localSignal_enchantOpened", "BindableEvent", "Event", function()
if currentCategoryTab ~= "ability" then
currentCategoryTab = "ability"
updateInventory()
for i, button in pairs(sortCategoryButtonContainer:GetChildren()) do
if button.Name == currentCategoryTab then
button.ImageColor3 = Color3.fromRGB(84, 190, 255)
elseif button:IsA("GuiObject") then
button.ImageColor3 = Color3.fromRGB(202, 202, 202)
end
end
end
end)
local function setupSortCategoryButtonEffectsAndEvents(sortButton)
local isHovered = false
local tweenInformation = TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.Out, 0, false, 0)
tweenAnimations[sortButton.Name] = {}
tweenAnimations[sortButton.Name].mouseEnterTween = tweenService:Create(sortButton, tweenInformation, {ImageColor3 = Color3.fromRGB(120, 222, 222)})
tweenAnimations[sortButton.Name].mouseLeaveTween = tweenService:Create(sortButton, tweenInformation, {ImageColor3 = Color3.fromRGB(202, 202, 202)})
tweenAnimations[sortButton.Name].mouseClickTween = tweenService:Create(sortButton, tweenInformation, {ImageColor3 = Color3.fromRGB(84, 190, 255)})
sortButton.Activated:connect(function()
currentCategoryTab = sortButton.Name
-- update the inventory ui to reflect the change
updateInventory()
end)
local function onInputBegan(inputObject)
if inputObject.UserInputType == Enum.UserInputType.MouseMovement then
isHovered = true
if currentCategoryTab ~= sortButton.Name then
tweenAnimations[sortButton.Name].mouseEnterTween:Play()
end
elseif inputObject.UserInputType == Enum.UserInputType.MouseButton1 then
if currentCategoryTab ~= sortButton.Name then
tweenAnimations[sortButton.Name].mouseClickTween:Play()
if tweenAnimations[currentCategoryTab] then
tweenAnimations[currentCategoryTab].mouseLeaveTween:Play()
end
end
end
end
local function onInputEnded(inputObject)
if inputObject.UserInputType == Enum.UserInputType.MouseMovement then
isHovered = false
if currentCategoryTab ~= sortButton.Name then
tweenAnimations[sortButton.Name].mouseLeaveTween:Play()
end
end
end
sortButton.InputBegan:connect(onInputBegan)
sortButton.InputEnded:connect(onInputEnded)
end
local function onGetInventorySlotDataByInventorySlotUI(inventorySlotUI)
if inventorySlotUI then
if inventorySlotPairing[inventorySlotUI] then
return inventorySlotPairing[inventorySlotUI], "item"
end
if abilityPairing[inventorySlotUI] then
return abilityPairing[inventorySlotUI], "ability"
end
end
return nil
end
local function onGetCurrentInventoryCategory()
return currentCategoryTab
end
local function onGetInventorySlotDataWithItemId(itemId)
if not lastInventoryDataReceived then return end
for i, inventorySlotData in pairs(lastInventoryDataReceived) do
if inventorySlotData.id == itemId then
return inventorySlotData
end
end
return nil
end
local function main()
playerData = network:invoke("getLocalPlayerDataCache")
updateInventory(network:invoke("getCacheValueByNameTag", "inventory"))
for i, sortCategoryButton in pairs(sortCategoryButtonContainer:GetChildren()) do
if sortCategoryButton:IsA("ImageButton") then
setupSortCategoryButtonEffectsAndEvents(sortCategoryButton)
end
end
network:connect("stopChannels", "Event", onStopChannels_cancelConsuming)
network:create("setCanPlayerUseConsumable", "BindableFunction", "OnInvoke", onSetCanPlayerUseConsumable)
network:create("getInventorySlotDataByInventorySlotUI", "BindableFunction", "OnInvoke", onGetInventorySlotDataByInventorySlotUI)
network:create("getCurrentInventoryCategory", "BindableFunction", "OnInvoke", onGetCurrentInventoryCategory)
network:create("getInventorySlotDataWithItemId", "BindableFunction", "OnInvoke", onGetInventorySlotDataWithItemId)
--network:create("setOngoingPlayerTradeDataForInventoryException")
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
-- allow other scripts to update the inventoryUI
network:create("updateInventoryUI", "BindableFunction", "OnInvoke", function()
updateInventory()
end)
local function onInputChanged(input)
if module.isEnchantingEquipment then
if scrollingMask.ImageTransparency ~= 0 then
scrollingMask.ImageTransparency = 0
end
Modules.tween(scrollingMask,{"Position"},UDim2.new(0, input.Position.X - 40, 0, input.Position.Y + 20),0.2)
--scrollingMask.Position = UDim2.new(0, input.Position.X - 50, 0, input.Position.Y + 25)
end
end
local userInputService = game:GetService("UserInputService")
userInputService.InputChanged:connect(onInputChanged)
userInputService.InputBegan:connect(function(inputObject)
if inputObject.UserInputType == Enum.UserInputType.MouseButton1 and module.isEnchantingEquipment then
wait(0.15)
itemUseDebounce = false
-- module.isEnchantingEquipment = false
module.setIsEnchantingEquipment(false)
menuUI.scrollPrompt.Visible = false
inventorySlotData_enchantment = nil
scrollingMask.ImageTransparency = 1
-- disgusting
network:invoke("updateInventoryUI")
end
end)
end
delay(1, main)
end
return module
================================================
FILE: src/StarterGui/itemAcquistion.lua
================================================
local module = {}
local modules = require(game.ReplicatedStorage.modules)
local network = modules.load("network")
local placeSetup = modules.load("placeSetup")
local utilities = modules.load("utilities")
local ability_utilities = modules.load("ability_utilities")
local enchantment = modules.load("enchantment")
local userInputService = game:GetService("UserInputService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local textService = game:GetService("TextService")
local itemsDataFolder = replicatedStorage:WaitForChild("itemData")
local itemLookup = require(itemsDataFolder)
local perkLookup = require(replicatedStorage:WaitForChild("perkLookup"))
local itemAttributes = require(replicatedStorage:WaitForChild("itemAttributes"))
local player = game.Players.LocalPlayer
local playerGui = player.PlayerGui
local itemsFolder = placeSetup.awaitPlaceFolder("items")
local rootFrame = script.Parent.gameUI.itemHoverFrame
local itemHoverFrame = rootFrame.contents
local gameUI = rootFrame.Parent
rootFrame.Visible = false
local uiCreator = require(playerGui.uiCreator)
local currentItemHoverData = nil
-- currentItemHoverData.item = nil
-- currentItemHoverData.pickupSizeAnimation = nil
-- currentItemHoverData.regularSizeAnimation = nil
local ITEM_ACQUISITION_RANGE = 5
local pickupInteractionPromptTable = {
prompt = uiCreator.createInteractionPrompt({promptId = "pickupInteractionPrompt"},
{text = "Pick up"}
);
item = nil;
}
module.getTitleColorForInventorySlotData = function()
end
local titleTextSize = 20
module.tierColors = enchantment.tierColors
pickupInteractionPromptTable.prompt.manifest.LayoutOrder = 3
pickupInteractionPromptTable.prompt:hide(true)
function module.init(Modules)
local Mouse = player:GetMouse()
local function reposition()
local screensize = workspace.CurrentCamera.ViewportSize
local x, y
if Modules.input.mode.Value == "pc" then
x = Mouse.X + 15
y = Mouse.Y - 5
if x + rootFrame.AbsoluteSize.X > screensize.X then
x = Mouse.X - 15 - rootFrame.AbsoluteSize.X
end
local yDisplacement = (y + rootFrame.AbsoluteSize.Y) - (screensize.Y - 36)
if yDisplacement > -115 then
y = y - yDisplacement - 115
end
local targetPosition = UDim2.new(0, x, 0, y)
rootFrame.Position = targetPosition
elseif Modules.input.mode.Value == "xbox" then
local frame = game.GuiService.SelectedObject
if frame then
x = frame.AbsolutePosition.X + frame.AbsoluteSize.X + 15
if x + rootFrame.AbsoluteSize.X > screensize.X then
x = frame.AbsolutePosition.X - rootFrame.AbsoluteSize.X - 15
end
y = frame.AbsolutePosition.Y + frame.AbsoluteSize.Y/2 - rootFrame.AbsoluteSize.Y/2
local yDisplacement = (y + rootFrame.AbsoluteSize.Y) - (screensize.Y - 36)
if yDisplacement > -115 then
y = y - yDisplacement - 115
end
local targetPosition = UDim2.new(0, x, 0, y)
rootFrame.Position = targetPosition
end
end
end
game:GetService("RunService").Heartbeat:connect(reposition)
local localization = Modules.localization
local function raycastFromScreenPositionForItems(screenPositionX, screenPositionY)
if player.Character and player.Character.PrimaryPart then
local cameraRay = workspace.CurrentCamera:ScreenPointToRay(screenPositionX, screenPositionY)
local ray = Ray.new(cameraRay.Origin, cameraRay.Direction.unit * 50)
local hitPart, hitPosition = workspace:FindPartOnRayWithWhitelist(ray, {placeSetup.awaitPlaceFolder("items")})
return hitPart, hitPosition, (hitPart and utilities.magnitude(hitPart.Position - player.Character.PrimaryPart.Position) or nil)
end
end
module.source = "none"
local function cleanup()
for i,child in pairs(itemHoverFrame:GetChildren()) do
if child.Name == "perk" then
child:Destroy()
end
end
itemHoverFrame.main.thumbnailBG.ImageColor3 = Color3.new(1,1,1)
itemHoverFrame.soulbound.Visible = false
itemHoverFrame.enchantments.Visible = false
itemHoverFrame.notOwned.Visible = false
rootFrame.UIScale.Scale = 1
end
local function getItemInfo(itemBaseData, inventorySlotData)
local statBonuses = {}
local totalStats = {}
if itemBaseData.baseDamage then
totalStats["baseDamage"] = (totalStats["baseDamage"] or 0) + itemBaseData.baseDamage
end
if itemBaseData.attackSpeed then
totalStats["attackSpeed"] = (totalStats["attackSpeed"] or 0) + itemBaseData.attackSpeed
end
if itemBaseData.modifierData then
for e,modifier in pairs(itemBaseData.modifierData) do
for statName, statValue in pairs(modifier) do
totalStats[statName] = (totalStats[statName] or 0) + statValue
end
end
end
-- cycle thru additional stats, increase totalDamage
if inventorySlotData and inventorySlotData.modifierData then
for i, modifierData in pairs(inventorySlotData.modifierData) do
for statName,statValue in pairs(modifierData) do
statBonuses[statName] = (statBonuses[statName] or 0) + statValue
totalStats[statName] = (totalStats[statName] or 0) + statValue
end
end
end
-- attribute
-- new: attributes treated as bonus stats
if inventorySlotData and inventorySlotData.attribute then
local attributeData = itemAttributes[inventorySlotData.attribute]
if attributeData and attributeData.modifier then
local attributeModifierData = attributeData.modifier(itemBaseData, inventorySlotData)
if attributeModifierData then
for statName, statValue in pairs(attributeModifierData) do
statBonuses[statName] = (statBonuses[statName] or 0) + statValue
totalStats[statName] = (totalStats[statName] or 0) + statValue
end
end
end
end
-- cycle thru enchantments
local statUpgrade = 0
if inventorySlotData and inventorySlotData.enchantments then
for i, enchantment in pairs(inventorySlotData.enchantments) do
local enchantmentBaseData = itemLookup[enchantment.id]
local enchantState = enchantmentBaseData.enchantments[enchantment.state]
if enchantState then
if enchantState.modifierData then
for statName, statValue in pairs(enchantState.modifierData) do
statBonuses[statName] = (statBonuses[statName] or 0) + statValue
totalStats[statName] = (totalStats[statName] or 0) + statValue
end
end
if enchantState.statUpgrade then
statUpgrade = statUpgrade + enchantState.statUpgrade
end
end
end
end
-- statUpgrade from items with variable enhancements (mainly hats!)
if statUpgrade > 0 and itemBaseData.statUpgrade then
for statName, baseValue in pairs(itemBaseData.statUpgrade) do
if baseValue ~= 0 then
local statValue = baseValue * statUpgrade
statBonuses[statName] = (statBonuses[statName] or 0) + statValue
totalStats[statName] = (totalStats[statName] or 0) + statValue
end
end
end
if itemBaseData.bonusStats then
-- populate base stats
for statName, statValue in pairs(itemBaseData.bonusStats) do
if type(statValue) == "number" then
totalStats[statName] = (totalStats[statName] or 0) + statValue
end
end
end
return totalStats, statBonuses
end
local function getTitleColorForInventorySlotData(inventorySlotData)
local itemBaseData = itemLookup[inventorySlotData.id]
local itemBaseTier = itemBaseData.tier
local baseModifierData = itemBaseData.modifierData or {}
local score = 0
local totalStats, statBonuses = getItemInfo(itemBaseData, inventorySlotData)
for stat, value in pairs(statBonuses) do
local adjustedValue = (value < 0 and value * 0.5) or value
if stat == "baseDamage" or stat == "defense" then
score = score + adjustedValue
elseif stat == "maxMana" or stat == "maxHealth" then
score = score + adjustedValue * 0.05
elseif stat == "str" or stat == "dex" or stat == "int" or stat == "vit" then
score = score + adjustedValue * 0.7
elseif stat == "stamina" then
score = score + adjustedValue * 2
elseif stat == "jump" or stat == "walkspeed" then
score = score + adjustedValue
elseif stat == "criticalStrikeChance" or stat == "blockChance" then
score = score + adjustedValue * 50
end
end
local baseMaxUpgrades = (itemBaseData.maxUpgrades or 0)
local bench = baseMaxUpgrades / 7
local enchantTier
if baseMaxUpgrades > 0 and not itemBaseData.notUpgradable then
if score >= 49*bench then
enchantTier = 6
elseif score >= 32*bench then
enchantTier = 5
elseif score >= 21*bench then
enchantTier = 4
elseif score >= 10*bench then
enchantTier = 3
elseif score < 0 then
enchantTier = -1
elseif inventorySlotData and inventorySlotData.upgrades and inventorySlotData.upgrades > 0 then
if inventorySlotData.successfulUpgrades and inventorySlotData.successfulUpgrades > 0 then
enchantTier = 2
else
enchantTier = -1
end
end
end
local trueTier = itemBaseTier
if enchantTier then
if itemBaseTier == nil or enchantTier > itemBaseTier then
trueTier = enchantTier
end
end
local titleColor = trueTier and module.tierColors[trueTier]
return titleColor, trueTier
end
module.getTitleColorForInventorySlotData = getTitleColorForInventorySlotData
-- used before reverting to 7 upgrade system
local function getTitleColorForInventorySlotData_defunct(inventorySlotData)
local itemBaseData = itemLookup[inventorySlotData.id]
local itemBaseTier = itemBaseData.tier or 1
local maxUpgrades = inventorySlotData.maxUpgrades or itemBaseData.maxUpgrades
local averageEnchantmentTier = 0
local enchantmentCount = inventorySlotData and inventorySlotData.upgrades or 0
-- we want to include failed upgrades which are not included in slotData.enchantments
local enchantments = inventorySlotData.enchantments or {}
if enchantmentCount > 0 then
local enchantmentTierTotal = 0
for i, enchantmentData in pairs(enchantments) do
local enchantmentBaseData = itemLookup[enchantmentData.id].enchantments[enchantmentData.state]
local enchantmentTier = enchantmentBaseData.tier
if enchantmentTier then
enchantmentTierTotal = enchantmentTierTotal + enchantmentTier
end
end
averageEnchantmentTier = 1 + 0.01 + enchantmentTierTotal / maxUpgrades
end
local tier = ((averageEnchantmentTier > itemBaseTier or averageEnchantmentTier == -1) and math.floor(averageEnchantmentTier)) or itemBaseTier
-- any upgrade will turn an item blue
if averageEnchantmentTier > 1 and tier < 2 then
tier = 2
end
if tier ~= 1 then
local titleColor = module.tierColors[tier]
return titleColor
end
end
local lastTextData
local function populateItemHoverFrameWithTextData(textData)
-- dont let text override important stuff
if itemHoverFrame.Parent.Visible and itemHoverFrame.Visible and itemHoverFrame.main.Visible and itemHoverFrame.main.thumbnail.Visible then
return false
end
itemHoverFrame.header.itemName.Position = UDim2.new(0, 0,0, 3)
itemHoverFrame.header.itemName.cuteDecor.Visible = false
if textData == nil or textData.text == nil then
if textData.source == nil or textData.source == lastTextData.source then
rootFrame.Visible = false
rootFrame.contents.Visible = false
end
return false
end
rootFrame.Size = UDim2.new(0, 320 + 4, 0, 100)
rootFrame.contents.Visible = false
lastTextData = textData
Modules.input.setCurrentFocusFrame(rootFrame)
-- rootFrame.itemType.Visible = false
cleanup()
module.source = "text"
itemHoverFrame.main.Visible = false
itemHoverFrame.stats.Visible = false
local txtsrc = textData.text and localization.translate(textData.text, itemHoverFrame.header.itemName)
itemHoverFrame.header.itemName.Text = txtsrc or "???"
itemHoverFrame.header.itemName.TextColor3 = textData.textColor3 or Color3.new(1,1,1)
itemHoverFrame.header.itemName.Font = textData.font or Enum.Font.SourceSansBold
local itemNameTextSize = textService:GetTextSize(itemHoverFrame.header.itemName.Text, titleTextSize, itemHoverFrame.header.itemName.Font, Vector2.new())
local maxTextSize = itemNameTextSize
local numStats = 0
-- clear previous stats
for i, obj in pairs(itemHoverFrame.stats.container:GetChildren()) do
if not obj:IsA("UIListLayout") then
obj:Destroy()
end
end
if textData.additionalLines then
for i, line in pairs(textData.additionalLines) do
uiCreator.createTextFragmentLabels(itemHoverFrame.stats.container, {line})
local fragmentTextSize = textService:GetTextSize(line.text or "???", line.textSize or 20, line.font or Enum.Font.SourceSansBold, Vector2.new())
if fragmentTextSize.X > maxTextSize.X then
maxTextSize = Vector2.new(math.min(320 + 10, fragmentTextSize.X), fragmentTextSize.Y)
end
local fragmentYSize = math.ceil(fragmentTextSize.X / (320 + 10))
numStats = numStats + fragmentYSize
end
end
itemHoverFrame.stats.container.Size = UDim2.new(1, 0, 0, numStats * 20)
itemHoverFrame.stats.Size = UDim2.new(1, 0, 0, numStats * 20)
itemHoverFrame.stats.Visible = numStats > 0
local y = 0
for i,child in pairs(itemHoverFrame:GetChildren()) do
if child:IsA("GuiObject") and child.Visible then
y = y + child.AbsoluteSize.Y + itemHoverFrame.UIListLayout.Padding.Offset
end
end
local x = 20 + maxTextSize.X
local openSize = UDim2.new(0, x + 4, 0, y + 4)
rootFrame.Size = openSize
itemHoverFrame.header.itemName.Size = UDim2.new(0, itemNameTextSize.X,0, 18)
itemHoverFrame.header.itemName.stars.Visible = false
rootFrame.UIScale.Scale = Modules.input.menuScale
reposition()
rootFrame.Visible = true
rootFrame.contents.Visible = true
end
network:create("populateItemHoverFrameWithTextData","BindableFunction","OnInvoke", populateItemHoverFrameWithTextData)
local function populateItemHoverFrameWithAbility(abilityInfo, level, comparisonInfo, comparisonLevel)
-- use comparison info for seeing dif between variants and upgrades
local displayAbilityInfo = comparisonInfo or abilityInfo
local displayLevel = comparisonLevel or level
itemHoverFrame.header.itemName.Position = UDim2.new(0, 18,0, 3)
itemHoverFrame.header.itemName.cuteDecor.Visible = true
itemHoverFrame.header.itemName.cuteDecor.Image = "rbxassetid://2528902611"
if abilityInfo == nil then
rootFrame.Visible = false
rootFrame.contents.Visible = false
return false
end
rootFrame.Size = UDim2.new(0, 320 + 4, 0, 40 + 4)
rootFrame.contents.Visible = false
Modules.input.setCurrentFocusFrame(rootFrame)
itemHoverFrame.main.thumbnailBG.Visible = false
-- rootFrame.itemType.Visible = false
itemHoverFrame.header.itemName.AutoLocalize = false
itemHoverFrame.main.Visible = true
itemHoverFrame.main.thumbnail.Image = displayAbilityInfo.image
local abilityName = displayAbilityInfo.name and localization.translate(displayAbilityInfo.name, itemHoverFrame.header.itemName) or "???"
local unlearnedString = "(" .. localization.translate("Unlearned", itemHoverFrame.header.itemName) .. ")"
itemHoverFrame.header.itemName.Text = abilityName .. " " .. ((displayLevel > 1 and "+"..(displayLevel-1)) or (displayLevel == 0 and unlearnedString) or "")
cleanup()
module.source = "ability"
local levelToUse = (level > 0 and level) or 1
local abilityStats
if abilityInfo.statistics then
abilityStats = ability_utilities.getAbilityStatisticsForRank(abilityInfo, levelToUse)
end
local tier = -1
if displayLevel > 0 then
tier = 1
if displayLevel > 1 then
tier = 2
end
end
local comparisonStats
if comparisonInfo and comparisonLevel then
comparisonStats = ability_utilities.getAbilityStatisticsForRank(comparisonInfo, comparisonLevel)
end
if displayLevel ~= 0 and abilityStats and abilityStats.tier and abilityStats.tier > tier then
tier = abilityStats.tier
end
if displayLevel ~= 0 and comparisonStats and comparisonStats.tier and comparisonStats.tier > tier then
tier = comparisonStats.tier
end
local titleColor = module.tierColors[tier]
local desc = displayAbilityInfo.description and localization.translate(displayAbilityInfo.description, itemHoverFrame.main.mainContents.itemDescription)
itemHoverFrame.main.mainContents.itemDescription.Text = desc or "???"
itemHoverFrame.main.mainContents.equippableClasses.Visible = false
itemHoverFrame.main.mainContents.abilityInfo.Visible = false
-- clear previous stats
for i, obj in pairs(itemHoverFrame.stats.container:GetChildren()) do
if not obj:IsA("UIListLayout") then
obj:Destroy()
end
end
local numStats = 0
if abilityInfo.statistics then
if comparisonStats then
for stat,value in pairs(comparisonStats) do
if not abilityStats[stat] then
abilityStats[stat] = 0
end
end
end
for stat,value in pairs(abilityStats) do
if stat ~= "tier" and string.sub(stat, 1, 1) ~= "_" and not ((stat == "manaCost" or stat == "cost") and value == 0) then
local prefix = stat
local displayValueFunction
local statTextColor = Color3.fromRGB(255,255,255)
local statPriority = 5
local operator = ":"
if stat == "damageMultiplier" then
prefix = "Power"
displayValueFunction = function(displayValue)
return displayValue * 100
end
statPriority = 1
elseif stat == "healing" then
statPriority = 2
elseif stat == "walkspeed" then
prefix = "Movement Speed"
operator = " +"
elseif stat == "staminaRecovery" then
prefix = "Stamina Recovery"
displayValueFunction = function(displayValue)
return displayValue * 100 .. "%"
end
operator = " +"
elseif stat == "manaRestored" then
prefix = "MP Restored"
elseif stat == "cooldown" then
displayValueFunction = function(displayValue)
return displayValue .. "s"
end
statTextColor = Color3.fromRGB(160,160,160)
statPriority = 6
elseif stat == "range" then
displayValueFunction = function(displayValue)
return displayValue .. "m"
end
statPriority = 3
elseif stat == "cost" or stat == "manaCost" then
prefix = "MP"
statTextColor = Color3.fromRGB(0, 152, 255)
statPriority = 7
elseif stat == "healthCost" then
prefix = "HP"
statTextColor = Color3.fromRGB(226, 34, 40)
statPriority = 7
end
prefix = string.upper(string.sub(prefix,1,1)) .. string.sub(prefix,2)
local nextLevelString
if comparisonStats and (comparisonStats[stat] == nil or comparisonStats[stat] ~= value) then
local comparisonValue = comparisonStats[stat] or 0
local comparisonDisplayValue = displayValueFunction and displayValueFunction(comparisonValue) or comparisonValue
nextLevelString = {
text = "→ " .. comparisonDisplayValue;
textColor3 = Color3.fromRGB(220, 181, 25);
font = Enum.Font.SourceSansBold;
textSize = 23;
}
end
local displayValue = displayValueFunction and displayValueFunction(value) or value
local fragment = uiCreator.createTextFragmentLabels(itemHoverFrame.stats.container,{
--[[
{text = (statValue >= 0 and "+" or "") .. statValue .. (statName:find("Percent") and "%" or "")},
{text = statName:gsub("Percent", ""):gsub("Flat", "")}
]]
{text = prefix..operator, textColor3 = statTextColor; textSize = 19;},
{text = tostring(displayValue); font = Enum.Font.SourceSansBold, textColor3 = statTextColor; textSize = 23;},
nextLevelString
})
numStats = numStats + 1
fragment.LayoutOrder = statPriority
end
end
end
local itemNameTextSize = textService:GetTextSize(itemHoverFrame.header.itemName.Text, titleTextSize, itemHoverFrame.header.itemName.Font, Vector2.new())
local itemDescriptionTextSize = textService:GetTextSize(itemHoverFrame.main.mainContents.itemDescription.Text, itemHoverFrame.main.mainContents.itemDescription.TextSize, itemHoverFrame.main.mainContents.itemDescription.Font, Vector2.new())
local itemNameLines = 1 -- math.ceil(itemNameTextSize.X / itemHoverFrame.header.itemName.AbsoluteSize.X)
local itemDescriptionLines = math.ceil(itemDescriptionTextSize.X / itemHoverFrame.main.mainContents.itemDescription.AbsoluteSize.X)
--[[
local headerYSize = itemNameTextSize.Y * itemNameLines + itemHoverFrame.header.itemType.TextBounds.Y + 5
itemHoverFrame.header.Size = UDim2.new(1, -10, 0, headerYSize)
itemHoverFrame.header.itemName.Size = UDim2.new(1, -5, 0, itemNameTextSize.Y * itemNameLines)
itemHoverFrame.stats.container.Size = UDim2.new(1, 0, 0, numStats * 18)
itemHoverFrame.stats.Size = UDim2.new(1, 0, 0, numStats * 18 + 20)
itemHoverFrame.main.mainContents.itemDescription.Size = UDim2.new(1, 0, 0, itemDescriptionTextSize.Y * itemDescriptionLines + 10)
--local y = headerYSize + itemDescriptionTextSize.Y * itemDescriptionLines + 20 + numStats * 18 + 30 + 10
local y = headerYSize + itemHoverFrame.main.mainContents.AbsoluteSize.Y + numStats * 18 + 20 + (numStats > 0 and 20 or 0)
]]
local headerYSize = itemNameTextSize.Y * itemNameLines
itemHoverFrame.header.Size = UDim2.new(1, 0, 0, headerYSize)
itemHoverFrame.header.itemName.Size = UDim2.new(0, itemNameTextSize.X, 0, itemNameTextSize.Y * itemNameLines)
itemHoverFrame.header.itemName.stars.Visible = false
itemHoverFrame.stats.container.Size = UDim2.new(1, 0, 0, numStats * 19)
itemHoverFrame.stats.Size = UDim2.new(1, 0, 0, numStats * 19)
itemHoverFrame.stats.Visible = numStats > 0
itemHoverFrame.main.mainContents.itemDescription.Size = UDim2.new(1, 0, 0, itemDescriptionTextSize.Y * itemDescriptionLines + 10)
local y = 16
for i,child in pairs(itemHoverFrame:GetChildren()) do
if child:IsA("GuiObject") and child.Visible then
y = y + child.AbsoluteSize.Y + itemHoverFrame.UIListLayout.Padding.Offset
end
end
local openSize = UDim2.new(0, 320 + 4, 0, y + 4)
rootFrame.Size = openSize
itemHoverFrame.header.itemName.TextColor3 = titleColor
itemHoverFrame.header.itemName.cuteDecor.ImageColor3 = titleColor
itemHoverFrame.main.thumbnail.BorderColor3 = titleColor
rootFrame.UIScale.Scale = Modules.input.menuScale
reposition()
rootFrame.Visible = true
rootFrame.UIScale.Scale = Modules.input.menuScale
rootFrame.contents.Visible = true
end
network:create("populateItemHoverFrameWithAbility","BindableFunction","OnInvoke", populateItemHoverFrameWithAbility)
local function populateItemHoverFrame(itemBaseData, newSource, inventorySlotData, additionalInfo)
itemHoverFrame.header.itemName.Position = UDim2.new(0, 18,0, 3)
itemHoverFrame.header.itemName.cuteDecor.Visible = true
itemHoverFrame.header.itemName.cuteDecor.Image = "rbxassetid://2528902611"
-- itemHoverFrame.main.thumbnail.BackgroundTransparency = 0.2
itemHoverFrame.main.thumbnailBG.Visible = true
Modules.input.setCurrentFocusFrame(rootFrame)
itemHoverFrame.main.mainContents.abilityInfo.Visible = false
itemHoverFrame.main.Visible = true
newSource = newSource or "pickup"
module.source = newSource
if itemBaseData == nil then
rootFrame.Visible = false
rootFrame.contents.Visible = false
module.source = "none"
return false
end
rootFrame.Size = UDim2.new(0, 320 + 4, 0, 40 + 4)
if not rootFrame.Visible then
rootFrame.contents.Visible = false
end
itemHoverFrame.header.itemName.AutoLocalize = false
if inventorySlotData and inventorySlotData.customName then
itemHoverFrame.header.itemName.Font = Enum.Font.SourceSansItalic
-- itemHoverFrame.header.itemName.AutoLocalize = false
else
itemHoverFrame.header.itemName.Font = Enum.Font.SourceSansBold
-- itemHoverFrame.header.itemName.AutoLocalize = true
end
cleanup()
local itemBaseName = itemBaseData.name and localization.translate(itemBaseData.name, itemHoverFrame.header.itemName)
local itemname = inventorySlotData and inventorySlotData.customName or itemBaseName or "???"
local attribute = inventorySlotData.attribute
if attribute then
local attributeData = itemAttributes[attribute]
if attributeData then
if attributeData.color then
itemHoverFrame.main.thumbnailBG.ImageColor3 = attributeData.color
end
if attributeData.prefix and not inventorySlotData.customName then
local attributeName = localization.translate(attributeData.prefix, itemHoverFrame.header.itemName)
itemname = attributeName .. " " .. itemname
end
end
end
itemname = itemname .. ((inventorySlotData and inventorySlotData.upgrades and inventorySlotData.upgrades > 0 and " +"..(inventorySlotData.successfulUpgrades or 0)) or "")
itemHoverFrame.header.itemName.Text = itemname
itemHoverFrame.main.mainContents.itemDescription.AutoLocalize = false
local desc = itemBaseData.description and localization.translate(itemBaseData.description, itemHoverFrame.main.mainContents.itemDescription)
itemHoverFrame.main.mainContents.itemDescription.Text = desc or "Unknown"
itemHoverFrame.header.itemType.Text = itemBaseData.category and string.upper(itemBaseData.category:sub(1, 1)) .. itemBaseData.category:sub(2) or "Unknown"
--[[
local itemNameTextSize = textService:GetTextSize(itemBaseData.name, 20, Enum.Font.SourceSansBold, Vector2.new())
local itemDescriptionTextSize = textService:GetTextSize(itemBaseData.description, 14, Enum.Font.SourceSansItalic, Vector2.new())
]]
local itemNameTextSize = textService:GetTextSize(itemHoverFrame.header.itemName.Text, titleTextSize, itemHoverFrame.header.itemName.Font, Vector2.new())
local itemDescriptionTextSize = textService:GetTextSize(itemHoverFrame.main.mainContents.itemDescription.Text, itemHoverFrame.main.mainContents.itemDescription.TextSize, itemHoverFrame.main.mainContents.itemDescription.Font, Vector2.new())
local push = 0
-- old scrolls indicator (defunct)
--[[
if inventorySlotData.enchantments and #inventorySlotData.enchantments > 0 then
for i, child in pairs(itemHoverFrame.enchantments:GetChildren()) do
if child:IsA("GuiObject") then
child:Destroy()
end
end
for i, enchantment in pairs(inventorySlotData.enchantments) do
local enchantmentBaseData = itemLookup[enchantment.id]
local enchantment = enchantmentBaseData.enchantments[enchantment.state]
local frame = script.enchantment:Clone()
local tierColor = module.tierColors[enchantment.tier + 1]
frame.frame.ImageColor3 = tierColor
frame.shine.ImageColor3 = tierColor
frame.item.Image = enchantmentBaseData.image
frame.LayoutOrder = 10-(enchantment.tier)
frame.Parent = itemHoverFrame.enchantments
Modules.fx.setFlash(frame.frame, enchantment.tier >= 1)
end
itemHoverFrame.enchantments.Visible = true
push = push + 36
end
]]
if (inventorySlotData and inventorySlotData.soulbound) or itemBaseData.soulbound then
itemHoverFrame.soulbound.Visible = true
push = push + 24
end
if itemBaseData.perks then
for perkName, active in pairs(itemBaseData.perks) do
if active then
local perkData = perkLookup[perkName]
if perkData then
local perk = script.perk:Clone()
perk.title.Text = perkData.title
perk.description.Text = perkData.description
if perkData.color then
perk.ImageColor3 = perkData.color
end
perk.Visible = true
perk.Parent = itemHoverFrame
push = push + 46
end
end
end
end
if additionalInfo and additionalInfo.notOwned then
itemHoverFrame.notOwned.Visible = true
push = push + 24
end
local itemNameLines = 1
-- local itemNameLines = math.ceil(itemNameTextSize.X / itemHoverFrame.header.itemName.AbsoluteSize.X)
local itemDescriptionLines = math.ceil(itemDescriptionTextSize.X / itemHoverFrame.main.mainContents.itemDescription.AbsoluteSize.X)
itemHoverFrame.main.thumbnail.Image = itemBaseData.image
itemHoverFrame.main.mainContents.equippableClasses.Visible = itemBaseData.category and itemBaseData.category == "equipment"
itemHoverFrame.main.mainContents.equippableClasses.requirements.reqLevel.Visible = false
-- itemHoverFrame.main.mainContents.equippableClasses.requirements.reqLevel
if itemBaseData.minLevel then
itemHoverFrame.main.mainContents.equippableClasses.requirements.reqLevel.Visible = true
itemHoverFrame.main.mainContents.equippableClasses.requirements.reqLevel.Text = "REQ Lvl. "..itemBaseData.minLevel or 0
local level = network:invoke("getCacheValueByNameTag", "level") or 1
itemHoverFrame.main.mainContents.equippableClasses.requirements.reqLevel.TextColor3 = Color3.fromRGB(203, 203, 203)
if itemBaseData.minLevel > level then
itemHoverFrame.main.mainContents.equippableClasses.requirements.reqLevel.TextColor3 = Color3.fromRGB(203, 69, 71)
end
end
local canEquipClass = false
local class = itemBaseData.minimumClass or "adventurer"
class = class:lower()
if class == "adventurer" then
canEquipClass = true
elseif network:invoke("getCacheValueByNameTag", "class"):lower() == class then
canEquipClass = true
else
local abilityBooks = network:invoke("getCacheValueByNameTag", "abilityBooks")
canEquipClass = abilityBooks[class] ~= nil
end
local classTree = {
["hunter"] = "hunter";
["assassin"] = "hunter";
["trickster"] = "hunter";
["ranger"] = "hunter";
["mage"] = "mage";
["sorcerer"] = "mage";
["warlock"] = "mage";
["cleric"] = "mage";
["warrior"] = "warrior";
["paladin"] = "warrior";
["knight"] = "warrior";
["berserker"] = "warrior";
}
local classRoot = classTree[class]
classRoot = classRoot or class
for i,obj in pairs(itemHoverFrame.main.mainContents.equippableClasses:GetChildren()) do
if obj:IsA("ImageLabel") then
if classRoot and classRoot == obj.Name then
obj.ImageTransparency = 0
obj.Image = "rbxgameasset://Images/emblem_"..class
else
obj.ImageTransparency = 0.7
obj.Image = "rbxgameasset://Images/emblem_"..obj.Name
end
if canEquipClass then
obj.ImageColor3 = Color3.fromRGB(173, 173, 173)
else
obj.ImageColor3 = Color3.fromRGB(203, 69, 71)
end
end
end
-- clear previous stats
for i, obj in pairs(itemHoverFrame.stats.container:GetChildren()) do
if not obj:IsA("UIListLayout") then
obj:Destroy()
end
end
local numStats = 0
local totalStats, statBonuses = getItemInfo(itemBaseData, inventorySlotData)
local equippedStats
local equipped = network:invoke("getCacheValueByNameTag", "equipment")
-- look for an equipped item to compare this to
local correspendingEquipmentInventoryData
for i,equipment in pairs(equipped) do
if itemBaseData.equipmentSlot and equipment.position and itemBaseData.equipmentSlot == equipment.position then
correspendingEquipmentInventoryData = equipment
break
end
end
if newSource ~= "equipment" then
if correspendingEquipmentInventoryData then
local correspondingEquipmentBaseData = itemLookup[correspendingEquipmentInventoryData.id]
if correspondingEquipmentBaseData then
equippedStats = getItemInfo(correspondingEquipmentBaseData,correspendingEquipmentInventoryData)
end
end
end
for statName,statValue in pairs(totalStats) do
if statValue ~= 0 then
local statDisplayName = #statName <=3 and statName:upper() or statName:sub(1,1):upper() .. statName:sub(2)
local statDisplayValue
local statTextColor = Color3.new(160,160,160)
local statPriority = 5
local statValueSize = 24
if statName == "baseDamage" then
statDisplayName = "Weapon Attack"
statPriority = 1
elseif statName == "defense" then
statPriority = 1
elseif statName == "woodcutting" then
statPriority = 2
statDisplayName = "Woodcutting Power"
elseif statName == "mining" then
statPriority = 2
statDisplayName = "Mining Power"
elseif statName == "damageTakenMulti" then
statDisplayName = "Damage Taken"
statPriority = 3
statDisplayValue = statValue * 100 .. "%" .. (statBonuses[statName] and statBonuses[statName] > 0 and " (+" .. statBonuses[statName] * 100 .. "%)" or "")
elseif statName == "damageGivenMulti" then
statDisplayName = "Damage Given"
statPriority = 3
statDisplayValue = statValue * 100 .. "%" .. (statBonuses[statName] and statBonuses[statName] > 0 and " (+" .. statBonuses[statName] * 100 .. "%)" or "")
elseif statName == "magicalDamage" then
statDisplayName = "Magic Attack"
statPriority = 3
elseif statName == "rangedDamage" then
statDisplayName = "Ranged Attack"
statPriority = 3
elseif statName == "physicalDamage" then
statDisplayName = "Physical Attack"
statPriority = 3
elseif statName == "magicalDefense" then
statDisplayName = "Magic Defense"
statPriority = 3
elseif statName == "rangedDefense" then
statDisplayName = "Projectile Defense"
statPriority = 3
elseif statName == "physicalDefense" then
statDisplayName = "Physical Defense"
statPriority = 3
elseif statName == "maxMana" then
statDisplayName = "Max MP"
statPriority = 4
elseif statName == "maxHealth" then
statDisplayName = "Max HP"
statPriority = 4
elseif statName == "healthRegen" then
statDisplayName = "HP Recovery"
statPriority = 5
statDisplayValue = statValue .. "%" .. (statBonuses[statName] and statBonuses[statName] > 0 and " (+" .. statBonuses[statName] .. "%)" or "")
elseif statName == "manaRegen" then
statDisplayName = "MP Recovery"
statPriority = 5
statDisplayValue = statValue .. "%" .. (statBonuses[statName] and statBonuses[statName] > 0 and " (+" .. statBonuses[statName] .. "%)" or "")
elseif statName == "criticalStrikeChance" then
statDisplayName = "Critical Hits"
statDisplayValue = statValue * 100 .. "%" .. (statBonuses[statName] and statBonuses[statName] > 0 and " (+" .. statBonuses[statName] * 100 .. "%)" or "")
statPriority = 5
elseif statName == "blockChance" then
statDisplayName = "Block Chance"
statDisplayValue = statValue * 100 .. "%" .. (statBonuses[statName] and statBonuses[statName] > 0 and " (+" .. statBonuses[statName] * 100 .. "%)" or "")
statPriority = 5
elseif statName == "greed" then
statDisplayName = "Greed"
statPriority = 6
statDisplayValue = statValue * 100 .. "%" .. (statBonuses[statName] and statBonuses[statName] > 0 and " (+" .. statBonuses[statName] * 100 .. "%)" or "")
elseif statName == "wisdom" then
statDisplayName = "XP"
statPriority = 6
statDisplayValue = statValue * 100 .. "%" .. (statBonuses[statName] and statBonuses[statName] > 0 and " (+" .. statBonuses[statName] * 100 .. "%)" or "")
elseif statName:find("_totalMultiplicative") then
statDisplayName = statName:gsub("_totalMultiplicative", "")
statDisplayName = statDisplayName:sub(1, 1):upper()..statDisplayName:sub(2)
statPriority = 7
statDisplayValue = (statValue * 100).."%"
elseif statName == "attackSpeed" then
statDisplayName = "Attack Speed"
statPriority = 10
statValueSize = 19
statDisplayValue = ({"Very slow", "Slow", "Normal", "Fast", "Very fast"})[statValue] or statValue
end
-- if a name isn't right, just change it here
local replacements = {
["Walkspeed"] = "Movement Speed",
}
statDisplayName = replacements[statDisplayName] or statDisplayName
if not statDisplayValue then
statDisplayValue = (statValue .. (statBonuses[statName] and
((statBonuses[statName] > 0 and " (+" .. statBonuses[statName] .. ")" ) or
(statBonuses[statName] < 0 and " (" .. statBonuses[statName] .. ")"))
or ""))
end
local comparisonExtension
if equippedStats and equippedStats[statName] and equippedStats[statName] ~= 0 then
local difference = ((statValue - equippedStats[statName]) / equippedStats[statName])
if statName == "damageTakenMulti" then
difference = -difference
end
if difference == difference then
local comparisonText
local comparisonColor
if statValue == equippedStats[statName] or math.abs(difference) <= 0.01 then
--comparisonText = "="
elseif difference >= 0 then
comparisonText = "↑"
comparisonColor = Color3.fromRGB(150,255,150)
--[[
if difference >= 1 then
comparisonText = "↑↑↑"
elseif difference >= 0.25 then
comparisonText = "↑↑"
end
]]
else
comparisonText = "↓"
comparisonColor = Color3.fromRGB(255,150,150)
--[[
if difference <= -0.5 then
comparisonText = "↓↓↓"
elseif difference <= -0.2 then
comparisonText = "↓↓"
end
]]
end
if comparisonText then
comparisonExtension = {text = comparisonText; textSize=20; font=Enum.Font.SourceSansBold; textColor3 = comparisonColor or Color3.new(0.6,0.6,0.6), autoLocalize = false}
end
end
end
if statValue > 0 then
statDisplayValue = statDisplayValue
end
statDisplayName = localization.translate(statDisplayName, itemHoverFrame.stats.container) .. ": "
local textContainer = uiCreator.createTextFragmentLabels(itemHoverFrame.stats.container, {
--[[
{text = (statValue >= 0 and "+" or "") .. statValue .. (statName:find("Percent") and "%" or "")},
{text = statName:gsub("Percent", ""):gsub("Flat", "")}
]]
{text = statDisplayName, textColor3 = statTextColor, textSize = 19, autoLocalize = false},
{text = statDisplayValue, font = Enum.Font.SourceSansBold, textSize = statValueSize, textColor3 = statTextColor, autoLocalize = false},
comparisonExtension
})
textContainer.LayoutOrder = statPriority
numStats = numStats + 1
if statBonuses[statName] and statBonuses[statName] > 0 then
if statName == "baseDamage" then
local totalDamage = totalStats["baseDamage"] or 0.1
local modifierBaseDamage = statBonuses["baseDamage"] or 0
local difference = totalDamage / (totalDamage - modifierBaseDamage)
end
end
end
end
if inventorySlotData and inventorySlotData.upgrades then
local maxUpgrades = (itemBaseData.maxUpgrades or 0) + (inventorySlotData.bonusUpgrades or 0)
local upgradesLeft = maxUpgrades - inventorySlotData.upgrades
local suffix = upgradesLeft == 1 and "upgrade attempt remaining" or "upgrade attempts remaining"
local label = uiCreator.createTextFragmentLabels(itemHoverFrame.stats.container, {
{text = (tostring(upgradesLeft).." "..suffix); font = Enum.Font.SourceSans; textColor3 = Color3.new(1,1,1); textTransparency = 0.5; autoLocalize = true}
})
label.LayoutOrder = 7
numStats = numStats + 1
end
--[[
rootFrame.itemType.Visible = true
for i,itemTypeLabel in pairs(rootFrame.itemType:GetChildren()) do
if itemBaseData.category and itemBaseData.category == itemTypeLabel.Name then
itemTypeLabel.Visible = true
else
itemTypeLabel.Visible = false
end
end
]]
local titleColor
if inventorySlotData then
titleColor = getTitleColorForInventorySlotData(inventorySlotData)
end
titleColor = titleColor or itemBaseData.nameColor or Color3.new(1,1,1)
itemHoverFrame.header.itemName.TextColor3 = titleColor
itemHoverFrame.header.itemName.cuteDecor.ImageColor3 = titleColor
local itemType = itemBaseData.itemType
itemHoverFrame.header.itemName.cuteDecor.Image = "rbxgameasset://Images/category_"..itemType
local maxUpgrades = itemBaseData.maxUpgrades or 0
-- stars next to item name (defunct)
--[[
for i,star in pairs(itemHoverFrame.header.itemName.stars:GetChildren()) do
if star:IsA("ImageLabel") then
local n = tonumber(star.Name)
star.ImageColor3 = Color3.fromRGB(255, 255, 255)
star.ImageTransparency = 0.9
star.LayoutOrder = 12
star.Visible = n <= maxUpgrades
star.Image = "rbxassetid://3763830087"
end
end
local n = 0
if inventorySlotData.enchantments then
for i, enchantmentData in pairs(inventorySlotData.enchantments) do
local enchantmentBaseData = itemLookup[enchantmentData.id].enchantments[enchantmentData.state]
local star = itemHoverFrame.header.itemName.stars:FindFirstChild(tostring(i))
star.ImageTransparency = 0
local tier = enchantmentBaseData.tier or 0
-- star.ImageColor3 = module.tierColors[tier]
star.ImageColor3 = titleColor
star.LayoutOrder = 10 - tier
star.Visible = true
n = n + 1
end
end
local fails = math.clamp((inventorySlotData.upgrades or 0) - (inventorySlotData.successfulUpgrades or 0), 0, maxUpgrades)
for i=1,fails do
local star = itemHoverFrame.header.itemName.stars:FindFirstChild(tostring(n+i))
if star then
star.LayoutOrder = 11
star.ImageTransparency = 0
star.ImageColor3 = module.tierColors[-1]
star.Image = "rbxassetid://3799648173"
end
end
]]
itemHoverFrame.main.thumbnailBG.frame.ImageColor3 = titleColor
itemHoverFrame.main.thumbnailBG.shine.ImageColor3 = titleColor
itemHoverFrame.main.thumbnail.BorderColor3 = titleColor
--inventorySlotData and inventorySlotData.upgrades
itemHoverFrame.main.thumbnail.ImageColor3 = Color3.new(1,1,1)
local customTag = (inventorySlotData and inventorySlotData.customTag) or (itemBaseData and itemBaseData.customTag)
if customTag then
local label = uiCreator.createTextFragmentLabels(itemHoverFrame.stats.container,customTag)
label.LayoutOrder = 12
numStats = numStats + 1
end
local perks = {}
if itemBaseData.perkData then
table.insert(perks, itemBaseData.perkData)
end
if inventorySlotData.perkData then
table.insert(perks, inventorySlotData.perkData)
end
for i,perkData in pairs(perks) do
local label = uiCreator.createTextFragmentLabels(itemHoverFrame.stats.container, {
{text = itemBaseData.perkData.title; font = Enum.Font.SourceSansBold; textColor3 = module.tierColors[perkData.tier or 1]; textTransparency = 0; autoLocalize = true}
})
label.LayoutOrder = 9
numStats = numStats + 1
end
if inventorySlotData and inventorySlotData.dye then
itemHoverFrame.main.thumbnail.ImageColor3 = Color3.fromRGB(inventorySlotData.dye.r, inventorySlotData.dye.g, inventorySlotData.dye.b)
local label = uiCreator.createTextFragmentLabels(itemHoverFrame.stats.container, {
{text = "Dyed"; font = Enum.Font.SourceSansBold; textColor3 = Color3.fromRGB(inventorySlotData.dye.r, inventorySlotData.dye.g, inventorySlotData.dye.b); textTransparency = 0}
})
label.LayoutOrder = 10
numStats = numStats + 1
end
if inventorySlotData and inventorySlotData.customStory then
local label = uiCreator.createTextFragmentLabels(itemHoverFrame.stats.container, {
{text = ""; font = Enum.Font.SourceSans; textTransparency = 1; autoLocalize = false;}
})
label.LayoutOrder = 11
local label = uiCreator.createTextFragmentLabels(itemHoverFrame.stats.container, {
{text = "\""..inventorySlotData.customStory.."\""; font = Enum.Font.Antique; textColor3 = Color3.new(1, 1, 1); textTransparency = 0.2; autoLocalize = false;}
})
label.LayoutOrder = 11
numStats = numStats + 2
end
local headerYSize = itemNameTextSize.Y * itemNameLines
itemHoverFrame.header.Size = UDim2.new(1, 0, 0, headerYSize)
itemHoverFrame.header.itemName.Size = UDim2.new(0, itemNameTextSize.X, 0, itemNameTextSize.Y * itemNameLines)
-- itemHoverFrame.header.itemName.stars.Visible = true
itemHoverFrame.stats.container.Size = UDim2.new(1, 0, 0, numStats * 20)
itemHoverFrame.stats.Size = UDim2.new(1, 0, 0, numStats * 20)
itemHoverFrame.stats.Visible = numStats > 0
itemHoverFrame.main.mainContents.itemDescription.Size = UDim2.new(1, 0, 0, itemDescriptionTextSize.Y * itemDescriptionLines + 10)
--local y = headerYSize + itemDescriptionTextSize.Y * itemDescriptionLines + 20 + numStats * 18 + 30 + 10
-- local y = headerYSize + mainContentsSize + numStats * 18 + (numStats > 0 and 20 or 0) + push
local y = 10
for i,child in pairs(itemHoverFrame:GetChildren()) do
if child:IsA("GuiObject") and child.Visible then
y = y + child.AbsoluteSize.Y + itemHoverFrame.UIListLayout.Padding.Offset
end
end
local openSize = UDim2.new(0, 320 + 4, 0, y + 4)
rootFrame.Size = openSize
rootFrame.contents.Visible = true
if module.source ~= "pickup" and module.source ~= "none" then
rootFrame.UIScale.Scale = Modules.input.menuScale
reposition()
rootFrame.Visible = true
end
--rootFrame.Size = openSize
-- returns the
--return
-- tweenService:Create(itemHoverFrame, BASIC_TWEEN_INFO, {Size = openSize}),
-- tweenService:Create(itemHoverFrame, BASIC_TWEEN_INFO, {Size = UDim2.new(0, 195, 0, headerYSize + itemDescriptionTextSize.Y * itemDescriptionLines + 15 + (18 + 5) + 5)})
end
network:create("populateItemHoverFrame","BindableFunction","OnInvoke",populateItemHoverFrame)
local function getItemBaseDataFromItemsPart(itemPart)
local itemDataModule = itemsDataFolder:FindFirstChild(itemPart.Name)
if itemDataModule then
local itemBaseData = require(itemDataModule)
itemBaseData.physItemName = itemPart.Name
return itemBaseData
end
end
local function getClosestItem()
if player.Character and player.Character.PrimaryPart then
local closestItem, closestItemDistance = nil, ITEM_ACQUISITION_RANGE
for i, item in pairs(itemsFolder:GetChildren()) do
local distanceAway = utilities.magnitude(player.Character.PrimaryPart.Position - item.Position)
if (not closestItem and distanceAway <= closestItemDistance) or (closestItem and distanceAway < closestItemDistance) then
if utilities.playerCanPickUpItem(player, item) then
closestItem = item
closestItemDistance = distanceAway
end
end
end
return closestItem, closestItemDistance
end
end
--game:GetService("RunService"):BindToRenderStep("itemHoverRepos",Enum.RenderPriority.Camera - 5,reposition)
local function onInputChanged(inputObject)
if inputObject.UserInputType == Enum.UserInputType.MouseMovement then
local hitPart, hitPosition, distanceAway = raycastFromScreenPositionForItems(inputObject.Position.X, inputObject.Position.Y)
if hitPart then
local item = (hitPart.Parent == itemsFolder and hitPart) or (hitPart.Parent.Parent == itemsFolder and hitPart.Parent) or (hitPart.Parent.Parent.Parent == itemsFolder and hitPart.Parent.Parent)
local itemBaseData = getItemBaseDataFromItemsPart(item)
local isOwner = utilities.playerCanPickUpItem(player, item)
local additionalInfo = {}
if not isOwner then
additionalInfo.notOwned = true
end
if itemBaseData then
if not currentItemHoverData or currentItemHoverData.item ~= item then
if item:FindFirstChild("metadata") and item.metadata.Value ~= "" then
local regularSizeAnimation, pickupSizeAnimation = populateItemHoverFrame(itemBaseData, "pickup", game:GetService("HttpService"):JSONDecode(item.metadata.Value), additionalInfo)
else
local regularSizeAnimation, pickupSizeAnimation = populateItemHoverFrame(itemBaseData, "pickup", nil, additionalInfo)
end
--[[
currentItemHoverData = {
item = hitPart;
itemBaseData = itemBaseData;
regularSizeAnimation = regularSizeAnimation;
pickupSizeAnimation = pickupSizeAnimation;
}
]]
end
--if distanceAway <= ITEM_ACQUISITION_RANGE then
-- currentItemHoverData.pickupSizeAnimation:Play()
--else
-- currentItemHoverData.regularSizeAnimation:Play()
--end
if not rootFrame.Visible then
module.source = "pickup"
rootFrame.UIScale.Scale = Modules.input.menuScale
reposition()
rootFrame.Visible = true
end
--[[
local targetPosition = UDim2.new(0, inputObject.Position.X + 20, 0, inputObject.Position.Y + 10)
-- clamp Y axis to bottom side of the screen
if targetPosition.Y.Offset + rootFrame.AbsoluteSize.Y > gameUI.AbsoluteSize.Y then
targetPosition = UDim2.new(0, targetPosition.X.Offset, 0, gameUI.AbsoluteSize.Y - rootFrame.AbsoluteSize.Y)
end
-- clamp X axis to right side of the screen
if targetPosition.X.Offset + rootFrame.AbsoluteSize.X > gameUI.AbsoluteSize.X then
targetPosition = UDim2.new(0, gameUI.AbsoluteSize.X - rootFrame.AbsoluteSize.X, 0, targetPosition.Y.Offset)
end
rootFrame.Position = targetPosition
]]
else
if rootFrame.Visible and module.source == "pickup" then
rootFrame.Visible = false
end
end
else
currentItemHoverData = nil
if rootFrame.Visible and module.source == "pickup" then
rootFrame.Visible = false
end
end
end
end
local itemBackup
local function outQuad(t, d)
local b = 0
local c = 1
t = t / d
return -c * t * (t - 2) + b
end
local RunService = game:GetService("RunService")
local function attract(representation)
local startpos = representation.CFrame
local start = tick()
local duration = 0.5
while tick() - start <= duration do
RunService.Heartbeat:wait()
if representation then
if game.Players.LocalPlayer.Character and game.Players.LocalPlayer.Character.PrimaryPart then
representation.CFrame = startpos:lerp(game.Players.LocalPlayer.Character.PrimaryPart.CFrame, outQuad(tick()-start,duration))
else
break
end
end
end
end
local function onPickUpItemRequestFromServer(metadata, success, value, representationOverride, resultMessage)
-- bere edit: show display name
local realItem = itemLookup[metadata.id]
if success then
if realItem.id == 1 then
--[[
local prompt = uiCreator.createInteractionPrompt(nil,
{text = "Obtained"; textColor3 = Color3.fromRGB(120,120,120)},
{text = (value or 0).." gold"; textColor3 = Color3.fromRGB(255, 255, 0)}
)
prompt:setBackgroundColor3(Color3.fromRGB(190, 190, 190))
prompt:setExpireTime(1.5)
]]
uiCreator.showCurrency(value)
elseif realItem.autoConsume and resultMessage then
local prompt = uiCreator.createInteractionPrompt(nil,
{text = resultMessage; textColor3 = Color3.fromRGB(70,70,70)}
)
prompt:setBackgroundColor3(Color3.fromRGB(190, 190, 190))
prompt:setExpireTime(5)
if realItem.useSound then
utilities.playSound(realItem.useSound)
end
else
uiCreator.showItemPickup(realItem, value, metadata)
-- auto-bind first 2 consumables
-- edit: dont
--[[
if realItem.category == "consumable" and realItem.activationEffect then
local hotbarSlotPairing = network:invoke("getHotbarSlotPairing")
local itemAlreadyExists
for i, hotbarSlot in pairs(hotbarSlotPairing) do
local hotbarButton = hotbarSlot.button
local hotbarData = hotbarSlot.data
if hotbarData and hotbarData.id and hotbarData.id == realItem.id then
itemAlreadyExists = true
end
end
if not itemAlreadyExists then
for i, hotbarSlot in pairs(hotbarSlotPairing) do
local hotbarButton = hotbarSlot.button
local hotbarData = hotbarSlot.data
if hotbarData == nil or hotbarData.id == nil or hotbarData.id <= 0 then
local num = string.gsub(hotbarButton.Name,"[^.0-9]+","")
num = tonumber(num)
if num == 1 or num == 2 then -- 1 and 2 reserved for consumables
local bindsuccess = network:invokeServer("registerHotbarSlotData", mapping.dataType.item, realItem.id, tonumber(num))
if bindsuccess then
if game.ReplicatedStorage.sounds:FindFirstChild("idolPickup") then
utilities.playSound("idolPickup")
--game.ReplicatedStorage.sounds.idolPickup:Play()
end
for i=1,4 do
local flare = hotbarButton.flare:Clone()
flare.Name = "flareCopy"
flare.Parent = hotbarButton
flare.Visible = true
flare.Size = UDim2.new(1,4,1,4)
flare.Position = UDim2.new(0.5,0,1,2)
flare.AnchorPoint = Vector2.new(0.5,1)
local y = (180 - 40*i)
local x = (14 - 2*i)
local EndPosition = UDim2.new(0.5,0,1,2)
local EndSize = UDim2.new(1,x,1,y)
tween(flare,{"Position","Size","ImageTransparency"},{EndPosition, EndSize, 1},0.5*i)
end
break
end
end
end
end
end
end
]]
end
local Character = game.Players.LocalPlayer.Character
if itemBackup and Character and Character.PrimaryPart and not representationOverride then
local representation = itemBackup:Clone()
itemBackup:Destroy()
itemBackup = nil
representation.Parent = workspace.CurrentCamera
representation.Anchored = true
representation.CanCollide = false
local sound
if representation.Name == "monster idol" then
sound = utilities.playSound("idolPickup")
elseif representation:FindFirstChild("Legendary") and representation.Legendary.Enabled then
sound = utilities.playSound("legendaryItemPickup")
elseif representation:FindFirstChild("Rare") and representation.Rare.Enabled then
sound = utilities.playSound("rareItemPickup")
else
sound = utilities.playSound("pickup")
end
if representation:IsA("BasePart") then
representation.CanCollide = false
end
for _, descend in pairs(representation:GetDescendants()) do
if descend:IsA("BasePart") then
descend.CanCollide = false
end
end
attract(representation)
if sound then
pcall(function() -- ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
sound.Parent = workspace.CurrentCamera
game.Debris:AddItem(sound, 2)
end)
end
representation:Destroy()
end
else
if resultMessage == "full-inventory" then
network:fire("alert", {text = "Your inventory is full for this item's category."})
warn("inventory full?")
end
end
end
local function itemsRecieved(items, money)
if money and money > 0 then
table.insert(items, {id = 1; value = money})
end
for i,item in pairs(items) do
local realItem = itemLookup[item.id or item.id]
if realItem then
onPickUpItemRequestFromServer(item, true, (item.value or item.stacks), true)
if (realItem.rarity and realItem.rarity == "Legendary") then
utilities.playSound("legendaryItemPickup")
elseif (realItem.rarity and realItem.rarity == "Rare") or (realItem.category and realItem.category == "equipment") then
utilities.playSound("rareItemPickup")
end
end
end
end
network:create("displayRewards", "BindableFunction", "OnInvoke", itemsRecieved)
network:connect("itemsRecieved","OnClientEvent",itemsRecieved)
local itemPickupData = {}
local function processPickupKeyDown(id)
if id == "pickup" and pickupInteractionPromptTable.item then
itemBackup = pickupInteractionPromptTable.item
local success, returnValue = network:invokeServer("pickUpItemRequest", pickupInteractionPromptTable.item)
if not success then
network:fire("alert", {id = pickupInteractionPromptTable.item; text = "Pick-up failed: " .. returnValue})
end
pickupInteractionPromptTable.item = nil
end
end
local keyDown = false
local inputObject
local function inputGained(newInputObject)
inputObject = newInputObject
if inputObject and inputObject.UserInputState == Enum.UserInputState.Begin then
keyDown = true
processPickupKeyDown("pickup")
else
keyDown = false
end
end
module.pickupInputGained = inputGained
network:invoke("addInputAction", "pick up", inputGained, "F")
local function main()
userInputService.InputChanged:connect(onInputChanged)
--network:connect("pickUpItemRequest", "OnClientEvent", onPickUpItemRequestFromServer)--onPickUpItemRequestFromServer)
network:connect("notifyPlayerPickUpItem", "OnClientEvent", onPickUpItemRequestFromServer)
-- todo: make it so this isn't always running somehow?
while wait(1 / 15) do
if inputObject and inputObject.UserInputState == Enum.UserInputState.Begin then
keyDown = true
else
keyDown = false
end
local actionObject = Modules.input.actions["pick up"]
local keybind = actionObject and actionObject.bindedTo or "F"
module.closestItem = getClosestItem()
if not currentItemHoverData then
if module.closestItem then
if pickupInteractionPromptTable.item ~= module.closestItem then
if not pickupInteractionPromptTable.itemBaseData or pickupInteractionPromptTable.itemBaseData.physItemName ~= module.closestItem.Name then
local itemBaseData = getItemBaseDataFromItemsPart(module.closestItem)
if itemBaseData then
pickupInteractionPromptTable.itemBaseData = itemBaseData
end
end
pickupInteractionPromptTable.item = module.closestItem
pickupInteractionPromptTable.prompt:updateTextFragments(false,
{text = "Pick up"},
{text = pickupInteractionPromptTable.itemBaseData and pickupInteractionPromptTable.itemBaseData.name or module.closestItem.Name; textColor3 = Color3.fromRGB(143, 120, 255)}
)
if keyDown and pickupInteractionPromptTable.item then
processPickupKeyDown("pickup")
end
end
else
if not pickupInteractionPromptTable.prompt.isHiding then
pickupInteractionPromptTable.prompt:hide()
pickupInteractionPromptTable.item = nil
pickupInteractionPromptTable.itemBaseData = nil
end
end
else
if pickupInteractionPromptTable.item ~= currentItemHoverData.item then
pickupInteractionPromptTable.item = currentItemHoverData.item
pickupInteractionPromptTable.itemBaseData = currentItemHoverData.itemBaseData
pickupInteractionPromptTable.prompt:updateTextFragments(false,
{text = "Pick up"},
{text = pickupInteractionPromptTable.itemBaseData and pickupInteractionPromptTable.itemBaseData.name or module.closestItem.Name; textColor3 = Color3.fromRGB(143, 120, 255)}
)
if keyDown and pickupInteractionPromptTable.item then
processPickupKeyDown("pickup")
end
end
end
end
end
spawn(main)
end
--
return module
================================================
FILE: src/StarterGui/leaderboard.lua
================================================
-- leaderboard module by berezaa
local module = {}
local player = game.Players.localPlayer
local playerGui = player.PlayerGui
local ui = playerGui.gameUI.leaderboard
function module.init(Modules)
local network = Modules.network
local httpService = game:GetService("HttpService")
-- server browser hookup
spawn(function()
local serversDataValue = game.ReplicatedStorage:WaitForChild("serversData")
local function update()
local servers = serversDataValue.Value ~= "" and httpService:JSONDecode(serversDataValue.Value)
local serverCount = 0
if servers then
for serverId, serverData in pairs(servers) do
if serverId ~= game.JobId then
serverCount = serverCount + 1
end
end
end
ui.top.leave.Visible = servers and serverCount > 0
end
update()
serversDataValue.Changed:connect(update)
-- can't direct ref .open bc of init race condition
ui.top.leave.Activated:Connect(function()
Modules.serverBrowser.open()
end)
end)
local function repareRender(viewport, player, data)
if viewport:FindFirstChild("entity") then
viewport.entity:Destroy()
end
if viewport:FindFirstChild("entity2") then
viewport.entity2:Destroy()
end
local camera = viewport.CurrentCamera
if camera == nil then
camera = Instance.new("Camera")
camera.Parent = viewport
viewport.CurrentCamera = camera
end
local client = player
local character = player.Character
local mask = viewport.characterMask
local characterAppearanceData = {}
characterAppearanceData.equipment = data.equipment or {}
characterAppearanceData.accessories = data.accessories or {}
local characterRender = network:invoke("createRenderCharacterContainerFromCharacterAppearanceData",mask, characterAppearanceData or {}, client)
characterRender.Parent = workspace.CurrentCamera
local animationController = characterRender.entity:WaitForChild("AnimationController")
--[[
local track = animationController:LoadAnimation(mask.idle)
track.Looped = true
track.Priority = Enum.AnimationPriority.Idle
track:Play()
]]
local currentEquipment = network:invoke("getCurrentlyEquippedForRenderCharacter", characterRender.entity)
--[[
local weaponType do
if currentEquipment["1"] then
weaponType = currentEquipment["1"].baseData.equipmentType
end
end
]]
local weaponType
local track = network:invoke("getMovementAnimationForCharacter", animationController, "idling", weaponType, nil)
if track then
if typeof(track) == "Instance" then
track:Play()
elseif typeof(track) == "table" then
for ii, obj in pairs(track) do
obj:Play()
end
end
spawn(function()
while true do
wait()
if typeof(track) == "Instance" then
if track.Length > 0 then
break
end
elseif typeof(track) == "table" then
local isGood = true
for ii, obj in pairs(track) do
if track.Length == 0 then
isGood = false
end
end
if isGood then
break
end
end
end
if characterRender then
if viewport:FindFirstChild("entity") then
viewport.entity:Destroy()
end
local entity = characterRender.entity
entity.Parent = viewport
characterRender:Destroy()
local focus = CFrame.new(entity.PrimaryPart.Position + entity.PrimaryPart.CFrame.lookVector * 6.3, entity.PrimaryPart.Position) * CFrame.new(3,0,0)
camera.CFrame = CFrame.new(focus.p + Vector3.new(0,1.5,0), entity.PrimaryPart.Position + Vector3.new(0,0.5,0))
end
end)
else
local track = animationController:LoadAnimation(mask.idle)
track.Looped = true
track.Priority = Enum.AnimationPriority.Idle
track:Play()
end
end
local playerCardTemplate = ui.content.sample:Clone()
ui.content.sample:Destroy()
local selectedPlayerCard
local function update()
-- update player cards
local playerCardCount = 0
for i, player in pairs(game.Players:GetPlayers()) do
if player:FindFirstChild("DataLoaded") then
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart:FindFirstChild("appearance") then
local appearance = player.Character.PrimaryPart.appearance.Value
local data = game:GetService("HttpService"):JSONDecode(appearance)
if data then
local playerCard = ui.content:FindFirstChild(player.Name)
if playerCard == nil then
playerCard = playerCardTemplate:Clone()
playerCard.Name = player.Name
playerCard.Parent = ui.content
playerCard.Visible = true
playerCard.Activated:connect(function()
Modules.inspectPlayer.open(player)
Modules.inspectPlayerPreview.close()
end)
playerCard.MouseEnter:connect(function()
selectedPlayerCard = playerCard
Modules.inspectPlayerPreview.open(player, playerCard)
end)
playerCard.SelectionGained:connect(function()
selectedPlayerCard = playerCard
Modules.inspectPlayerPreview.open(player, playerCard)
end)
playerCard.MouseLeave:connect(function()
if selectedPlayerCard == playerCard then
selectedPlayerCard = nil
Modules.inspectPlayerPreview.close()
end
end)
playerCard.SelectionLost:connect(function()
if selectedPlayerCard == playerCard then
selectedPlayerCard = nil
Modules.inspectPlayerPreview.close()
end
end)
playerCard.LayoutOrder = 22
end
if playerCard.appearance.Value ~= appearance and Modules.loadingScreen.loaded then
playerCard.appearance.Value = appearance
repareRender(playerCard.character.ViewportFrame, player, data)
end
local gold = player:FindFirstChild("gold") and player.gold.Value or 0
Modules.money.setLabelAmount(playerCard.content.money, gold)
local level = player:FindFirstChild("level") and player.level.Value or 0
playerCard.content.level.value.Text = "Lvl." .. level
playerCard.content.icon.ImageLabel.Image = "https://www.roblox.com/headshot-thumbnail/image?userId=" .. player.userId .. "&width=100&height=100&format=png"
playerCard.content.username.Text = player.Name
local localPlayer = game.Players.LocalPlayer
local origin = localPlayer.Character and localPlayer.Character.PrimaryPart and localPlayer.Character.PrimaryPart.Position
origin = origin or Vector3.new()
local class = player:FindFirstChild("class") and player.class.Value:lower() or "unknown"
local emblemVisible
if class:lower() ~= "adventurer" then
playerCard.content.emblem.Image = "rbxgameasset://Images/emblem_"..class:lower()
playerCard.content.emblem.Visible = true
emblemVisible = true
else
playerCard.content.emblem.Visible = false
end
local playerColor = Color3.fromRGB(208, 208, 208)
local distance = (player.Character.PrimaryPart.Position - origin).magnitude
-- do not update layoutOrder based on distance when leaderboard is selected
local layoutOrder = (selectedPlayerCard == nil) and math.clamp(math.ceil(math.sqrt(distance)), 1, 20)
if player == localPlayer then
layoutOrder = -1
playerColor = Color3.fromRGB(255, 206, 89)
else
local partyInfo = network:invoke("getCurrentPartyInfo")
if partyInfo and partyInfo.members then
for i,partyMemberInfo in pairs(partyInfo.members) do
if player == partyMemberInfo.player then
layoutOrder = 0
playerColor = Color3.fromRGB(87, 255, 255)
break
end
end
end
end
playerCard.content.icon.BorderColor3 = playerColor
playerCard.content.emblem.ImageColor3 = playerColor
playerCard.content.username.TextColor3 = playerColor
--playerCard.premium.ImageColor3 = playerColor
--playerCard.premium.Visible = player.MembershipType == Enum.MembershipType.Premium
if layoutOrder then
playerCard.LayoutOrder = layoutOrder
end
playerCardCount = playerCardCount + 1
end
end
end
end
ui.content.CanvasSize = UDim2.new(0, 0, 0, 35 * playerCardCount)
-- remove un-used cards
for i, card in pairs(ui.content:GetChildren()) do
if card:IsA("GuiObject") and game.Players:FindFirstChild(card.Name) == nil then
card:Destroy()
end
end
end
spawn(function()
while wait(1) do
update()
end
end)
end
return module
================================================
FILE: src/StarterGui/loadingScreen.lua
================================================
local module = {}
module.loaded = false
function module.init(Modules)
-- disabled
module.loaded = true
if true then
return
end
local contentProvider = game:GetService("ContentProvider")
local runService = game:GetService("RunService")
local teleService = game:GetService("TeleportService")
local teleportData = teleService:GetLocalPlayerTeleportData() or {}
local arrivingFrom = teleportData.arrivingFrom
-- no need to load assets if you are arriving from a teleport
if arrivingFrom and arrivingFrom ~= 2015602902 and arrivingFrom ~= 2376885433 and arrivingFrom ~= 2015602902 then
module.loaded = true
return false
end
local tween = Modules.tween
local contentList = {}
local loading = true
table.insert(contentList, game.ReplicatedStorage:WaitForChild("characterAnimations"))
table.insert(contentList, game.ReplicatedStorage:WaitForChild("abilityAnimations"))
table.insert(contentList, game.ReplicatedStorage:WaitForChild("sounds"))
table.insert(contentList, game:GetService("StarterGui"))
table.insert(contentList, game.ReplicatedStorage:WaitForChild("itemData"))
table.insert(contentList, game.ReplicatedStorage:WaitForChild("abilityLookup"))
table.insert(contentList, game.ReplicatedStorage:WaitForChild("accessoryLookup"))
local contents = script.Parent.contents
spawn(function()
local maxQueueSize = contentProvider.RequestQueueSize
while loading do
local queueSize = contentProvider.RequestQueueSize
if queueSize > maxQueueSize then
maxQueueSize = queueSize
end
local loadedAssetCount = maxQueueSize - queueSize
contents.value.Text = tostring(loadedAssetCount) .. "/" .. tostring(maxQueueSize)
contents.spinner.Rotation = contents.spinner.Rotation + 1
runService.Heartbeat:wait()
end
end)
spawn(function()
-- make sure the loading UI is loaded in
contentProvider:PreloadAsync({script.Parent})
script.Parent.Visible = true
contentProvider:PreloadAsync(contentList)
module.loaded = true
loading = false
contents.spinner.Image = "rbxassetid://2528903599"
-- contents.spinner.ImageColor3 = Color3.fromRGB(132, 255, 98)
contents.spinner.Rotation = 0
contents.value.Text = "All done!"
-- contents.value.TextColor3 = Color3.fromRGB(132, 255, 98)
tween(contents.spinner,{"ImageColor3"},{Color3.fromRGB(132, 255, 98)}, 0.5)
tween(contents.value,{"TextColor3"},{Color3.fromRGB(132, 255, 98)}, 0.5)
for i=1,4 do
local flare = script.Parent.flare:Clone()
flare.Name = "flareCopy"
flare.Parent = script.Parent
flare.Visible = true
flare.Size = UDim2.new(1,4,1,4)
flare.Position = UDim2.new(0,-2,0.5,0)
flare.AnchorPoint = Vector2.new(0,0.5)
local x = (180 - 40*i)
local y = (14 - 2*i)
local EndPosition = UDim2.new(0,-y/2,0.5,0)
local EndSize = UDim2.new(1,x,1,y)
tween(flare,{"Position","Size","ImageTransparency"},{EndPosition, EndSize, 1},0.5*i)
end
wait(2)
tween(contents, {"Position"}, {UDim2.new(-1,-20,0,0)}, 0.5)
wait(0.5)
script.Parent.Visible = false
end)
end
return module
================================================
FILE: src/StarterGui/loreBook.lua
================================================
local module = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local util = {}
util.network = network
local player = game.Players.LocalPlayer
local playerGui = player.PlayerGui
local ui = playerGui.gameUI.loreBook
local onPage = 1
local currentPages = {}
local textContent = ui.bookHolder.pages.lore.info.textcontent
function module.init(Modules)
local function updatePage()
if currentPages[onPage] and currentPages[onPage].text then
textContent.Text = currentPages[onPage].text
if onPage >= #currentPages then
onPage = #currentPages -- to be sure
ui.bookHolder.pages.lore.next.Visible = false
else
ui.bookHolder.pages.lore.next.Visible = true
end
if onPage <= 1 then
onPage = 1
ui.bookHolder.pages.lore.prev.Visible = false
else
ui.bookHolder.pages.lore.prev.Visible = true
end
ui.bookHolder.pages.lore.title.Text = "Page "..onPage
if currentPages[onPage].openFunc then
currentPages[onPage].openFunc(util)
end
return true
end
return false
end
local function createBook(pages, color)
ui.bookCover.ImageColor3 = color or Color3.fromRGB(38, 42, 58)
currentPages = pages
onPage = 1
local success = updatePage()
if success then
module.open()
end
return true
end
local function main()
network:create("openLoreBookFromClient", "BindableFunction")
network:connect("openLoreBookFromClient", "OnInvoke", createBook)
network:connect("openLoreBookFromServer", "OnClientEvent", createBook)
ui.bookHolder.pages.lore.next.MouseButton1Click:connect(function()
if onPage < #currentPages then
onPage = onPage + 1
updatePage()
end
end)
ui.bookHolder.pages.lore.prev.MouseButton1Click:connect(function()
if onPage > 1 then
onPage = onPage - 1
updatePage()
end
end)
ui.close.MouseButton1Click:connect(function()
if ui.Visible then
Modules.focus.toggle(ui)
end
end)
end
function module.open()
if not ui.Visible then
ui.UIScale.Scale = (Modules.input.menuScale + .15 or 1.25) --* 0.75
-- Modules.tween(ui.UIScale, {"Scale"}, (Modules.input.menuScale or 1), 0.5, Enum.EasingStyle.Bounce)
end
Modules.focus.toggle(ui)
end
main()
end
return module
================================================
FILE: src/StarterGui/menuButtons.lua
================================================
local module = {}
local ui = script.Parent.gameUI.right.buttons
function module.init(Modules)
local tween = Modules.tween
ui.openEquipment.Activated:connect(function()
Modules.equipment.show()
end)
ui.openInventory.Activated:connect(function()
Modules.inventory.show()
end)
ui.openAbilities.Activated:connect(function()
Modules.abilities.show()
end)
ui.openSettings.Activated:connect(function()
Modules.settings.show()
end)
for _, button in pairs(ui:GetChildren()) do
if button:IsA("GuiButton") then
local function selected()
-- Hide other buttons
for _, button in pairs(ui:GetChildren()) do
if button:IsA("GuiButton") then
button.ZIndex = 1
end
end
button.ZIndex = 2
local position = button.Position
local newPosition = UDim2.new(position.X.Scale, position.X.Offset, position.Y.Scale, -36)
tween(button, {"Position"}, newPosition, 0.5)
end
local function unselected()
local position = button.Position
local newPosition = UDim2.new(position.X.Scale, position.X.Offset, position.Y.Scale, 0)
tween(button, {"Position"}, newPosition, 0.5)
end
button.MouseEnter:connect(selected)
button.SelectionGained:connect(selected)
button.MouseLeave:connect(unselected)
button.SelectionLost:connect(unselected)
end
end
end
return module
================================================
FILE: src/StarterGui/mobileButtons.lua
================================================
local module = {}
local player = game:GetService("Players").LocalPlayer
local gui = player.PlayerGui.gameUI.mobileButtons
function module.init(Modules)
local control = Modules.control
local itemAcquistion = Modules.itemAcquistion
gui.jump.Activated:Connect(function()
control.doJump()
end)
gui.pickup.InputBegan:Connect(function(Input)
itemAcquistion.pickupInputGained(Input)
end)
end
return module
================================================
FILE: src/StarterGui/money.lua
================================================
-- WOAH MONEY XDDDDDDD
-- hand crafted in San Mateo, CA by Andrew "the rock" Berezaa ;P
local module = {}
local player = game:GetService("Players").LocalPlayer
local gui = player.PlayerGui.gameUI.bottomRight.money
module.suffixes = {"k","M","B","T","qd","Qn","sx","Sp","O","N","de","Ud","DD","tdD","qdD","QnD","sxD","SpD","OcD","NvD","Vgn","UVg","DVg","TVg","qtV","QnV","SeV","SPG","OVG","NVG","TGN","UTG","DTG","tsTG","qtTG","QnTG","ssTG","SpTG","OcTG","NoTG","QdDR","uQDR","dQDR","tQDR","qdQDR","QnQDR","sxQDR","SpQDR","OQDDr","NQDDr","qQGNT","uQGNT","dQGNT","tQGNT","qdQGNT","QnQGNT","sxQGNT","SpQGNT", "OQQGNT","NQQGNT","SXGNTL"}
local function shorten(Input)
local Negative = Input < 0
Input = math.abs(Input)
local Paired = false
for i,v in pairs(module.suffixes) do
if not (Input >= 10^(3*i)) then
Input = Input / 10^(3*(i-1))
local isComplex = (string.find(tostring(Input),".") and string.sub(tostring(Input),4,4) ~= ".")
Input = string.sub(tostring(Input),1,(isComplex and 4) or 3) .. (module.suffixes[i-1] or "")
Paired = true
break;
end
end
if not Paired then
local Rounded = math.floor(Input)
Input = tostring(Rounded)
end
if Negative then
return "-"..Input
end
return Input
end
function module.setLabelAmount(label, amount, costInfo)
local negative = false
if amount < 0 then
negative = true
amount = math.abs(amount)
end
if costInfo and costInfo.costType and costInfo.costType ~= "money" then
label.icon.Image = costInfo.icon or ""
label.amount.TextColor3 = costInfo.textColor or Color3.new(1,1,1)
label.amount.Text = shorten(amount)
else
if amount >= 10^6 then
label.icon.Image = "rbxassetid://2536432897"
label.amount.TextColor3 = Color3.fromRGB(255, 236, 123)
if amount >= 10^8 then
-- no decimals
label.amount.Text = tostring(math.floor(amount/10^6))
else
-- one decimal point
label.amount.Text = tostring(math.floor(amount/10^5)/10)
end
elseif amount >= 10^3 then
-- silver
label.icon.Image = "rbxassetid://2535600034"
label.amount.TextColor3 = Color3.fromRGB(223, 223, 223)
if amount >= 10^5 then
-- no decimals
label.amount.Text = tostring(math.floor(amount/10^3))
else
-- one decimal point
label.amount.Text = tostring(math.floor(amount/10^2)/10)
end
else
-- bronze
label.icon.Image = "rbxassetid://2535600080"
label.amount.TextColor3 = Color3.fromRGB(255, 170, 149)
label.amount.Text = tostring(math.floor(amount))
end
end
if negative then
label.amount.Text = "-"..label.amount.Text
end
local len = game.TextService:GetTextSize(label.amount.Text, label.amount.TextSize, label.amount.Font, Vector2.new(0,0)).X + 2
local padding = label:FindFirstChild("padding") and label.padding.Value or 0
label.amount.Size = UDim2.new(0, len + padding, label.amount.Size.Y.Scale, label.amount.Size.Y.Offset + padding)
end
module.labels = {}
local lastGoldValue = 0
function module.subscribeToPlayerMoney(label)
local lastMouseOver
label.MouseEnter:connect(function()
if lastGoldValue > 999 then
if not label.Parent:FindFirstChild("exactMoney") then return end
local thisMouseOver = tick()
lastMouseOver = thisMouseOver
label.Parent.exactMoney.Visible = true
wait(10)
if lastMouseOver == thisMouseOver then
label.Parent.exactMoney.Visible = false
end
end
end)
label.MouseLeave:connect(function()
if not label.Parent:FindFirstChild("exactMoney") then return end
label.Parent.exactMoney.Visible = false
end)
table.insert(module.labels, label)
end
function module.init(Modules)
local network = Modules.network
function module.subscribeToPlayerMoney(label)
local gold = network:invoke("getCacheValueByNameTag", "gold")
module.setLabelAmount(label, gold)
lastGoldValue = gold
if label.Parent:FindFirstChild("exactMoney") then
label.Parent.exactMoney.Text = math.floor(lastGoldValue)
local xBounds = game.TextService:GetTextSize(label.Parent.exactMoney.Text, label.Parent.exactMoney.TextSize, label.Parent.exactMoney.Font, Vector2.new(0,0)).X
label.Parent.exactMoney.Size = UDim2.new(0, xBounds + 16 + 10, 0, 26 + 10)
local lastMouseOver
label.MouseEnter:connect(function()
if lastGoldValue > 999 then
local thisMouseOver = tick()
lastMouseOver = thisMouseOver
label.Parent.exactMoney.Visible = true
wait(10)
if lastMouseOver == thisMouseOver then
label.Parent.exactMoney.Visible = false
end
end
end)
label.MouseLeave:connect(function()
label.Parent.exactMoney.Visible = false
end)
end
table.insert(module.labels, label)
end
local function onDataChange(key, value)
if key == "gold" then
lastGoldValue = value
for i,label in pairs(module.labels) do
module.setLabelAmount(label, value)
if label.Parent:FindFirstChild("exactMoney") then
label.Parent.exactMoney.Text = math.floor(lastGoldValue)
local xBounds = game.TextService:GetTextSize(label.Parent.exactMoney.Text, label.Parent.exactMoney.TextSize, label.Parent.exactMoney.Font, Vector2.new(0,0)).X
label.Parent.exactMoney.Size = UDim2.new(0, xBounds + 16 + 10, 0, 26 + 10)
end
end
end
end
-- update any subscribed labels from before module.init
onDataChange("gold", network:invoke("getCacheValueByNameTag", "gold"))
module.subscribeToPlayerMoney(gui)
network:connect("propogationRequestToSelf", "Event", onDataChange)
end
return module
================================================
FILE: src/StarterGui/monsterBook.lua
================================================
-- Monster book thingie by the honorable lord ber
local module = {}
local player = game:GetService("Players").LocalPlayer
local ui = player.PlayerGui.gameUI.monsterBook
function module.close()
ui.Visible = false
end
function module.open(newBook)
end
local extentsSizeCache = {}
function module.init(Modules)
spawn(function()
local network = Modules.network
if game.Players.LocalPlayer:FindFirstChild("bountyHunter") then
local bountyBookButton = Modules.input.menuButtons["openMonsterBook"]
local bookFrame = ui
local currentTab
local monsterBookData = {}
local pages = ui.bookHolder.pages
local monsterLookup = require(game.ReplicatedStorage.monsterLookup)
local itemLookup = require(game.ReplicatedStorage.itemData)
local levels = Modules.levels
local currentMonsterName
pages.monster.close.Activated:connect(function()
pages.monster.Visible = false
pages.main.Visible = true
end)
pages.monster.claim.Activated:connect(function()
if currentMonsterName then
local success, reason = network:invokeServer("playerRequest_claimBounty", currentMonsterName)
if success then
pages.monster.Visible = false
Modules.utilities.playSound("questTurnedIn")
pages.main.Visible = true
end
end
end)
local centerOfMassCache = {}
local function getCenterOfMassOfModel(model)
local totalVotedPosition = Vector3.new()
local totalVotes = 0
for i,part in pairs(model:GetDescendants()) do
if part:IsA("BasePart") then
local center = part.Position
local mass = part:GetMass()
totalVotedPosition = totalVotedPosition + (center * mass)
totalVotes = totalVotes + mass
end
end
return totalVotedPosition / totalVotes
end
local idolCaps = {
["1"] = 5;
["2"] = 10;
["3"] = 15;
["4"] = 20;
["5"] = 25;
["6"] = 30;
["99"] = 15;
}
local existingMonsterViewports = {}
local function getMonsterViewport(monsterName)
if existingMonsterViewports[monsterName] then
return existingMonsterViewports[monsterName]
end
local viewport = script.ViewportFrame:Clone()
local monster = monsterLookup[monsterName]
local monsterModule = monster.module
local entity
if monsterModule:FindFirstChild("displayEntity") then
entity = monsterModule.displayEntity:Clone()
else
entity = monsterModule.entity:Clone()
end
if entity:FindFirstChild("animations") and entity.animations:FindFirstChild("idling") then
entity.Parent = workspace
entity.PrimaryPart.Anchored = true
local entityController = entity:FindFirstChild("AnimationController")
game.ContentProvider:PreloadAsync({entity.animations.idling})
entityController:LoadAnimation(entity.animations.idling):Play()
wait(0.1)
local oldEntity = entity
entity = entity:Clone()
oldEntity:Destroy()
end
entity.Parent = viewport
entity:SetPrimaryPartCFrame(CFrame.new())
local extents = extentsSizeCache[monsterModule.Name]
if extents == nil then
extents = entity:GetExtentsSize()
extentsSizeCache[monsterModule.Name] = extents
end
local centerOfMass = centerOfMassCache[monsterModule.Name]
if centerOfMass == nil then
centerOfMass = getCenterOfMassOfModel(entity)
centerOfMassCache[monsterModule.Name] = centerOfMass
end
local camera = Instance.new("Camera")
local min = math.min(extents.x, extents.z)
local multi = Vector3.new(extents.z, extents.y, extents.x)/min
local pos = ((centerOfMass + entity.PrimaryPart.Position)/2) + (extents * Vector3.new(0.5,0.1,-0.8) * multi)
camera.CameraType = Enum.CameraType.Scriptable
camera.CFrame = CFrame.new(pos, centerOfMass) * (monster.cameraOffset or CFrame.new())
camera.Parent = viewport
viewport.CurrentCamera = camera
existingMonsterViewports[monsterName] = viewport
return viewport
end
local function updateTabPage()
-- clear existing children
for i,child in pairs(pages.main:GetChildren()) do
if child:IsA("GuiButton") then
child:Destroy()
end
end
-- populate with new buttons
for i,monsterModule in pairs(game.ReplicatedStorage.monsterLookup:GetChildren()) do
local monster = monsterLookup[monsterModule.Name]
if monster and monster.monsterBookPage and monster.monsterBookPage == tonumber(currentTab.Name) then
local idolCap = idolCaps[currentTab.Name]
local monsterButton = script.monster:Clone()
monsterButton.Name = monsterModule.Name
monsterButton.LayoutOrder = monster.level or 999
monsterButton.alert.Visible = false
local viewport = getMonsterViewport(monsterModule.Name):Clone()
if monsterButton:FindFirstChild("ViewportFrame") then
monsterButton.ViewportFrame:Destroy()
end
viewport.Parent = monsterButton
monsterButton.money.Visible = false
monsterButton.progress.Visible = false
local playerBountyData = monsterBookData[monsterModule.Name]
local kills = playerBountyData and playerBountyData.kills or 0
local lastBounty = playerBountyData and playerBountyData.lastBounty or 0
if kills > 0 or lastBounty > 0 then
local bountyPageInfo = levels.bountyPageInfo[tostring(monster.monsterBookPage)]
local bountyInfo = bountyPageInfo[lastBounty + 1]
if bountyInfo then
-- this line is duplicated in manager_player
local goldReward = levels.getBountyGoldReward(bountyInfo, monster)
Modules.money.setLabelAmount(
monsterButton.money,
goldReward
)
monsterButton.money.Visible = true
monsterButton.progress.Visible = true
monsterButton.progress.amount.Text = tostring(playerBountyData.kills) .. "/" .. tostring(bountyInfo.kills)
monsterButton.progress.xp.value.Size = UDim2.new(math.clamp(playerBountyData.kills/bountyInfo.kills,0,1),0,1,0)
if kills >= bountyInfo.kills then
monsterButton.alert.Visible = true
end
end
monsterButton.tooltip.Value = monsterModule.Name
monsterButton.Activated:connect(function()
-- display monster
currentMonsterName = monsterModule.Name
pages.monster.progress.Visible = false
pages.monster.money.Visible = false
pages.monster.claim.Visible = false
if bountyInfo then
local goldReward = levels.getBountyGoldReward(bountyInfo, monster)
Modules.money.setLabelAmount(
pages.monster.money,
goldReward
)
pages.monster.money.Visible = true
pages.monster.progress.Visible = true
pages.monster.progress.xp.value.Size = UDim2.new(math.clamp(playerBountyData.kills/bountyInfo.kills,0,1),0,1,0)
if playerBountyData.kills >= bountyInfo.kills then
pages.monster.claim.Visible = true
end
end
if pages.monster.holder:FindFirstChild("ViewportFrame") then
pages.monster.holder.ViewportFrame:Destroy()
end
monsterButton.ViewportFrame:Clone().Parent = pages.monster.holder
-- pages.monster.progress.xp.value.Size = UDim2.new(math.clamp(idols/idolCap,0,1),0,1,0)
pages.monster.title.Text = monsterModule.Name
pages.monster.info.level.Text = "Lvl. "..tostring(monster.level or "?")
pages.monster.info.health.Text = (monster.maxHealth or "???") .." HP"
-- local bonus = 0.5 * math.clamp(idols / idolCap, 0, 1)
-- pages.monster.info.bonus.title.Text = "+" .. tostring(math.ceil(bonus * 100)) .. "% EXP"
pages.monster.Visible = true
pages.main.Visible = false
-- clear existing loot
for i,lootObject in pairs(pages.monster.loot:GetChildren()) do
if lootObject:isA("GuiObject") then
lootObject:Destroy()
end
end
local lootToShow = {}
local addedLoot = {}
-- remove duplicates
if monster.lootDrops then
for i,lootDropData in pairs(monster.lootDrops) do
if lootDropData.id ~= 1 then
local realItem = itemLookup[lootDropData.id or lootDropData.itemName]
if realItem then
if not addedLoot[realItem.name] then
table.insert(lootToShow, lootDropData)
addedLoot[realItem.name] = true
end
end
end
end
end
local rows = math.ceil(#lootToShow/4)
pages.monster.loot.CanvasSize = UDim2.new(0,0,0,61 * rows)
for i, loot in pairs(lootToShow) do
local lootObject = script.inventoryItemTemplate:Clone()
local realItem = itemLookup[loot.id or loot.itemName]
lootObject.item.Image = realItem.image or "rbxassetid://2679574493"
lootObject.locked.Visible = false
lootObject.item.Visible = true
local percentDropChance = "?%"
if loot.spawnChance then
if loot.spawnChance >= 0.1 then
percentDropChance = tostring(math.floor(loot.spawnChance * 1000)/10).."%"
elseif loot.spawnChance >= 0.01 then
percentDropChance = tostring(math.floor(loot.spawnChance * 10000)/100).."%"
elseif loot.spawnChance >= 0.001 then
percentDropChance = tostring(math.floor(loot.spawnChance * 100000)/1000).."%"
else
percentDropChance = tostring(math.floor(loot.spawnChance * 1000000)/10000).."%"
end
end
lootObject.item.tooltip.Value = realItem.name .. " - " .. percentDropChance
lootObject.Parent = pages.monster.loot
end
end)
else
-- monsterButton.ViewportFrame.Visible = false
monsterButton.ViewportFrame.ImageColor3 = Color3.new(0,0,0 )
monsterButton.ViewportFrame.ImageTransparency = 0.7
monsterButton.progress.Visible = false
-- monsterButton.locked.Visible = true
monsterButton.ImageColor3 = Color3.fromRGB(60, 60, 60)
monsterButton.ImageTransparency = 0.5
monsterButton.shadow.ImageTransparency = 0.5
end
monsterButton.Parent = pages.main
end
end
end
function module.open()
if not ui.Visible then
ui.UIScale.Scale = (Modules.input.menuScale or 1) * 0.75
Modules.tween(ui.UIScale, {"Scale"}, (Modules.input.menuScale or 1), 0.5, Enum.EasingStyle.Bounce)
end
if currentTab then
updateTabPage()
end
Modules.focus.toggle(ui)
pages.main.Visible = true
pages.monster.Visible = false
end
function module.close()
if ui.Visible then
Modules.focus.toggle(ui)
end
end
ui.close.Activated:connect(module.close)
function module.loadTab(tab)
currentTab = tab
local tabNumber = tonumber(tab.Name)
for i,otherTab in pairs(tab.Parent:GetChildren()) do
if otherTab:IsA("GuiObject") then
otherTab.Size = UDim2.new(0, 50, 0, 40)
end
end
tab.Size = UDim2.new(0, 60, 0, 40)
local col = tab.ImageColor3
bookFrame.bookCover.ImageColor3 = Color3.new((col.r + 0.15) * 0.6, (col.g + 0.15) * 0.6, (col.b + 0.15) * 0.6)
script.inventoryItemTemplate.ImageColor3 = Color3.new((col.r + 0.15) * 0.3, (col.g + 0.15) * 0.3, (col.b + 0.15) * 0.3)
script.monster.ImageColor3 = Color3.new((col.r + 0.15) * 0.9, (col.g + 0.15) * 0.9, (col.b + 0.15) * 0.9)
pages.monster.holder.ImageColor3 = script.monster.ImageColor3
script.inventoryItemTemplate.locked.ImageColor3 = Color3.new((col.r + 0.1) * 0.92, (col.g + 0.1) * 0.92, (col.b + 0.1) * 0.92)
-- script.monster.ViewportFrame.BackgroundColor3 = script.monster.ImageColor3
script.monster.progress.xp.value.ImageColor3 = col
pages.monster.progress.xp.value.ImageColor3 = col
pages.monster.info.bonus.title.TextColor3 = col
pages.monster.holder.level.TextColor3 = col
pages.main.Visible = true
pages.monster.Visible = false
updateTabPage()
end
for i,tabButton in pairs(bookFrame.tabs:GetChildren()) do
if tabButton:IsA("GuiButton") then
tabButton.Activated:connect(function()
module.loadTab(tabButton)
end)
end
end
local existingAlertTotal
local function monsterBookDataUpdated(monsterBookData)
local alertTotal = 0
for monsterName, playerBountyData in pairs(monsterBookData) do
local kills = playerBountyData and playerBountyData.kills or 0
local lastBounty = playerBountyData and playerBountyData.lastBounty or 0
local monster = monsterLookup[monsterName]
if monster and monster.monsterBookPage then
local bountyPageInfo = levels.bountyPageInfo[tostring(monster.monsterBookPage)]
local bountyInfo = bountyPageInfo[lastBounty + 1]
if bountyInfo then
if kills >= bountyInfo.kills then
alertTotal = alertTotal + 1
end
end
end
end
bountyBookButton.alert.value.Text = tostring(alertTotal)
bountyBookButton.alert.Visible = alertTotal > 0
if existingAlertTotal and alertTotal > existingAlertTotal then
local textObject = {
text = "You completed a monster bounty!";
textColor3 = Color3.fromRGB(255, 85, 70);
id = "newpoints";
}
Modules.notifications.alert(textObject,4, "idolPickup")
end
existingAlertTotal = alertTotal
end
network:connect("propogationRequestToSelf", "Event", function(index, value)
if index == "bountyBook" then
local existingMonsterBookData = monsterBookData
monsterBookData = value
monsterBookDataUpdated(monsterBookData)
if currentTab and ui.Visible then
updateTabPage()
end
end
end)
spawn(function()
for i, monster in pairs(game.ReplicatedStorage.monsterLookup:GetChildren()) do
getMonsterViewport(monster.Name)
end
if not currentTab then
module.loadTab(bookFrame.tabs["1"])
end
monsterBookData = network:invoke("getCacheValueByNameTag", "bountyBook")
monsterBookDataUpdated(monsterBookData)
end)
end
end)
end
return module
================================================
FILE: src/StarterGui/notifications.lua
================================================
local module = {}
local alerts = {}
function module.alert()
end
function module.init(Modules)
if game.ReplicatedStorage:FindFirstChild("alertsOffset") then
script.Parent.Position = UDim2.new(0.5, 0, 0, script.Parent.Position.Y.Offset + game.ReplicatedStorage.alertsOffset.Value)
end
local tween = Modules.tween
local network = Modules.network
local utilities = Modules.utilities
function module.alert(textObject, duration, soundeffect)
duration = duration or 4
if soundeffect and game.ReplicatedStorage.assets.sounds:FindFirstChild(soundeffect) then
utilities.playSound(soundeffect)
--game.ReplicatedStorage.sounds[soundeffect]:Play()
end
local isNewAlert = false
local alert
if textObject.id and alerts[textObject.id] then
alert = alerts[textObject.id].alert
alerts[textObject.id].start = tick()
else
alert = script.Parent:WaitForChild("alert"):clone()
isNewAlert = true
if textObject.id then
alerts[textObject.id] = {alert = alert; start = tick()}
end
end
alert.TextLabel.Text = textObject.text or textObject.Text or ""
alert.TextLabel.TextColor3 = textObject.textColor3 or textObject.Color or Color3.new(1,1,1)
alert.TextLabel.TextStrokeColor3 = textObject.textStrokeColor3 or Color3.new(0,0,0)
alert.TextLabel.Font = textObject.font or textObject.Font or Enum.Font.SourceSansBold
alert.TextLabel.BackgroundColor3 = textObject.backgroundColor3 or Color3.new(1,1,1)
local textBounds = game.TextService:GetTextSize(alert.TextLabel.Text,alert.TextLabel.TextSize,alert.TextLabel.Font,Vector2.new())
alert.TextLabel.Size = UDim2.new(0,textBounds.X + 20,1,0)
alert.Parent = script.Parent
alert.Visible = true
if isNewAlert then
local textTransparency = textObject.textTransparency or 0
local textStrokeTransparency = textObject.textStrokeTransparency or 0
local backgroundTransparency = textObject.backgroundTransparency or 1
alert.TextLabel.TextTransparency = 1
alert.TextLabel.BackgroundTransparency = 1
alert.TextLabel.TextStrokeTransparency = 1
tween(alert.TextLabel, {"TextTransparency", "TextStrokeTransparency", "BackgroundTransparency"}, {textTransparency, textStrokeTransparency, backgroundTransparency}, 0.5)
else
alert.TextLabel.TextTransparency = textObject.textTransparency or 0
alert.TextLabel.TextStrokeTransparency = textObject.textStrokeTransparency or 0
alert.TextLabel.BackgroundTransparency = textObject.backgroundTransparency or 1
end
spawn(function()
wait(duration)
if textObject.id and alerts[textObject.id] then
if alerts[textObject.id].alert == alert then
alerts[textObject.id] = nil
tween(alert.TextLabel,{"TextTransparency", "TextStrokeTransparency", "BackgroundTransparency"}, {1, 1, 1}, 0.5)
game.Debris:AddItem(alert, 0.5)
end
elseif alert and alert.Parent == script.Parent then
tween(alert.TextLabel,{"TextTransparency", "TextStrokeTransparency", "BackgroundTransparency"}, {1, 1, 1}, 0.5)
game.Debris:AddItem(alert, 0.5)
end
end)
end
network:create("alert","BindableEvent", "Event", module.alert)
network:connect("alertPlayerNotification", "OnClientEvent", module.alert)
end
return module
================================================
FILE: src/StarterGui/party.lua
================================================
-- local party menu
-- berezaa
local module = {}
local localPlayer = game:GetService("Players").LocalPlayer
local gui = localPlayer.PlayerGui.gameUI.party
function module.init(Modules)
local network = Modules.network
local tween = Modules.tween
local fx = Modules.fx
local utilities = Modules.utilities
local isPartyLeader = false
local playerCards = {}
module.currentPartyInfo = nil
network:create("getCurrentPartyInfo","BindableFunction","OnInvoke",function()
if module.currentPartyInfo then
module.currentPartyInfo.isClientPartyLeader = isPartyLeader
end
return module.currentPartyInfo
end)
local function setInviteFrameSize()
-- gui.contents.invite.Size = UDim2.new(0,250,0,50)
local xSize = 100
if gui.contents.invite.textBox.Visible then
xSize = xSize + 150
end
if gui.contents.invite.leave.Visible then
xSize = xSize + 50
end
gui.contents.invite.Size = UDim2.new(0,xSize,0,60)
end
local function updateInviteButton()
if gui.contents.invite.textBox.Visible then
gui.contents.invite.button.Active = true
if game.Players:FindFirstChild(gui.contents.invite.textBox.Text) then
gui.contents.invite.button.ImageColor3 = Color3.fromRGB(82, 255, 71)
gui.contents.invite.button.detail.Text = ">"
else
gui.contents.invite.button.ImageColor3 = Color3.fromRGB(247, 138, 64)
gui.contents.invite.button.detail.Text = "-"
end
else
gui.contents.invite.button.detail.Text = "+"
if module.currentPartyInfo == nil or #module.currentPartyInfo.members < 6 then
gui.contents.invite.button.ImageColor3 = Color3.fromRGB(93, 249, 249)
gui.contents.invite.button.Active = true
else
gui.contents.invite.button.ImageColor3 = Color3.fromRGB(180,180,180)
gui.contents.invite.button.Active = false
end
end
setInviteFrameSize()
end
updateInviteButton()
local lastSelectedPartyManifest
gui.contents.invite.textBox.Changed:connect(updateInviteButton)
gui.contents.invite.leave.MouseButton1Click:connect(function()
gui.contents.invite.leave.ImageColor3 = Color3.new(0.7,0.7,0.7)
network:invokeServer("playerRequest_leaveParty")
gui.contents.invite.leave.ImageColor3 = Color3.fromRGB(246, 58, 63)
end)
local function closeInviteWindow()
Modules.focus.cleanup()
local invite = gui.contents.invite
invite.button.Visible = true
invite.ImageLabel.Visible = true
gui.contents.invite.textBox.Visible = false
tween(gui.contents.invite,{"ImageTransparency"},0.7,0.3)
if lastSelectedPartyManifest then
if lastSelectedPartyManifest and lastSelectedPartyManifest.Parent then
tween(lastSelectedPartyManifest,{"ImageTransparency","ImageColor3"},{0.5,Color3.new(0,0,0)},0.5)
lastSelectedPartyManifest.details.Visible = false
end
lastSelectedPartyManifest = nil
end
updateInviteButton()
end
local function openInviteWindow()
if module.currentPartyInfo == nil or #module.currentPartyInfo.members < 6 then
gui.contents.invite.textBox.Text = ""
gui.contents.invite.textBox.Visible = true
tween(gui.contents.invite,{"ImageTransparency"},0,0.3)
end
if Modules.input.mode.Value == "xbox" then
if game.GuiService.SelectedObject and game.GuiService.SelectedObject:IsDescendantOf(gui) then
closeInviteWindow()
else
Modules.focus.change(gui)
game.GuiService.SelectedObject = gui.contents.invite
end
end
updateInviteButton()
end
gui.contents.invite.button.MouseButton1Click:connect(function()
if gui.contents.invite.button.Active then
local buttonText = gui.contents.invite.button.detail.Text
if buttonText == ">" then
local success, reason = false, "Could not find player"
local targetPlayer = game.Players:FindFirstChild(gui.contents.invite.textBox.Text)
if targetPlayer then
success, reason = network:invokeServer("playerRequest_invitePlayerToMyParty", targetPlayer)
end
local invite = gui.contents.invite
local duration = 1
if success then
Modules.notifications.alert({text = "Invited "..targetPlayer.Name.." to the party."}, 2)
invite.button.Visible = false
invite.leave.Visible = false
invite.textBox.Visible = false
invite.ImageLabel.Visible = false
local ribben = fx.statusRibbon(invite, "Party invite sent!", "success", duration, UDim2.new(0,0,0.5,0))
ribben.Size = UDim2.new(0,150,0,30)
invite.Size = UDim2.new(0,200,0,60)
--gui.contents.invite.textBox.Text = ""
elseif reason then
duration = 2
invite.button.Visible = false
invite.leave.Visible = false
invite.textBox.Visible = false
invite.ImageLabel.Visible = false
local ribben = fx.statusRibbon(invite, reason, "fail", duration, UDim2.new(0,0,0.5,0))
ribben.Size = UDim2.new(0,150,0,30)
invite.Size = UDim2.new(0,200,0,60)
end
wait(duration)
closeInviteWindow()
--
elseif buttonText == "-" then
closeInviteWindow()
elseif buttonText == "+" then
openInviteWindow()
end
end
end)
local function clearPlayerCard(card, i)
if i == nil then
for e, playerCard in pairs(playerCards) do
if playerCard == card then
i = e
break
end
end
end
if i then
if card.manifest then
card.manifest:Destroy()
end
if card.connections then
for e,connection in pairs(card.connections) do
connection:disconnect()
end
end
playerCards[i] = nil
end
end
local function clearParty()
for i,card in pairs(playerCards) do
clearPlayerCard(card, i)
end
playerCards = {}
end
local teleporting = false
local function applyNameTag(manifest, player)
local playerCharacter = player.Character and player.Character.PrimaryPart and player.Character
local partyInfo = module.currentPartyInfo
if partyInfo.teleportState == "pending" and partyInfo.teleportPosition and playerCharacter and utilities.magnitude(playerCharacter.PrimaryPart.Position - partyInfo.teleportPosition) <= 20 then
manifest.header.class.Image = "rbxassetid://2528902744"
manifest.header.class.Visible = true
manifest.header.class.ImageColor3 = Color3.new(0.1,1,0.1)
manifest.header.username.TextColor3 = Color3.new(0.1,1,0.1)
else
local class = player:FindFirstChild("class") and player.class.Value:lower() or "unknown"
if class:lower() ~= "adventurer" then
manifest.header.class.Image = "rbxgameasset://Images/emblem_"..class:lower()
manifest.header.class.Visible = true
else
manifest.header.class.Visible = false
end
manifest.header.class.ImageColor3 = Color3.new(1,1,1)
manifest.header.username.TextColor3 = Color3.new(1,1,1)
end
end
local function updatePartyInfo(partyInfo)
local isOldPartyInfo = false
if partyInfo == nil then
isOldPartyInfo = true
partyInfo = network:invokeServer("playerRequest_getMyPartyData")
end
module.currentPartyInfo = partyInfo
gui.contents.invite.leave.Visible = partyInfo ~= nil
updateInviteButton()
if partyInfo then
if not isOldPartyInfo then
if partyInfo.status then
Modules.notifications.alert(partyInfo.status, 3)
game.StarterGui:SetCore("ChatMakeSystemMessage", {Text = partyInfo.status.text; Color = partyInfo.status.textColor3 or Color3.new(0.7,0.7,0.7)})
end
end
script.partyTeleportBeacon.Enabled = false
script.partyTeleportBeacon.Adornee = nil
if partyInfo.teleportState == "pending" then
if not teleporting then
teleporting = true
local alert = {
text = "The party leader has initiated a teleport. Group up!";
textColor3 = Color3.new(1,1,1);
backgroundColor3 = Color3.new(0,1,0.2);
backgroundTransparency = 0;
textStrokeTransparency = 1;
}
Modules.notifications.alert(alert, 4)
-- update dat name tag to account for people entering/leaving the teleport zone
spawn(function()
while module.currentPartyInfo and module.currentPartyInfo.teleportState == "pending" do
for i,partyMemberInfo in pairs(module.currentPartyInfo.members) do
local player = partyMemberInfo.player
local playerCard = playerCards[player]
if playerCard and playerCard.manifest then
applyNameTag(playerCard.manifest, player)
end
end
wait(1/10)
end
end)
end
elseif partyInfo.teleportState == nil or partyInfo.teleportState == "none" then
if teleporting then
teleporting = false
local alert = {
text = "The party leader canceled the teleport.";
textColor3 = Color3.new(1,1,1);
backgroundColor3 = Color3.new(0.9,0.1,0.1);
backgroundTransparency = 0;
textStrokeTransparency = 1;
}
Modules.notifications.alert(alert, 3)
end
end
local partyMembers = {}
-- Make sure every member of the party has a card
for i,partyMemberInfo in pairs(partyInfo.members) do
local player = partyMemberInfo.player
if player == localPlayer then
isPartyLeader = partyMemberInfo.isLeader
end
partyMembers[player] = true
if player ~= localPlayer and not playerCards[player] then
local playerCard = {}
playerCard.player = player
local manifest = script.playerInfo:Clone()
manifest.Name = player.Name
manifest.header.username.Text = player.Name
applyNameTag(manifest, player)
manifest.header.leader.Visible = partyMemberInfo.isLeader
local connections = {}
local function onMouseEnter()
if lastSelectedPartyManifest and lastSelectedPartyManifest.Parent and lastSelectedPartyManifest ~= manifest then
tween(lastSelectedPartyManifest,{"ImageTransparency","ImageColor3"},{0.5,Color3.new(0,0,0)},0.5)
lastSelectedPartyManifest.details.Visible = false
end
lastSelectedPartyManifest = manifest
if not manifest.details.Visible then
tween(manifest,{"ImageTransparency","ImageColor3"},{0.5,Color3.new(0,0,0.7)},0.5)
end
end
local function onMouseLeave()
if not manifest.details.Visible then
tween(manifest,{"ImageTransparency","ImageColor3"},{0.5,Color3.new(81, 81, 81)},0.5)
end
end
local function onActivated()
if manifest.details.Visible then
manifest.details.Visible = false
onMouseLeave()
else
if lastSelectedPartyManifest and lastSelectedPartyManifest.Parent and lastSelectedPartyManifest ~= manifest then
tween(lastSelectedPartyManifest,{"ImageTransparency","ImageColor3"},{0.5,Color3.new(81, 81, 81)},0.5)
lastSelectedPartyManifest.details.Visible = false
end
lastSelectedPartyManifest = manifest
manifest.details.Visible = true
manifest.details.kick.Visible = isPartyLeader
tween(manifest,{"ImageTransparency","ImageColor3"},{0,Color3.new(0,0,0.85)},0.5)
end
end
manifest.MouseEnter:connect(onMouseEnter)
manifest.SelectionGained:connect(onMouseEnter)
manifest.MouseLeave:connect(onMouseLeave)
manifest.SelectionLost:connect(onMouseLeave)
manifest.Activated:connect(onActivated)
manifest.details.kick.Activated:connect(function()
if player then
local success, reason = network:invokeServer("playerRequest_leaveParty", player)
end
end)
manifest.details.friendRequest.Activated:connect(function()
if player then
game.StarterGui:SetCore("PromptSendFriendRequest", player)
end
end)
local function updatePlayerHealthDisplay()
if player and player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart:FindFirstChild("health") and player.Character.PrimaryPart:FindFirstChild("maxHealth") then
manifest.healthBar.value.Size = UDim2.new(player.Character.PrimaryPart.health.Value/player.Character.PrimaryPart.maxHealth.Value,0,1,0)
manifest.healthBar.value.bar.ImageColor3 = Color3.fromRGB(255, 0, 4)
manifest.healthBar.title.Text = tostring(math.ceil(player.Character.PrimaryPart.health.Value)) .. "/" .. tostring(player.Character.PrimaryPart.maxHealth.Value)
else
manifest.healthBar.value.Size = UDim2.new(1,0,1,0)
manifest.healthBar.value.bar.ImageColor3 = Color3.fromRGB(130, 130, 130)
manifest.healthBar.title.Text = "???"
end
end
local function characterAdded(character)
updatePlayerHealthDisplay()
local startTime = tick()
repeat wait() until player.Character.PrimaryPart or tick - startTime > 5
if player.Character.PrimaryPart then
local healthValue = player.Character.PrimaryPart:WaitForChild("health",5)
local maxHealthValue = player.Character.PrimaryPart:WaitForChild("maxHealth",5)
table.insert(connections, healthValue.Changed:connect(updatePlayerHealthDisplay))
table.insert(connections, maxHealthValue.Changed:connect(updatePlayerHealthDisplay))
end
end
if player.Character then
characterAdded(player.Character)
end
table.insert(connections, player.CharacterAdded:connect(characterAdded))
playerCard.manifest = manifest
playerCard.connections = connections
--table.insert(playerCards, playerCard)
playerCards[player] = playerCard
manifest.Parent = gui.contents
manifest.Visible = true
end
end
-- clear any cards that are no longer party members
for i,playerCard in pairs(playerCards) do
if not partyMembers[playerCard.player] then
clearPlayerCard(playerCard, i)
end
end
-- other gui stuff
if #module.currentPartyInfo.members >= 6 then
closeInviteWindow()
end
else -- not in a party
clearParty()
end
end
updatePartyInfo()
network:connect("signal_myPartyDataChanged", "OnClientEvent", updatePartyInfo)
-- handle party requests
network:connect("signal_playerInvitedToParty", "OnClientEvent", function(playerInviting, inviteId)
if playerInviting then
local accepted = Modules.prompting.prompt(playerInviting.Name.." wants you to join their party.")
if accepted then
local success, reason = network:invokeServer("playerRequest_acceptMyPartyInvitation", inviteId)
end
end
end)
end
return module
================================================
FILE: src/StarterGui/playerInfo.lua
================================================
-- Player name, health, mana, etc. display
-- berezaa
local module = {}
local characterPrimaryPart
local player = game:GetService("Players").LocalPlayer
local gui = player.PlayerGui.gameUI.playerInfo
local selected = false
-- todo
local Rand = Random.new(os.time())
function module.init(Modules)
local network = Modules.network
local fx = Modules.fx
local oldxp = 0
local ColorEffect = Instance.new("ColorCorrectionEffect")
ColorEffect.Name = "DamageColor"
ColorEffect.Parent = game.Lighting
local BlurEffect = Instance.new("BlurEffect")
BlurEffect.Name = "DamageBlur"
BlurEffect.Parent = game.Lighting
BlurEffect.Size = 0
local tween = Modules.tween
--gui.header.username.value.Text = game.Players.LocalPlayer.Name
local function updateNameTag(class)
local label = gui.header.username.value
label.Text = game.Players.LocalPlayer.Name
local xSize = game.TextService:GetTextSize(label.Text, label.TextSize, label.Font, Vector2.new()).X + 15
class = string.lower(class or network:invoke("getCacheValueByNameTag", "class") or "Unknown")
if class:lower() ~= "adventurer" then
gui.header.username.icon.Image = "rbxgameasset://Images/emblem_"..class:lower()
gui.header.username.icon.Visible = true
label.Size = UDim2.new(1, -25,1, 0)
xSize = xSize + 25
else
gui.header.username.icon.Visible = false
label.Size = UDim2.new(1, 0,1, 0)
end
gui.header.username.Size = UDim2.new(0, xSize + 10, 0, 26 + 10)
end
updateNameTag()
repeat wait() until game.Players.LocalPlayer.Character
local character = game.Players.LocalPlayer.Character
while not character.PrimaryPart and character.Parent and character:IsDescendantOf(workspace) do
local primaryPart = character:WaitForChild("hitbox", 1)
if primaryPart then
character.PrimaryPart = primaryPart
break
else
warn("Waiting for PrimaryPart in", script.Name)
end
end
characterPrimaryPart = character.PrimaryPart
characterPrimaryPart:WaitForChild("health")
local lastHealth = characterPrimaryPart.health.Value
local function healthRefresh()
local delta = math.clamp(characterPrimaryPart.health.Value - lastHealth, -characterPrimaryPart.maxHealth.Value * 0.25, characterPrimaryPart.maxHealth.Value * 0.25) / (characterPrimaryPart.maxHealth.Value * 0.25)
lastHealth = characterPrimaryPart.health.Value
local percent = math.clamp(characterPrimaryPart.health.Value / characterPrimaryPart.maxHealth.Value, 0, 1)
local manaPercent = math.clamp(characterPrimaryPart.mana.Value / characterPrimaryPart.maxMana.Value, 0, 1)
gui.content.healthBar.value.Size = UDim2.new(percent,0,1,0)
gui.content.healthBar.title.Text = math.floor(characterPrimaryPart.health.Value + 0.5) .. "/" .. math.floor(characterPrimaryPart.maxHealth.Value + 0.5)
gui.content.manaBar.value.Size = UDim2.new(manaPercent,0,1,0)
gui.content.manaBar.title.Text = math.floor(characterPrimaryPart.mana.Value + 0.5) .. "/" .. math.floor(characterPrimaryPart.maxMana.Value + 0.5)
if delta < 0 then
local thresh = (0.9) * (math.abs(delta))
local duration = 0.15 + thresh / 1.4
tween(ColorEffect,{"TintColor","Contrast"},{Color3.fromRGB(255,255 - thresh * 150,255 - thresh * 150),thresh/3},duration/2)
tween(BlurEffect,{"Size"},thresh * 5,duration/2)
spawn(function()
wait(duration/2)
tween(ColorEffect,{"TintColor","Contrast"},{Color3.fromRGB(255,255,255),0},duration/2)
tween(BlurEffect,{"Size"},0,duration/2)
end)
spawn(function()
for i=1,3 do
gui.content.healthBarUnder.Visible = true
wait(0.08)
gui.content.healthBarUnder.Visible = false
wait(0.08)
end
end)
else
if characterPrimaryPart.health.Value - lastHealth > 5 and delta > 0.01 then
local thresh = 0.3 + math.abs(delta)
local duration = thresh / 1.4
tween(ColorEffect,{"TintColor","Contrast"},{Color3.fromRGB(255 - thresh * 150,255,255 - thresh * 150),-thresh/3},duration/2)
spawn(function()
wait(duration/1.5)
tween(ColorEffect,{"TintColor","Contrast"},{Color3.fromRGB(255,255,255),0},duration/2)
end)
end
end
end
characterPrimaryPart.health.Changed:connect(healthRefresh)
characterPrimaryPart.mana.Changed:connect(healthRefresh)
characterPrimaryPart.maxHealth.Changed:connect(healthRefresh)
characterPrimaryPart.maxMana.Changed:connect(healthRefresh)
healthRefresh()
-- gui.header.xp.Visible = true
local levels = Modules.levels
local network = Modules.network
local function setup()
local value = network:invoke("getCacheValueByNameTag", "exp")
local level = network:invoke("getCacheValueByNameTag", "level")
local label = gui.header.level.value
label.Text = "Lvl. "..level
local xSize = game.TextService:GetTextSize(label.Text, label.TextSize, label.Font, Vector2.new()).X + 16
gui.header.level.Size = UDim2.new(0, xSize + 10, 0, 26 + 10)
local xp = value
local needed = math.floor(levels.getEXPToNextLevel(level))
oldxp = value
gui.header.xp.title.Text = "XP: " .. xp .. "/" .. needed
gui.header.xp.value.Size = UDim2.new(xp/needed,0,1,0)
gui.header.xp.instant.Size = gui.header.xp.value.Size
--[[
local gold = network:invoke("getCacheValueByNameTag", "gold")
gui.header.gold.Text = "$"..gold
]]
end
local function onDataChange(key, value)
if key == "class" then
updateNameTag(value)
elseif key == "gold" then
-- if game.ReplicatedStorage:FindFirstChild("sounds") and game.ReplicatedStorage.sounds:FindFirstChild("coins") then
-- game.ReplicatedStorage.sounds.coins:Play()
-- end
elseif key == "level" then
local col = gui.header.xp.value.ImageColor3
gui.header.xp.ImageColor3 = col
tween(gui.header.xp, {"ImageColor3"},Color3.new(col.r + 0.2, col.g + 0.2, col.b + 0.2),0.4)
gui.header.xp.pulse.ImageTransparency = 0
gui.header.xp.pulse.ImageColor3 = col
gui.header.xp.pulse.Size = UDim2.new(1,0,1,0)
gui.header.xp.pulse.Visible = true
tween(gui.header.xp.pulse,{"Size","ImageTransparency"},{UDim2.new(1,140,1,140),1},0.5)
wait(0.4)
tween(gui.header.xp,{"ImageColor3"},Color3.fromRGB(15, 15, 15),1)
elseif key == "exp" then
local level = network:invoke("getCacheValueByNameTag", "level")
local label = gui.header.level.value
label.Text = "Lvl. "..level
local xSize = game.TextService:GetTextSize(label.Text, label.TextSize, label.Font, Vector2.new()).X + 16
gui.header.level.Size = UDim2.new(0, xSize + 10, 0, 26 + 10)
--local xp = math.floor(levels.getEXPPastCurrentLevel(value))
local xp = value
local needed = math.floor( levels.getEXPToNextLevel(level))
gui.header.xp.title.Text = "XP: " .. math.floor(xp) .. "/" .. needed
-- ahh crying internally
local change = xp - oldxp
local notice = gui.content.noticeTemplate:Clone()
notice.Name = "Notice"
notice.TextTransparency = 1
notice.TextStrokeTransparency = 1
notice.Parent = gui.content
notice.Visible = true
notice.Text = "+"..math.floor(change).." EXP"
notice.Position = UDim2.new(1,Rand:NextInteger(-10,50),0,Rand:NextInteger(30,70))
Modules.tween(notice,{"Position"},notice.Position + UDim2.new(0,0,0,-100),3)
Modules.tween(notice,{"TextTransparency","TextStrokeTransparency"},{0,0.7},1.5)
local sampleParticle = gui.header.xp.value.tip.sample
for i=1,6 do
local particle = sampleParticle:Clone()
particle.Rotation = math.random(1,90)
particle.Parent = sampleParticle.Parent
particle.Visible = true
tween(particle,{"Rotation", "Position", "Size", "BackgroundTransparency"},
{particle.Rotation + math.random(100,200), UDim2.new(0, math.random(3,25),0.5,math.random(-20,20)), UDim2.new(0,16,0,16), 1},math.random(60,130)/100)
game.Debris:AddItem(particle,1.5)
end
if xp < oldxp then
Modules.tween(gui.header.xp.value,{"Size"},{UDim2.new(1,0,1,0)},0.5)
gui.header.xp.instant.Size = UDim2.new(1,0,1,0)
spawn(function()
wait(0.25)
gui.header.xp.value.Size = UDim2.new(1,0,0,0)
local goal = UDim2.new(xp/needed,0,1,0)
Modules.tween(gui.header.xp.value,{"Size"},{goal},0.5)
gui.header.xp.instant.Size = goal
end)
else
Modules.tween(gui.header.xp.value,{"Size"},{UDim2.new(xp/needed,0,1,0)},1)
gui.header.xp.instant.Size = UDim2.new(xp/needed,0,1,0)
end
-- gui.header.xp.Visible = true
oldxp = value
spawn(function()
wait(0.5)
Modules.tween(notice,{"TextTransparency","TextStrokeTransparency"},{1,1},1.5)
wait(3)
if oldxp == value and not selected then
-- gui.header.xp.Visible = false
end
end)
end
end
gui.header.xp.MouseEnter:connect(function()
gui.header.xp.title.Visible = true
selected = true
end)
gui.header.xp.MouseLeave:connect(function()
gui.header.xp.title.Visible = false
selected = false
end)
network:connect("propogationRequestToSelf", "Event", onDataChange)
setup()
end
return module
================================================
FILE: src/StarterGui/playerInfoClone.lua
================================================
-- Player name, health, mana, etc. display
-- berezaa
local module = {}
local characterPrimaryPart
local ui = script.Parent.gameUI.playerInfo
local selected = false
-- todo
local Rand = Random.new(os.time())
function module.init(Modules)
local network = Modules.network
local fx = Modules.fx
local oldxp = 0
local ColorEffect = Instance.new("ColorCorrectionEffect")
ColorEffect.Name = "DamageColor"
ColorEffect.Parent = game.Lighting
local BlurEffect = Instance.new("BlurEffect")
BlurEffect.Name = "DamageBlur"
BlurEffect.Parent = game.Lighting
BlurEffect.Size = 0
local tween = Modules.tween
--ui.header.username.value.Text = game.Players.LocalPlayer.Name
local function updateNameTag(class)
local label = ui.header.username.value
label.Text = game.Players.LocalPlayer.Name
local xSize = game.TextService:GetTextSize(label.Text, label.TextSize, label.Font, Vector2.new()).X + 15
class = string.lower(class or network:invoke("getCacheValueByNameTag", "class") or "Unknown")
if class:lower() ~= "adventurer" then
ui.header.username.icon.Image = "rbxgameasset://Images/emblem_"..class:lower()
ui.header.username.icon.Visible = true
label.Size = UDim2.new(1, -25,1, 0)
xSize = xSize + 25
else
ui.header.username.icon.Visible = false
label.Size = UDim2.new(1, 0,1, 0)
end
ui.header.username.Size = UDim2.new(0, xSize + 10, 0, 26 + 10)
end
spawn(updateNameTag)
repeat wait() until game.Players.LocalPlayer.Character
local character = game.Players.LocalPlayer.Character
while not character.PrimaryPart and character.Parent and character:IsDescendantOf(workspace) do
local primaryPart = character:WaitForChild("hitbox", 1)
if primaryPart then
character.PrimaryPart = primaryPart
break
end
end
characterPrimaryPart = character.PrimaryPart
characterPrimaryPart:WaitForChild("health")
local lastHealth = characterPrimaryPart.health.Value
local function healthRefresh()
local delta = math.clamp(characterPrimaryPart.health.Value - lastHealth, -characterPrimaryPart.maxHealth.Value * 0.25, characterPrimaryPart.maxHealth.Value * 0.25) / (characterPrimaryPart.maxHealth.Value * 0.25)
lastHealth = characterPrimaryPart.health.Value
local percent = math.clamp(characterPrimaryPart.health.Value / characterPrimaryPart.maxHealth.Value, 0, 1)
local manaPercent = math.clamp(characterPrimaryPart.mana.Value / characterPrimaryPart.maxMana.Value, 0, 1)
ui.content.healthBar.value.Size = UDim2.new(percent,0,1,0)
ui.content.healthBar.title.Text = math.floor(characterPrimaryPart.health.Value + 0.5) .. "/" .. math.floor(characterPrimaryPart.maxHealth.Value + 0.5)
ui.content.manaBar.value.Size = UDim2.new(manaPercent,0,1,0)
ui.content.manaBar.title.Text = math.floor(characterPrimaryPart.mana.Value + 0.5) .. "/" .. math.floor(characterPrimaryPart.maxMana.Value + 0.5)
if delta < 0 then
local thresh = (0.9) * (math.abs(delta))
local duration = 0.15 + thresh / 1.4
tween(ColorEffect,{"TintColor","Contrast"},{Color3.fromRGB(255,255 - thresh * 150,255 - thresh * 150),thresh/3},duration/2)
tween(BlurEffect,{"Size"},thresh * 5,duration/2)
spawn(function()
wait(duration/2)
tween(ColorEffect,{"TintColor","Contrast"},{Color3.fromRGB(255,255,255),0},duration/2)
tween(BlurEffect,{"Size"},0,duration/2)
end)
spawn(function()
for i=1,3 do
ui.content.healthBarUnder.Visible = true
wait(0.08)
ui.content.healthBarUnder.Visible = false
wait(0.08)
end
end)
else
if characterPrimaryPart.health.Value - lastHealth > 5 and delta > 0.01 then
local thresh = 0.3 + math.abs(delta)
local duration = thresh / 1.4
tween(ColorEffect,{"TintColor","Contrast"},{Color3.fromRGB(255 - thresh * 150,255,255 - thresh * 150),-thresh/3},duration/2)
spawn(function()
wait(duration/1.5)
tween(ColorEffect,{"TintColor","Contrast"},{Color3.fromRGB(255,255,255),0},duration/2)
end)
end
end
end
characterPrimaryPart.health.Changed:connect(healthRefresh)
characterPrimaryPart.mana.Changed:connect(healthRefresh)
characterPrimaryPart.maxHealth.Changed:connect(healthRefresh)
characterPrimaryPart.maxMana.Changed:connect(healthRefresh)
healthRefresh()
ui.header.xp.Visible = true
local levels = Modules.levels
local network = Modules.network
local function setup()
local value = network:invoke("getCacheValueByNameTag", "exp")
local level = network:invoke("getCacheValueByNameTag", "level")
local label = ui.header.level.value
label.Text = "Lvl. "..level
local xSize = game.TextService:GetTextSize(label.Text, label.TextSize, label.Font, Vector2.new()).X + 16
ui.header.level.Size = UDim2.new(0, xSize + 10, 0, 26 + 10)
local xp = value
local needed = math.floor(levels.getEXPToNextLevel(level))
oldxp = value
ui.header.xp.title.Text = "XP: " .. xp .. "/" .. needed
ui.header.xp.value.Size = UDim2.new(xp/needed,0,1,0)
ui.header.xp.instant.Size = ui.header.xp.value.Size
--[[
local gold = network:invoke("getCacheValueByNameTag", "gold")
ui.header.gold.Text = "$"..gold
]]
end
local function onDataChange(key, value)
if key == "class" then
updateNameTag(value)
elseif key == "gold" then
-- if game.ReplicatedStorage:FindFirstChild("sounds") and game.ReplicatedStorage.sounds:FindFirstChild("coins") then
-- game.ReplicatedStorage.sounds.coins:Play()
-- end
elseif key == "level" then
local col = ui.header.xp.value.ImageColor3
ui.header.xp.ImageColor3 = col
tween(ui.header.xp, {"ImageColor3"},Color3.new(col.r + 0.2, col.g + 0.2, col.b + 0.2),0.4)
ui.header.xp.pulse.ImageTransparency = 0
ui.header.xp.pulse.ImageColor3 = col
ui.header.xp.pulse.Size = UDim2.new(1,0,1,0)
ui.header.xp.pulse.Visible = true
tween(ui.header.xp.pulse,{"Size","ImageTransparency"},{UDim2.new(1,140,1,140),1},0.5)
wait(0.4)
tween(ui.header.xp,{"ImageColor3"},Color3.fromRGB(15, 15, 15),1)
elseif key == "exp" then
local level = network:invoke("getCacheValueByNameTag", "level")
local label = ui.header.level.value
label.Text = "Lvl. "..level
local xSize = game.TextService:GetTextSize(label.Text, label.TextSize, label.Font, Vector2.new()).X + 16
ui.header.level.Size = UDim2.new(0, xSize + 10, 0, 26 + 10)
--local xp = math.floor(levels.getEXPPastCurrentLevel(value))
local xp = value
local needed = math.floor( levels.getEXPToNextLevel(level))
ui.header.xp.title.Text = "XP: " .. math.floor(xp) .. "/" .. needed
-- ahh crying internally
local change = xp - oldxp
--[[
local notice = ui.content.noticeTemplate:Clone()
notice.Name = "Notice"
notice.TextTransparency = 1
notice.TextStrokeTransparency = 1
notice.Parent = ui.content
notice.Visible = true
notice.Text = "+"..math.floor(change).." EXP"
notice.Position = UDim2.new(1,Rand:NextInteger(-10,50),0,Rand:NextInteger(30,70))
Modules.tween(notice,{"Position"},notice.Position + UDim2.new(0,0,0,-100),3)
Modules.tween(notice,{"TextTransparency","TextStrokeTransparency"},{0,0.7},1.5)
]]
local sampleParticle = ui.header.xp.value.tip.sample
for i=1,10 do
local particle = sampleParticle:Clone()
particle.Rotation = math.random(1,90)
particle.Parent = sampleParticle.Parent
particle.Visible = true
tween(particle,{"Rotation", "Position", "Size", "BackgroundTransparency"},
{particle.Rotation + math.random(100,200), UDim2.new(0, math.random(3,25),0.5,math.random(-20,20)), UDim2.new(0,16,0,16), 1},math.random(60,130)/100)
game.Debris:AddItem(particle,1.5)
end
if xp < oldxp then
Modules.tween(ui.header.xp.value,{"Size"},{UDim2.new(1,0,1,0)},0.5)
ui.header.xp.instant.Size = UDim2.new(1,0,1,0)
spawn(function()
wait(0.25)
ui.header.xp.value.Size = UDim2.new(1,0,0,0)
local goal = UDim2.new(xp/needed,0,1,0)
Modules.tween(ui.header.xp.value,{"Size"},{goal},0.5)
ui.header.xp.instant.Size = goal
end)
else
Modules.tween(ui.header.xp.value,{"Size"},{UDim2.new(xp/needed,0,1,0)},1)
ui.header.xp.instant.Size = UDim2.new(xp/needed,0,1,0)
end
ui.header.xp.Visible = true
oldxp = value
--[[
spawn(function()
wait(0.5)
Modules.tween(notice,{"TextTransparency","TextStrokeTransparency"},{1,1},1.5)
wait(3)
if oldxp == value and not selected then
-- ui.header.xp.Visible = false
end
end)
]]
end
end
ui.header.xp.MouseEnter:connect(function()
ui.header.xp.title.Visible = true
selected = true
end)
ui.header.xp.MouseLeave:connect(function()
ui.header.xp.title.Visible = false
selected = false
end)
network:connect("propogationRequestToSelf", "Event", onDataChange)
spawn(setup)
end
return module
================================================
FILE: src/StarterGui/playerInteract.lua
================================================
local module = {}
local activePlayer
local ui = script.Parent.gameUI.interactFrame.interact
function module.init(Modules)
local network = Modules.network
-- todo: fix
local animationInterface = Modules.animationInterface
function module.show(player)
activePlayer = player
if Modules.input.mode.Value ~= "mobile" then
ui.contents.username.Text = player.Name
ui.contents.level.Text = "Lvl. "..tostring(player:FindFirstChild("level") and player.level.Value or 0)
ui.interact.Visible = true
ui.options.Visible = false
ui.Size = UDim2.new(0, 700,0, 105)
ui.Visible = true
end
end
function module.activate(player)
Modules.inspectPlayer.open(player)
animationInterface:replicatePlayerAnimationSequence("emoteAnimations", "interaction_greeting")
end
function module.hide()
activePlayer = nil
ui.Visible = false
end
module.hide()
end
return module
================================================
FILE: src/StarterGui/products.lua
================================================
local module = {}
local runService = game:GetService("RunService")
local ui = script.Parent.gameUI.products
function module.open()
ui.Visible = not ui.Visible
end
-- module.remapTarget
local userSettings
function module.init(Modules)
local network = Modules.network
ui.close.Activated:connect(function()
Modules.focus.toggle(ui)
end)
function module.open()
if not ui.Visible then
ui.UIScale.Scale = (Modules.input.menuScale or 1) * 0.75
Modules.tween(ui.UIScale, {"Scale"}, (Modules.input.menuScale or 1), 0.5, Enum.EasingStyle.Bounce)
end
Modules.focus.toggle(ui)
end
for i,product in pairs(ui.contents:GetChildren()) do
if product:FindFirstChild("productId") and product:FindFirstChild("buy") then
product.buy.Activated:connect(function()
game.MarketplaceService:PromptProductPurchase(game.Players.LocalPlayer, product.productId.Value)
end)
end
end
--network:invokeServer("requestChangePlayerSetting", "clearingInteraction", true)
end
return module
================================================
FILE: src/StarterGui/prompting.lua
================================================
-- Input prompt by Locard, modified by berezaa
-- Imported from Miner's Haven
local module = {}
local runService = game:GetService("RunService")
local promptOut
local promptFrame = script.Parent.gameUI.leftBar.promptFrame
local buttonCons = {}
local currentDecision
function module.forceClose()
if promptOut then
currentDecision = false
end
end
function module.isPrompting()
return promptOut
end
-- moved under module.init
function module.prompt(headerText)
end
module.PROMPT_EXPIRE_TIME = 30
function module.init(Modules)
local tween = Modules.tween
promptFrame.Visible = false
local promptQueue = {}
local currentPrompt
local function displayPrompt(prompt)
currentPrompt = prompt
promptFrame.curve.ImageColor3 = Color3.fromRGB(30, 30, 30)
promptFrame.Visible = true
promptFrame.curve.yes.Visible = true
promptFrame.curve.no.Visible = true
promptFrame.curve.Visible = true
promptFrame.curve.title.Text = prompt.text
promptFrame.curve.Position = UDim2.new(-1,0,0,0)
tween(promptFrame.curve, {"Position"}, UDim2.new(0,0,0,0), 0.6)
end
local function addPrompt(prompt)
table.insert(promptQueue, prompt)
if #promptQueue == 1 or promptQueue[1] == prompt then
displayPrompt(prompt)
end
for i=1,3 do
local flare = script.Parent.flare:Clone()
flare.Name = "flareCopy"
flare.Parent = script.Parent
flare.Visible = true
flare.Size = UDim2.new(1,4,1,4)
flare.Position = UDim2.new(0,-2,0.5,0)
flare.AnchorPoint = Vector2.new(0,0.5)
local x = (180 - 40*i)
local y = (14 - 2*i)
local EndPosition = UDim2.new(0,-y/2,0.5,0)
local EndSize = UDim2.new(1,x,1,y)
tween(flare,{"Position","Size","ImageTransparency"},{EndPosition, EndSize, 1},0.5*i)
end
end
local function makeUserResponse(userResponse)
if currentPrompt then
-- register the user's response
currentPrompt.userResponse = userResponse
-- remove the response from queue
for i,prompt in pairs(promptQueue) do
if prompt == currentPrompt then
table.remove(promptQueue, i)
break
end
end
currentPrompt = nil
-- fancy transition out
local transitionCurve = promptFrame.curve:Clone()
transitionCurve.Name = "transition"
transitionCurve.ZIndex = transitionCurve.ZIndex + 1
transitionCurve.Parent = promptFrame
tween(transitionCurve, {"Position"}, UDim2.new(-1,-10,0,0), 0.6)
game.Debris:AddItem(transitionCurve, 0.6)
spawn(function()
wait(0.6)
if not currentPrompt then
promptFrame.Visible = false
end
end)
promptFrame.curve.Visible = false
-- show the next response if applicable
local nextPrompt = promptQueue[1]
if nextPrompt then
displayPrompt(nextPrompt)
end
end
end
function module.prompt(headerText)
local promptStartTime = tick()
local prompt = {text = headerText; timestamp = promptStartTime}
addPrompt(prompt)
repeat wait() until prompt.userResponse ~= nil or tick() - promptStartTime >= module.PROMPT_EXPIRE_TIME
-- remove the response from queue (if expired)
if tick() - promptStartTime >= module.PROMPT_EXPIRE_TIME then
if prompt == currentPrompt then
makeUserResponse(false)
else
for i,prompt in pairs(promptQueue) do
if prompt == currentPrompt then
table.remove(promptQueue, i)
break
end
end
end
end
return prompt.userResponse or false
end
promptFrame.curve.no.MouseButton1Click:Connect(function()
promptFrame.curve.ImageColor3 = Color3.fromRGB(30, 7, 8)
promptFrame.curve.yes.Visible = false
promptFrame.curve.no.Visible = false
makeUserResponse(false)
end)
promptFrame.curve.yes.MouseButton1Click:Connect(function()
promptFrame.curve.ImageColor3 = Color3.fromRGB(8, 30, 10)
promptFrame.curve.yes.Visible = false
promptFrame.curve.no.Visible = false
makeUserResponse(true)
end)
-- old stuff no one cares about:
function module.prompt_old(headerText)
if promptOut then
return false
end
promptFrame.Position = UDim2.new(0.5,0,0.5,0)
-- temp measure
local function selfIsSelected()
local obj = game:GetService("GuiService").SelectedObject
if obj then
return obj:IsDescendantOf(promptFrame)
else
return false
end
end
promptOut = true
local transitionOut
--First we initiate the prompt
promptFrame.curve.title.Text = headerText
local con0 = promptFrame.curve.no.MouseButton1Click:Connect(function()
--Modules.Menu.sounds.Click:Play()
if promptOut then
promptFrame.curve.ImageColor3 = Color3.fromRGB(30, 7, 8)
promptFrame.curve.yes.Visible = false
promptFrame.curve.no.Visible = false
currentDecision = false
end
end)
local con1 = promptFrame.curve.yes.MouseButton1Click:Connect(function()
--Modules.Menu.sounds.Click:Play()
if promptOut then
promptFrame.curve.ImageColor3 = Color3.fromRGB(8, 30, 10)
promptFrame.curve.yes.Visible = false
promptFrame.curve.no.Visible = false
currentDecision = true
end
end)
tween(promptFrame.curve, {"Position"}, UDim2.new(0,0,0,0), 0)
promptFrame.curve.ImageColor3 = Color3.fromRGB(30, 30, 30)
promptFrame.Visible = true
promptFrame.curve.yes.Visible = true
promptFrame.curve.no.Visible = true
for i=1,3 do
local flare = script.Parent.flare:Clone()
flare.Name = "flareCopy"
flare.Parent = script.Parent
flare.Visible = true
flare.Size = UDim2.new(1,4,1,4)
flare.Position = UDim2.new(0,-2,0.5,0)
flare.AnchorPoint = Vector2.new(0,0.5)
local x = (180 - 40*i)
local y = (14 - 2*i)
local EndPosition = UDim2.new(0,-y/2,0.5,0)
local EndSize = UDim2.new(1,x,1,y)
tween(flare,{"Position","Size","ImageTransparency"},{EndPosition, EndSize, 1},0.5*i)
end
--Transition the stuff into the screen
--spawn(function()
-- use my tween function nerd
--promptFrame.Absorb.Visible = true
--Modules.Menu.tween(promptFrame,{"BackgroundTransparency"}, .5, 0.7, Enum.EasingStyle.Quint)
--Modules.Menu.tween(promptFrame.InputPrompt, {"Position"}, UDim2.new(0.5,0,0.5,0), 0.7, Enum.EasingStyle.Quint)
--[[
local startT = tick()
for i = 1,60*deltaT do
if transitionOut then
break
end
local now = tick() - startT
local a = now/deltaT
local inputFrameY = quint(-.5,.5,a)
local bgTransparency = quint(1,.6,a)
promptFrame.BackgroundTransparency = bgTransparency
promptFrame.InputPrompt.Position = UDim2.new(0,0,inputFrameY,0)
runService.Heartbeat:Wait()
end
]]
--end)
--Yield the thread until an answer pops up
repeat
--[[
local Xbox = Modules.Input.mode.Value == "Xbox"
if Xbox and not selfIsSelected() then
Modules.Focus.stealFocus(promptFrame.InputPrompt)
end
]]
runService.Heartbeat:Wait()
until currentDecision ~= nil
local thisDecision = currentDecision
currentDecision = nil
--Disconnect the buttons
con0:Disconnect()
con1:Disconnect()
--make prompt ready
promptOut = false
tween(promptFrame.curve, {"Position"}, UDim2.new(-1,-10,0,0), 0.6)
spawn(function()
wait(0.6)
if not promptOut then
promptFrame.Visible = false
end
end)
-- spawn(function()
-- promptFrame.Absorb.Visible = false
-- Modules.Menu.tween(promptFrame,{"BackgroundTransparency"}, 1, 0.7, Enum.EasingStyle.Quint)
-- Modules.Menu.tween(promptFrame.InputPrompt, {"Position"}, UDim2.new(0.5,0,0,-250), 0.7, Enum.EasingStyle.Quint)
--[[
local startT = tick()
for i = 1,60*deltaT do
if promptOut then
break
end
local now = tick() - startT
local a = now/deltaT
local inputFrameY = quint(.5,-.5,a)
local bgTransparency = quint(.6,1,a)
promptFrame.BackgroundTransparency = bgTransparency
promptFrame.InputPrompt.Position = UDim2.new(0,0,inputFrameY,0)
runService.Heartbeat:Wait()
end
]]
-- end)
return thisDecision
end
Modules.network:create("promptAction","BindableFunction","OnInvoke",module.prompt)
end
return module
================================================
FILE: src/StarterGui/prompting_Fullscreen.lua
================================================
-- Input prompt by Locard, modified by berezaa
-- Imported from Miner's Haven
local module = {}
local runService = game:GetService("RunService")
local player = game:GetService("Players").LocalPlayer
local promptOut
local promptFrame = player.PlayerGui.gameUI.promptFullscreen
local buttonCons = {}
local currentDecision
function module.forceClose()
if promptOut then
currentDecision = false
end
end
function module.isPrompting()
return promptOut
end
-- moved under module.init
function module.prompt(headerText)
end
module.PROMPT_EXPIRE_TIME = 30
function module.init(Modules)
local tween = Modules.tween
promptFrame.Visible = false
local promptQueue = {}
local currentPrompt
local function displayPrompt(prompt)
currentPrompt = prompt
promptFrame.curve.ImageColor3 = Color3.fromRGB(30, 30, 30)
promptFrame.Visible = true
promptFrame.curve.yes.Visible = true
promptFrame.curve.no.Visible = true
promptFrame.curve.Visible = true
promptFrame.curve.title.Text = prompt.text
end
local function addPrompt(prompt)
table.insert(promptQueue, prompt)
if #promptQueue == 1 or promptQueue[1] == prompt then
displayPrompt(prompt)
end
end
local function makeUserResponse(userResponse)
if currentPrompt then
-- register the user's response
currentPrompt.userResponse = userResponse
-- remove the response from queue
for i,prompt in pairs(promptQueue) do
if prompt == currentPrompt then
table.remove(promptQueue, i)
break
end
end
currentPrompt = nil
-- fancy transition out
spawn(function()
wait()
if not currentPrompt then
promptFrame.Visible = false
end
end)
promptFrame.curve.Visible = false
-- show the next response if applicable
local nextPrompt = promptQueue[1]
if nextPrompt then
displayPrompt(nextPrompt)
end
end
end
function module.prompt(headerText)
local promptStartTime = tick()
local prompt = {text = headerText; timestamp = promptStartTime}
addPrompt(prompt)
repeat wait() until prompt.userResponse ~= nil or tick() - promptStartTime >= module.PROMPT_EXPIRE_TIME
-- remove the response from queue (if expired)
if tick() - promptStartTime >= module.PROMPT_EXPIRE_TIME then
if prompt == currentPrompt then
makeUserResponse(false)
else
for i,prompt in pairs(promptQueue) do
if prompt == currentPrompt then
table.remove(promptQueue, i)
break
end
end
end
end
return prompt.userResponse or false
end
promptFrame.curve.no.MouseButton1Click:Connect(function()
promptFrame.curve.ImageColor3 = Color3.fromRGB(30, 7, 8)
promptFrame.curve.yes.Visible = false
promptFrame.curve.no.Visible = false
makeUserResponse(false)
end)
promptFrame.curve.yes.MouseButton1Click:Connect(function()
promptFrame.curve.ImageColor3 = Color3.fromRGB(8, 30, 10)
promptFrame.curve.yes.Visible = false
promptFrame.curve.no.Visible = false
makeUserResponse(true)
end)
-- old stuff no one cares about:
function module.prompt_old(headerText)
if promptOut then
return false
end
-- temp measure
local function selfIsSelected()
local obj = game:GetService("GuiService").SelectedObject
if obj then
return obj:IsDescendantOf(promptFrame)
else
return false
end
end
promptOut = true
local transitionOut
--First we initiate the prompt
promptFrame.curve.title.Text = headerText
local con0 = promptFrame.curve.no.MouseButton1Click:Connect(function()
--Modules.Menu.sounds.Click:Play()
if promptOut then
promptFrame.curve.ImageColor3 = Color3.fromRGB(30, 7, 8)
promptFrame.curve.yes.Visible = false
promptFrame.curve.no.Visible = false
currentDecision = false
end
end)
local con1 = promptFrame.curve.yes.MouseButton1Click:Connect(function()
--Modules.Menu.sounds.Click:Play()
if promptOut then
promptFrame.curve.ImageColor3 = Color3.fromRGB(8, 30, 10)
promptFrame.curve.yes.Visible = false
promptFrame.curve.no.Visible = false
currentDecision = true
end
end)
promptFrame.curve.ImageColor3 = Color3.fromRGB(30, 30, 30)
promptFrame.Visible = true
promptFrame.curve.yes.Visible = true
promptFrame.curve.no.Visible = true
--Transition the stuff into the screen
--spawn(function()
-- use my tween function nerd
--promptFrame.Absorb.Visible = true
--Modules.Menu.tween(promptFrame,{"BackgroundTransparency"}, .5, 0.7, Enum.EasingStyle.Quint)
--Modules.Menu.tween(promptFrame.InputPrompt, {"Position"}, UDim2.new(0.5,0,0.5,0), 0.7, Enum.EasingStyle.Quint)
--[[
local startT = tick()
for i = 1,60*deltaT do
if transitionOut then
break
end
local now = tick() - startT
local a = now/deltaT
local inputFrameY = quint(-.5,.5,a)
local bgTransparency = quint(1,.6,a)
promptFrame.BackgroundTransparency = bgTransparency
promptFrame.InputPrompt.Position = UDim2.new(0,0,inputFrameY,0)
runService.Heartbeat:Wait()
end
]]
--end)
--Yield the thread until an answer pops up
repeat
--[[
local Xbox = Modules.Input.mode.Value == "Xbox"
if Xbox and not selfIsSelected() then
Modules.Focus.stealFocus(promptFrame.InputPrompt)
end
]]
runService.Heartbeat:Wait()
until currentDecision ~= nil
local thisDecision = currentDecision
currentDecision = nil
--Disconnect the buttons
con0:Disconnect()
con1:Disconnect()
--make prompt ready
promptOut = false
promptFrame.Visible = false
-- spawn(function()
-- promptFrame.Absorb.Visible = false
-- Modules.Menu.tween(promptFrame,{"BackgroundTransparency"}, 1, 0.7, Enum.EasingStyle.Quint)
-- Modules.Menu.tween(promptFrame.InputPrompt, {"Position"}, UDim2.new(0.5,0,0,-250), 0.7, Enum.EasingStyle.Quint)
--[[
local startT = tick()
for i = 1,60*deltaT do
if promptOut then
break
end
local now = tick() - startT
local a = now/deltaT
local inputFrameY = quint(.5,-.5,a)
local bgTransparency = quint(.6,1,a)
promptFrame.BackgroundTransparency = bgTransparency
promptFrame.InputPrompt.Position = UDim2.new(0,0,inputFrameY,0)
runService.Heartbeat:Wait()
end
]]
-- end)
return thisDecision
end
Modules.network:create("promptActionFullscreen","BindableFunction","OnInvoke",module.prompt)
end
return module
================================================
FILE: src/StarterGui/questLog.lua
================================================
local module = {}
function module.open()
end
--[[
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local questUtil = modules.load("client_quest_util")
local itemData = require(replicatedStorage.itemData)
local questLookup = require(replicatedStorage.questLookup)
-- todo: remove this disgusting absolute reference
--local uiCreator = require(script.Parent.Parent.Parent.Parent:WaitForChild("uiCreator"))
local player = game.Players.LocalPlayer
local instructFrame = script.Parent:WaitForChild("contents").logFrame.curve.questInstructions.contents
local selectFrame = script.Parent:WaitForChild("contents").quests.curve.contents
--local scrollingMask = script.Parent.Parent.Parent.Parent:WaitForChild("scrollingMask")
local currentQuestOpenId
local activeQuests = {}
local completedQuests = {}
local unassignedQuests = {}
local lastSelected
local openQuestOnJoinId
function module.init(Modules)
local function createSingleNote(text, completed, centered, col)
local slot = script.slot:Clone()
slot.Text = text
if completed then
slot.TextTransparency = .5
end
if centered then
slot.TextXAlignment = "Center"
end
local sizeY = game.TextService:GetTextSize(text, slot.TextSize, slot.Font, instructFrame.AbsoluteSize).Y + 25
slot.Size = UDim2.new(1,-12,0,sizeY)
if col then
slot.TextColor3 = col
end
slot.Parent = instructFrame
instructFrame.CanvasSize = UDim2.new(0,0,0,instructFrame.UIListLayout.AbsoluteContentSize.Y + 30)
end
local function drawQuestNotes(id)
if id == nil then return end
currentQuestOpenId = id -- needed don't get rid of me
local quests = network:invoke("getCacheValueByNameTag", "quests")
for i, child in pairs(instructFrame:GetChildren()) do
if child.Name == "slot" then
child:Destroy()
end
end
local baseState = ""
local questLookUpData = questLookup[id] -- for finding the text, triggertypes, to add to the notes
local playerQuestData -- for displaying progress. if nil then the quest is not started
local header = script.Parent:WaitForChild("contents").logFrame.curve.header
local titleT = header.quest
titleT.Text = questLookUpData.questLineName
header.titledecor.Size = UDim2.new(0,titleT.TextBounds.X+16,0,36)
for i, questData in pairs(quests.active) do
if questData.id == id and questData.objectives[1].started then
baseState = "active"
playerQuestData = questData
createSingleNote(questLookUpData.questLineDescription, false, true)
end
end
for i, questData in pairs(quests.completed) do
if questData.id == id and baseState ~= "active" then
baseState = "completed"
playerQuestData = questData
createSingleNote(questLookUpData.questLineDescription, true, true)
end
end
if baseState == "" then
baseState = "unassigned"
end
if playerQuestData and baseState == "active" then
for i, objectiveData in pairs(playerQuestData.objectives) do
if not objectiveData.started then
break
end
local objectiveReference = questLookUpData.objectives[i]
local stepsTotal = #objectiveData.steps
local stepsCompleted = 0
for ii, stepData in pairs(objectiveData.steps) do
local stepReference = questLookUpData.objectives[i].steps[ii]
local triggerType = questLookUpData.objectives[i].steps[ii].triggerType
local constructedString = ""
if stepReference.overridingNote then
constructedString = stepReference.overridingNote
else
local completedAmount = math.clamp(stepData.completion.amount, 0, stepData.requirement.amount)
constructedString = "["..(completedAmount).."/"..(stepData.requirement.amount).."]"
if stepReference.accompanyingNote then
constructedString = constructedString.." "..stepReference.accompanyingNote
elseif triggerType == "monster-killed" then
if stepReference.requirement.isGiant then
constructedString = constructedString.." Giant "..stepReference.requirement.monsterName.." kills"
else
constructedString = constructedString.." "..stepReference.requirement.monsterName.." kills"
end
elseif triggerType == "item-collected" then
local name = itemData[stepReference.requirement.id].name
constructedString = constructedString.." "..name .." collected"
end
end
if stepData.completion.amount >= stepData.requirement.amount then
stepsCompleted = stepsCompleted + 1
end
if stepReference.isSequentialStep then
local checkCompletedSteps = 0
for iii, stepData in pairs(objectiveData.steps) do
if iii >= ii then break end
if stepData.completion.amount >= stepData.requirement.amount then
checkCompletedSteps = checkCompletedSteps + 1
end
end
if checkCompletedSteps >= ii - 1 then
if stepReference.hideNote == nil then
createSingleNote(constructedString, stepData.completion.amount >= stepData.requirement.amount, false)
end
end
else
if stepReference.hideNote == nil then
createSingleNote(constructedString, stepData.completion.amount >= stepData.requirement.amount, false)
end
end
end
if stepsCompleted >= stepsTotal then
if playerQuestData.currentObjective > i then
if playerQuestData.objectives[i + 1].started then
-- case 1
-- player has turned in the current objective AND started the next objective
createSingleNote(objectiveReference.completedNotes, true, false)
createSingleNote(objectiveReference.handingNotes, true, true)
else
-- case 2
-- player has turned in the current objective BUT has not started the next objective. (player could not have completed the quest)
createSingleNote(objectiveReference.completedNotes, true, false)
createSingleNote(objectiveReference.handingNotes, false, true)
end
else
if baseState == "completed" then
-- case 3 player has completed the quest
createSingleNote(objectiveReference.completedNotes, true, false)
createSingleNote(objectiveReference.handingNotes, false, true)
elseif baseState == "active" then
-- case 4 player needs to turn in the quest
createSingleNote(objectiveReference.completedNotes, false, false, Color3.fromRGB(255, 210, 29))
end
end
end
end
elseif baseState == "completed" then
local completedNotes = "Quest Completed!"
if playerQuestData.failed then
completedNotes = "Quest Failed."
end
createSingleNote(completedNotes, false, true)
if questLookUpData.questEndedNote then
createSingleNote(questLookUpData.questEndedNote, false, false)
end
else
-- quest is unassigend, display data accordingly
--createSingleNote("my name jef", false, false)
end
instructFrame.CanvasPosition = Vector2.new(0,instructFrame.UIListLayout.AbsoluteContentSize.Y)
end
local function drawQuestSelectButton(id, col)
local slot = script.slotB:Clone()
slot.itemName.Text = questLookup[id].questLineName
slot.itemName.TextColor3 = col
slot.frame.ImageColor3 = col
slot.shine.ImageColor3 = col
slot.MouseButton1Click:connect(function()
-- update
currentQuestOpenId = id
drawQuestNotes(id)
end)
slot.Parent = selectFrame
selectFrame.CanvasSize = UDim2.new(0,0,0,selectFrame.UIListLayout.AbsoluteContentSize.Y + 10)
if selectFrame.UIListLayout.AbsoluteContentSize.Y >= selectFrame.AbsoluteSize.Y then
for i, child in pairs(selectFrame:GetChildren()) do
if child.Name == "slotB" then
child.Size = UDim2.new(1,-16,0,45)
end
end
end
end
local function updateQuestLog(updateQuestData)
if updateQuestData then
activeQuests = {}
completedQuests = {}
unassignedQuests = {}
--active first
for i, quest in pairs(updateQuestData.active) do
if quest.objectives[1].started then
table.insert(activeQuests, quest.id)
end
end
for i, quest in pairs(updateQuestData.completed) do
local repeated = false
for ii, id in pairs(activeQuests) do
if id == quest.id then
repeated = true
end
end
if not repeated then
table.insert(completedQuests, quest.id)
end
end
for key, val in pairs(questLookup) do
local testKey = tonumber(key)
if testKey then
local unassigned = true
for i, id in pairs(activeQuests) do
if id == testKey then
unassigned = false
end
end
for i, id in pairs(completedQuests) do
if id == testKey then
unassigned = false
end
end
if unassigned then
table.insert(unassignedQuests, testKey)
end
end
end
for i, child in pairs(selectFrame:GetChildren()) do
if child.Name == "slotB" then
child:Destroy()
end
end
local alertQuests = 0
for i, id in pairs(activeQuests) do
local state = questUtil.getPlayerQuestStateByQuestId(id)
if state == 9 then
if openQuestOnJoinId == nil then
openQuestOnJoinId = id
end
drawQuestSelectButton(id, Color3.fromRGB(255, 210, 29))
alertQuests = alertQuests + 1
end
end
for i, id in pairs(activeQuests) do
local state = questUtil.getPlayerQuestStateByQuestId(id)
if state ~= 9 then
if openQuestOnJoinId == nil then
openQuestOnJoinId = id
end
drawQuestSelectButton(id, Color3.fromRGB(255,255,255))
end
end
--for i, id in pairs(unassignedQuests) do
-- drawQuestSelectButton(id, Color3.fromRGB(147, 147, 147))
--end
for i, id in pairs(completedQuests) do
drawQuestSelectButton(id, Color3.fromRGB(147, 147, 147))
end
if currentQuestOpenId == nil and openQuestOnJoinId ~= nil then
drawQuestNotes(openQuestOnJoinId)
else
drawQuestNotes(currentQuestOpenId)
end
if alertQuests > 0 then
script.Parent.Parent.right.buttons.openQuestLog.alert.value.Text = alertQuests
script.Parent.Parent.right.buttons.openQuestLog.alert.Visible = true
else
script.Parent.Parent.right.buttons.openQuestLog.alert.Visible = false
end
end
end
local function onPropogationRequestToSelf(propogationNameTag, propogationData)
if propogationNameTag == "quests" then
updateQuestLog(propogationData)
end
end
local function main()
updateQuestLog(network:invoke("getCacheValueByNameTag", "quests"))
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:create("displayQuestInQuestLog", "BindableEvent")
network:connect("displayQuestInQuestLog", "Event", drawQuestNotes)
script.Parent.close.MouseButton1Click:connect(function()
Modules.focus.toggle(script.Parent)
end)
end
function module.open()
if not script.Parent.Visible then
script.Parent.UIScale.Scale = (Modules.input.menuScale or 1) * 0.75
Modules.tween(script.Parent.UIScale, {"Scale"}, (Modules.input.menuScale or 1), 0.5, Enum.EasingStyle.Bounce)
end
Modules.focus.toggle(script.Parent)
end
main()
end
]]
return module
================================================
FILE: src/StarterGui/save.client.lua
================================================
local resetUI = script.Parent:WaitForChild("resetUI")
resetUI.Enabled = false
resetUI.saveMe.Visible = false
game.Players.LocalPlayer:WaitForChild("DataLoaded", 60)
wait(5)
if (not resetUI.Parent.gameUI.Enabled) and (not resetUI.Parent.customize.Enabled) then
resetUI.Enabled = true
resetUI.saveMe.Visible = true
local function showReport()
local text = ""
for _, entry in pairs(game.LogService:GetLogHistory()) do
text = text .. " [["..entry.message.."]] "
end
resetUI.incident.contents.Text = text
resetUI.incident.Visible = true
end
resetUI.incident.continue.Activated:connect(function()
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
network:invokeServer("playerRequest_returnToMainMenu")
resetUI.incident.continue:Destroy()
end)
resetUI.saveMe.Activated:connect(function()
resetUI.saveMe.Visible = false
showReport()
end)
end
================================================
FILE: src/StarterGui/serverBrowser.lua
================================================
local module = {}
local player = game:GetService("Players").LocalPlayer
local gui = player.PlayerGui.gameUI.serverBrowser
function module.open()
end
function module.init(Modules)
local httpService = game:GetService("HttpService")
local servers
local serversDataValue
local network = Modules.network
gui.close.MouseButton1Click:connect(function()
Modules.focus.toggle(gui)
end)
gui.header.serverId.Text = "This server's ID: "..string.sub(game.JobId, 1, 8)
function module.open()
if not gui.Visible then
gui.UIScale.Scale = (Modules.input.menuScale or 1) * 0.75
Modules.tween(gui.UIScale, {"Scale"}, (Modules.input.menuScale or 1), 0.5, Enum.EasingStyle.Bounce)
end
Modules.focus.toggle(gui)
end
local function updated()
servers = serversDataValue.Value ~= "" and httpService:JSONDecode(serversDataValue.Value) or {}
for i,child in pairs(gui.servers:GetChildren()) do
if child:IsA("GuiObject") then
child:Destroy()
end
end
servers[game.JobId] = {
players = #game.Players:GetChildren();
}
for jobId, serverData in pairs(servers) do
local button = gui.sample:Clone()
button.id.Text = string.sub(jobId, 1, 8)
button.Name = jobId
-- same place so same MaxPlayers?
local serverFilled = serverData.players / game.Players.MaxPlayers
button.players.progress.Size = UDim2.new(serverFilled, 0, 1, 0)
if serverFilled >= 0.9 then
button.players.progress.BackgroundColor3 = Color3.fromRGB(255, 0, 4)
elseif serverFilled >= 0.75 then
button.players.progress.BackgroundColor3 = Color3.fromRGB(255, 191, 0)
else
button.players.progress.BackgroundColor3 = Color3.fromRGB(12, 255, 0)
end
button.LayoutOrder = 100 - math.floor(serverFilled * 100)
button.leave.Visible = true
local mainColor = Color3.new(1,1,1)
if jobId == game.JobId then
button.LayoutOrder = -1
mainColor = Color3.fromRGB(0, 255, 255)
button.leave.Visible = false
end
button.id.TextColor3 = mainColor
button.players.BorderColor3 = mainColor
button.players.progress.BorderColor3 = mainColor
local idString = string.gsub(jobId,"[^%d*]","")
local seed = math.floor((tonumber(idString)^(1/2))) + 1
local rand = Random.new(seed)
button.ImageColor3 = Color3.new(
rand:NextNumber(),
rand:NextNumber(),
rand:NextNumber()
)
button.leave.Activated:connect(function()
local success, err = network:invokeServer("playerRequest_teleportToJobId", jobId)
end)
button.Parent = gui.servers
end
end
local function main()
serversDataValue = game.ReplicatedStorage:WaitForChild("serversData")
updated()
serversDataValue.Changed:connect(updated)
end
spawn(main)
end
return module
================================================
FILE: src/StarterGui/serverMessage.lua
================================================
local module = {}
local ui = script.Parent.gameUI.leftBar.serverMessage
function module.init(Modules)
local network = Modules.network
local configuration = Modules.configuration
local tween = Modules.tween
local currentMessage
function module.displayMessage(message)
ui.contents.ImageColor3 = Color3.fromRGB(18, 18, 18)
ui.Visible = true
currentMessage = message
ui.contents.body.text.Text = currentMessage
local textBounds = ui.contents.body.text.TextBounds
ui.Size = UDim2.new(0, 280, 0, 62 + textBounds.Y)
for i=1,4 do
local flare = ui.flare:Clone()
flare.Name = "flareCopy"
flare.Parent = ui
flare.Visible = true
flare.Size = UDim2.new(1,4,1,4)
flare.Position = UDim2.new(0,-2,0.5,0)
flare.AnchorPoint = Vector2.new(0,0.5)
local x = (180 - 40*i)
local y = (14 - 2*i)
local EndPosition = UDim2.new(0,-y/2,0.5,0)
local EndSize = UDim2.new(1,x,1,y)
tween(flare,{"Position","Size","ImageTransparency"},{EndPosition, EndSize, 1},0.5*i)
end
end
function module.hide()
if ui.Visible then
currentMessage = ""
ui.Visible = false
end
end
ui.contents.Activated:connect(module.hide)
ui.contents.MouseEnter:connect(function()
ui.contents.ImageColor3 = Color3.fromRGB(12, 0, 63)
end)
ui.contents.MouseLeave:connect(function()
ui.contents.ImageColor3 = Color3.fromRGB(18, 18, 18)
end)
spawn(function()
network:connect("gameConfigurationUpdated", "Event", function(playerConfiguration)
local gameMessage = playerConfiguration.gameDisplayMessage
if gameMessage and #gameMessage > 0 then
module.displayMessage(gameMessage)
else
module.hide()
end
end)
local gameMessage = configuration.getConfigurationValue("gameDisplayMessage")
if gameMessage and #gameMessage > 0 then
module.displayMessage(gameMessage)
else
module.hide()
end
end)
end
--playerConfiguration.gameDisplayMessage
return module
================================================
FILE: src/StarterGui/settings.lua
================================================
local module = {}
local runService = game:GetService("RunService")
local player = game.Players.LocalPlayer
local playerGui = player.PlayerGui
local ui = playerGui.gameUI.menu_settings
function module.show()
ui.Visible = not ui.Visible
end
function module.hide()
ui.Visible = false
end
ui.close.Activated:connect(module.hide)
function module.refreshKeybinds()
warn("settings.refreshKeybinds not ready")
end
-- module.remapTarget
local userSettings
function module.init(Modules)
local network = Modules.network
ui.close.Activated:connect(function()
Modules.focus.toggle(ui)
end)
-- PAGE NAVIGATION
local function openPage(pageName)
for i,page in pairs(ui.pages:GetChildren()) do
if page:IsA("GuiObject") then
page.Visible = false
end
end
for i,pageButton in pairs(ui.header.buttons:GetChildren()) do
if pageButton:IsA("ImageButton") then
pageButton.ImageColor3 = Color3.fromRGB(212, 212, 212)
end
end
local targetPage = ui.pages:FindFirstChild(pageName)
local targetPageButton = ui.header.buttons:FindFirstChild(pageName)
targetPage.Visible = true
targetPageButton.ImageColor3 = Color3.fromRGB(90, 225, 255)
end
openPage("options")
for i,pageButton in pairs(ui.header.buttons:GetChildren()) do
if pageButton:IsA("GuiButton") then
pageButton.Activated:connect(function()
openPage(pageButton.Name)
end)
end
end
-- OPTIONS
-- volume slider
local volumeFrame = ui.pages.options.volume
volumeFrame.bar.InputBegan:connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
local startPointX = volumeFrame.bar.AbsolutePosition.X
local endPointX = volumeFrame.bar.AbsolutePosition.X + volumeFrame.bar.AbsoluteSize.X
local volume = volumeFrame.bar.slider.Position.X.Scale
while input.UserInputState ~= Enum.UserInputState.Cancel and input.UserInputState ~= Enum.UserInputState.End do
local inputPointX = input.Position.X - startPointX
local newVolume = math.clamp(inputPointX / (endPointX - startPointX), 0, 1)
if newVolume <= 0.05 then
newVolume = 0
end
if volume ~= newVolume then
-- fire volume changed
network:fire("musicVolumeChanged", newVolume)
volume = newVolume
end
volumeFrame.bar.slider.Position = UDim2.new(volume, 0, 0.5, 0)
runService.Heartbeat:wait()
end
-- post volume changes to server
if userSettings and (userSettings.musicVolume == nil or userSettings.musicVolume ~= volume) then
network:invokeServer("requestChangePlayerSetting", "musicVolume", volume)
end
end
end)
-- main menu
ui.pages.mainMenu.mainMenu.Activated:Connect(function()
network:invokeServer("playerRequest_returnToMainMenu")
end)
-- KEYBINDS
local keybinds = ui.pages.keybinds
local keybindSample = keybinds:WaitForChild("sampleAction")
keybindSample.Visible = false
keybindSample.Parent = script
module.keybindSample = keybindSample
local function buttonClicked(button)
local oldTarget = module.remapTarget
if oldTarget then
oldTarget.ImageColor3 = keybindSample.ImageColor3
if oldTarget == button then
module.remapTarget = nil
return true
end
end
module.remapTarget = button
button.ImageColor3 = Color3.fromRGB(61, 255, 74)
end
function module.open()
Modules.focus.toggle(ui)
end
function module.refreshKeybinds()
-- after hitting my head with a brick twenty times, this is where I call it a day
if keybindSample:FindFirstChild("title") == nil then
return false
end
module.remapTarget = nil
for i,child in pairs(keybinds:GetChildren()) do
if child:IsA("GuiObject") then
child:Destroy()
end
end
local count = 0
for actionName,action in pairs(Modules.input.actions) do
local keybind = keybindSample:Clone()
keybind.Name = actionName
keybind.title.Text = actionName
keybind.Parent = keybinds
keybind.LayoutOrder = action.priority
keybind.Visible = true
keybind.MouseButton1Click:connect(function()
buttonClicked(keybind)
end)
count = count + 1
end
local ysize = 10 + math.ceil(count/2) * (keybindSample.AbsoluteSize.Y + 5)
keybinds.CanvasSize = UDim2.new(0,0,0,ysize)
end
userSettings = network:invoke("getCacheValueByNameTag", "userSettings")
if userSettings.musicVolume then
volumeFrame.bar.slider.Position = UDim2.new(userSettings.musicVolume, 0, 0.5, 0)
end
network:connect("propogationRequestToSelf", "Event", function(key, value)
if key == "userSettings" then
userSettings = value
if userSettings.musicVolume then
volumeFrame.bar.slider.Position = UDim2.new(userSettings.musicVolume, 0, 0.5, 0)
end
end
end)
--network:invokeServer("requestChangePlayerSetting", "clearingInteraction", true)
end
return module
================================================
FILE: src/StarterGui/shop.lua
================================================
-- local interface for shop
-- author: not damien
local module = {}
local ui = script.Parent.gameUI.menu_shop
function module.open()
end
function module.close()
ui.Visible = false
end
--[[
FOR DAMIEN:
to initiate a sell in the shop, set currentItem.Value to the INTERNAL NAME of the item being sold.
it will be converted to item id when sending to server
set amount.Value to how many items are in the stack. This should be the default sell amount
set slotInfo to whatever you need to, or find the other "for damien:" comment and edit that part of code
--]]
-- todo: store in NPC instead of hard-coding
local defaultShopItems = {"health potion", "health potion 2", "wooden club", "wooden sword", "pitchfork", "oak axe"}
local replicatedStorage = game:GetService("ReplicatedStorage")
local itemData = require(replicatedStorage.itemData)
function module.init(Modules)
local network = Modules.network
local fx = Modules.fx
local tween = Modules.tween
local utilities = Modules.utilities
local economy = Modules.economy
function module.close(fromInteract)
ui.Visible = false
if not fromInteract then
warn("stop interact")
Modules.interaction.stopInteract()
end
end
-- ugly hack
ui.close.Activated:connect(function() module.close() end)
local shopItems
function module.open()
Modules.focus.toggle(ui)
if ui.Visible then
network:fire("localSignal_shopOpened")
end
end
-- Reset the buy/sell frame
local currentItemData = {}
local inventoryModule
-- Update the buy/sell frame
local function update()
ui.buy.content.amount.value.Text = tostring(ui.amount.Value)
ui.sell.selling.amount.value.Text = tostring(ui.amount.Value)
if not currentItemData.itemBaseData then
-- ui.sell.selling.Visible = false
-- ui.sell.empty.Visible = true
ui.sell.Visible = false
ui.sell.item.itemThumbnail.Image = ""
ui.buy.Visible = false
return
end
local itemName = currentItemData.itemBaseData.module.Name
for i,shopItem in pairs(ui.curve.contents:GetChildren()) do
if shopItem:IsA("ImageButton") then
if shopItem.Name ~= itemName then
shopItem.ImageColor3 = Color3.fromRGB(30, 30, 30)
shopItem.frame.Inner.ImageColor3 = Color3.fromRGB(30, 30, 30)
else
shopItem.ImageColor3 = Color3.fromRGB(9, 39, 58)
shopItem.frame.Inner.ImageColor3 = Color3.fromRGB(9, 39, 58)
end
end
end
if currentItemData.itemBaseData then
-- ui.sell.empty.Visible = false
ui.sell.selling.Visible = true
ui.sell.Visible = true
if currentItemData.itemBaseData.name then
--local sellValue = currentItemData.itemBaseData.sellValue or 1
if ui.buy.Visible and currentItemData.itemBaseData.buyValue then
ui.buy.content.itemName.Text = currentItemData.itemBaseData.name
ui.buy.item.itemThumbnail.Image = currentItemData.itemBaseData.image or "rbxassetid://2679574493"
-- ui.buy.content.cost.Text = currentItemData.itemBaseData.buyValue * ui.amount.Value
if currentItemData.costInfo then
Modules.money.setLabelAmount(
ui.buy.content.cost,
currentItemData.cost * ui.amount.Value,
currentItemData.costInfo
)
else
local statistics_final = network:invoke("getCacheValueByNameTag", "nonSerializeData").statistics_final
local coinCostReduction = 1 - math.clamp(statistics_final.merchantCostReduction, 0, 1)
Modules.money.setLabelAmount(
ui.buy.content.cost,
math.clamp(currentItemData.cost * ui.amount.Value * coinCostReduction, 1, math.huge),
currentItemData.costInfo
)
end
elseif ui.sell.Visible then
ui.sell.selling.itemName.Text = currentItemData.itemBaseData.name
ui.sell.item.itemThumbnail.Image = currentItemData.itemBaseData.image or "rbxassetid://2679574493"
local inventorySlotData = currentItemData.inventorySlotData or {}
local titleColor
if inventorySlotData then
titleColor = Modules.itemAcquistion.getTitleColorForInventorySlotData(inventorySlotData)
end
ui.sell.selling.itemName.TextColor3 = titleColor or Color3.new(1,1,1)
local inventoryItem = ui.sell.item.curve
inventoryItem.stars.Visible = false
local upgrades = inventorySlotData.successfulUpgrades
if upgrades then
for i,child in pairs(inventoryItem.stars:GetChildren()) do
if child:IsA("ImageLabel") then
child.ImageColor3 = titleColor or Color3.new(1,1,1)
child.Visible = false
elseif child:IsA("TextLabel") then
child.TextColor3 = titleColor or Color3.new(1,1,1)
child.Visible = false
end
end
inventoryItem.stars.Visible = true
if upgrades <= 3 then
for i,star in pairs(inventoryItem.stars:GetChildren()) do
local score = tonumber(star.Name)
if score then
star.Visible = score <= upgrades
end
end
inventoryItem.stars.exact.Visible = false
else
inventoryItem.stars["1"].Visible = true
inventoryItem.stars.exact.Visible = true
inventoryItem.stars.exact.Text = upgrades
end
inventoryItem.stars.Visible = true
end
-- ui.sell.selling.cost.Text = (currentItemData.itemBaseData.sellValue or 1) * ui.amount.Value
local sellValue = economy.getSellValue(currentItemData.itemBaseData, currentItemData.inventorySlotData)
Modules.money.setLabelAmount(ui.sell.selling.cost, (sellValue) * ui.amount.Value)
end
end
else
ui.buy.Visible = false
-- ui.sell.empty.Visible = true
-- ui.sell.selling.Visible = false
ui.sell.Visible = false
end
end
local function reset()
currentItemData = {}
ui.ethyr.Visible = false
update()
if Modules.input.mode.Value == "xbox" then
game.GuiService.SelectedObject = ui.sell.item.itemThumbnail
end
end
reset()
-- Request to buy the currently selected item
local function requestBuy()
if ui.buy.Visible and inventoryModule then
if currentItemData.itemBaseData then
if currentItemData.itemBaseData.buyValue then
-- todo: client-side check of money
local id = currentItemData.itemBaseData.id
local amount = ui.amount.Value
local itemCostInfo = currentItemData.costInfo
local success, reason = network:invokeServer("playerRequest_buyItemFromShop", {id = id, costInfo = itemCostInfo}, amount, inventoryModule)
-- todo: feedback for request status
if success then
fx.statusRibbon(ui, "Bought "..currentItemData.itemBaseData.name, "success", 3)
else
fx.statusRibbon(ui, "Failed to purchase.", "fail", 3)
warn(reason)
if itemCostInfo.costType == "ethyr" then
Modules.products.open()
end
end
reset()
end
end
end
end
ui.buy.content.no.MouseButton1Click:connect(reset)
ui.sell.selling.no.MouseButton1Click:connect(reset)
ui.amount.Changed:connect(update)
-- Set the buy frame (disables shop frame)
local function promptBuyItem(itemName, cost, costInfo)
ui.sell.Visible = false
ui.buy.Visible = true
if Modules.input.mode.Value == "xbox" then
game.GuiService.SelectedObject = ui.buy.content.no
end
--[[
if costInfo and costInfo.costType and costInfo.costType == "ethyr" then
ui.ethyr.Visible = true
else
ui.ethyr.Visible = false
end
]]
if currentItemData.itemBaseData and currentItemData.itemBaseData.name == itemName and ui.amount.Value == 1 then
update()
end
ui.amount.Value = 1
end
local function promptSellItem(itemName)
ui.sell.Visible = true
ui.sell.selling.Visible = true
-- ui.sell.empty.Visible = false
ui.buy.Visible = true
if currentItemData.itemBaseData and currentItemData.itemBaseData.name == itemName and ui.amount.Value == 1 then
update()
end
-- for damien: make this the amount you need
ui.amount.Value = 1
end
local function int__setCurrentItem(inventorySlotData, isSelling, cost, costInfo)
local itemId = inventorySlotData.id
local inventorySlotDataPosition = inventorySlotData.position
-- default to the size of the selected stack
local quantity = inventorySlotData.stacks or 1
ui.amount.Value = quantity
currentItemData = { id = itemId; inventorySlotDataPosition = inventorySlotDataPosition; inventorySlotData = inventorySlotData; itemBaseData = itemData[itemId]; cost = cost; costInfo = costInfo; }
-- toggle
ui.buy.Visible = not isSelling
ui.sell.Visible = not not isSelling
update()
end
network:create("shop_setCurrentItem", "BindableFunction", "OnInvoke", int__setCurrentItem)
-- Request to sell the currently selected item
local function requestSell()
if ui.sell.Visible and ui.sell.selling.Visible then
if not currentItemData.itemBaseData then reset() return end
local itemName = currentItemData.itemBaseData.name
if currentItemData.itemBaseData then
if currentItemData.itemBaseData.cantSell then
return false
end
local itemName = currentItemData.itemBaseData.name
local id = currentItemData.id
local amount = ui.amount.Value
-- for damien: may need to change this
local category = network:invoke("getCurrentInventoryCategory")
local success = network:invokeServer("playerRequest_sellItemToShop", {id = id; position = currentItemData.inventorySlotDataPosition}, amount)
if success then
fx.statusRibbon(ui, "Sold "..itemName, "gold", 3)
else
fx.statusRibbon(ui, "'fraid you can't do that :/", "fail", 3)
end
reset()
end
end
end
ui.buy.content.yes.MouseButton1Click:connect(requestBuy)
ui.sell.selling.yes.MouseButton1Click:connect(requestSell)
-- todo: exponential increase/decrease
local function increase()
if ui.amount.Value < 999 then
ui.amount.Value = ui.amount.Value + 1
end
end
local function decrease()
if ui.amount.Value > 1 then
ui.amount.Value = ui.amount.Value - 1
end
end
local function buyAmountValueChanged()
local buyAmount = tonumber(ui.buy.content.amount.value.Text)
if buyAmount then
ui.amount.Value = buyAmount
update()
end
end
local function sellAmountValueChanged()
local sellAmount = tonumber(ui.sell.selling.amount.value.Text)
if sellAmount then
ui.amount.Value = sellAmount
update()
end
end
ui.buy.content.amount.increase.MouseButton1Click:connect(increase)
ui.buy.content.amount.decrease.MouseButton1Click:connect(decrease)
ui.sell.selling.amount.increase.MouseButton1Click:connect(increase)
ui.sell.selling.amount.decrease.MouseButton1Click:connect(decrease)
ui.buy.content.amount.value.FocusLost:connect(buyAmountValueChanged)
ui.sell.selling.amount.value.FocusLost:connect(sellAmountValueChanged)
-- Setup
local lastSelected
local sample = ui.curve.contents:WaitForChild("sampleItem")
sample.Visible = false
sample.Parent = script
local function refreshShopInventory()
ui.ethyr.Visible = false
for i,existingItem in pairs(ui.curve.contents:GetChildren()) do
if existingItem:IsA("GuiObject") and existingItem:FindFirstChild("itemName") then
existingItem:Destroy()
end
end
local actualShopItems = 0
for i,shopItemInfo in pairs(shopItems) do
local shopItemName
local costInfo
local cost
if typeof(shopItemInfo) == "string" then
shopItemName = shopItemInfo
costInfo = {}
elseif typeof(shopItemInfo) == "table" then
shopItemName = shopItemInfo.itemName
costInfo = shopItemInfo.costInfo
if costInfo and costInfo.costType and costInfo.costType == "ethyr" then
-- ui.ethyr.Visible = true
end
cost = shopItemInfo.cost
else
error("Invalid shopItemInfo type. Failed to refresh shop inventory.")
end
local itemBaseData = itemData[shopItemName]
if itemBaseData and itemBaseData.name then
local costType = costInfo and costInfo.costType or "money"
cost = cost or itemBaseData.buyValue
if cost and costType then
local shopItem = sample:Clone()
shopItem.Name = shopItemName
shopItem.itemName.Text = itemBaseData.name
local itemType = itemBaseData.itemType
shopItem.itemName.cuteDecor.Image = "rbxgameasset://Images/category_"..itemType
shopItem.item.itemThumbnail.Image = itemBaseData.image or "rbxassetid://2679574493"
-- shopItem.cost.Text = "$"..itemBaseData.buyValue
Modules.money.setLabelAmount(shopItem.cost, cost, costInfo)
--[[
if costInfo.textColor then
shopItem.cost.amount.TextColor3 = costInfo.textColor
end
if costInfo.icon then
shopItem.cost.icon.Image = costInfo.icon
end
if costType ~= "money" and cost > 999 then
shopItem.cost.amount.Text = cost
end
]]
shopItem.Parent = ui.curve.contents
shopItem.LayoutOrder = i
shopItem.Visible = true
shopItem.MouseButton1Click:connect(function()
int__setCurrentItem(itemBaseData, nil, cost, costInfo)
promptBuyItem(shopItemName, cost, costInfo)
end)
shopItem.item.itemThumbnail.MouseButton1Click:connect(function()
int__setCurrentItem(itemBaseData, nil, cost, costInfo)
promptBuyItem(shopItemName, cost, costInfo)
end)
local inventorySlotData = {id = itemBaseData.id}
if shopItemInfo.attributes then
for attribute, value in pairs(shopItemInfo.attributes) do
if not inventorySlotData[attribute] then
inventorySlotData[attribute] = value
end
end
end
local function selected()
network:invoke("populateItemHoverFrame", itemBaseData, "shop", inventorySlotData)
lastSelected = shopItem
end
local function deselected()
if lastSelected == shopItem then
network:invoke("populateItemHoverFrame")
end
end
shopItem.item.itemThumbnail.MouseEnter:connect(selected)
shopItem.item.itemThumbnail.MouseLeave:connect(deselected)
shopItem.SelectionGained:connect(selected)
shopItem.SelectionLost:connect(deselected)
local titleColor
if itemBaseData then
titleColor = Modules.itemAcquistion.getTitleColorForInventorySlotData(inventorySlotData)
-- titleColor = Modules.itemAcquistion.getTitleColorForInventorySlotData(itemBaseData)
end
-- titleColor = titleColor or itemBaseData.nameColor
shopItem.shine.Visible = titleColor ~= nil
shopItem.shine.ImageColor3 = titleColor or Color3.fromRGB(179, 178, 185)
shopItem.frame.ImageColor3 = titleColor or Color3.fromRGB(106, 105, 107)
shopItem.shine.ImageColor3 = titleColor or Color3.fromRGB(179, 178, 185)
shopItem.itemName.TextColor3 = titleColor or Color3.new(1,1,1)
Modules.fx.setFlash(shopItem.frame, shopItem.shine.Visible)
actualShopItems = actualShopItems + 1
end
end
end
ui.curve.contents.CanvasSize = UDim2.new(0, 0, 0, 20 + math.ceil(actualShopItems) * (70 + 5))
end
function module.open(shopObject)
reset()
ui.curve.contents.CanvasPosition = Vector2.new()
if ui.Visible then
ui.Visible = not ui.Visible
else
network:fire("localSignal_shopOpened")
--ui.Visible = true
-- ui.Parent.Parent.UIScale.Scale = (Modules.input.menuScale or 1) * 0.75
-- Modules.tween(ui.Parent.Parent.UIScale, {"Scale"}, (Modules.input.menuScale or 1), 1, Enum.EasingStyle.Bounce)
if shopObject then
if shopObject:FindFirstChild("inventory") then
inventoryModule = shopObject.inventory
shopItems = require(shopObject.inventory)
if inventoryModule:FindFirstChild("shopName") then
ui.header.itemType.Text = inventoryModule:FindFirstChild("shopName").Value
else
ui.header.itemType.Text = "Merchant"
end
else
inventoryModule = nil
shopItems = {}
end
if shopObject:IsA("BasePart") then
--local cf = shopObject.CFrame:ToWorldSpace(camCf)
--network:invoke("lockCameraPosition",cf)
end
end
refreshShopInventory()
-- Modules.playerMenu.selectMenu(ui, Modules[script.Name])
ui.Visible = true
--[[
ui.Visible = true
if ui.Parent.tradeFrame.Visible then
ui.Parent.tradeFrame.Visible = false
Modules.trading.endTrade(true)
end
ui.Parent.equipFrame.Visible = false
ui.Parent.Parent.Visible = true
Modules.focus.change(ui.Parent.Parent)
]]
end
end
network:create("openShop","BindableFunction","OnInvoke", module.open)
end
return module
================================================
FILE: src/StarterGui/social.lua
================================================
local module = {}
module.friends = {}
module.places = {
["Main Menu"] = 2376885433;
["Free Demo"] = 2015602902;
["Mushtown"] = 2064647391;
["Mushroom Forest"] = 2035250551;
["Mushroom Grotto"] = 2060360203;
["The Clearing"] = 2060556572;
["Altdorf"] = 2119298605;
["Farmlands"] = 2180867434;
["Enchanted Forest"] = 2260598172;
["Redwood Pass"] = 2376890690;
["Seaside Path"] = 2093766642;
["Testing Environment"] = 2061558182;
}
local places = module.places
local function isPlaceInGame(placeId)
for i,place in pairs(places) do
if place == placeId then
return true
end
end
end
local player = game.Players.LocalPlayer
function module.init(Modules)
local function updateFriends()
local success, err = pcall(function()
local friendsInfo = player:GetFriendsOnline()
local friendsList = {}
for i,friend in pairs(friendsInfo) do
if friend.IsOnline and friend.PlaceId and isPlaceInGame(friend.PlaceId) then
friendsList[friend.VisitorId] = friend
end
end
module.friends = friendsList
end)
if not success then
warn("Failed to fetch online friends")
warn(err)
end
end
spawn(function()
updateFriends()
while wait(30) do
updateFriends()
end
end)
end
return module
================================================
FILE: src/StarterGui/stamina.lua
================================================
local module = {}
local player = game.Players.LocalPlayer
local ui = script.Parent.gameUI.stamina
function module.init(Modules)
ui.BackgroundTransparency = 1
ui.value.BackgroundTransparency = 1
ui.Active = false
ui.Visible = true
local function updateStamina()
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart:FindFirstChild("stamina") and player.Character.PrimaryPart:FindFirstChild("maxStamina") then
if player.Character.PrimaryPart.stamina.Value < player.Character.PrimaryPart.maxStamina.Value then
ui.Size = UDim2.new(0, 10, 0, player.Character.PrimaryPart.maxStamina.Value * 10)
Modules.tween(ui.value,{"Size"},UDim2.new(1,0,player.Character.PrimaryPart.stamina.Value / player.Character.PrimaryPart.maxStamina.Value,0),0.1, Enum.EasingStyle.Linear)
if not ui.Active then
ui.Active = true
Modules.tween(ui.value,{"BackgroundTransparency"},0,0.5)
Modules.tween(ui,{"BackgroundTransparency"},0,0.5)
end
if player.Character.PrimaryPart.stamina.Value <= 0 then
ui.value.Visible = false
spawn(function()
for i=1,5 do
ui.BorderSizePixel = 2
wait(0.1)
ui.BorderSizePixel = 0
wait(0.1)
end
if player.Character.PrimaryPart.stamina.Value <= 0 and Modules.network:invoke("doesPlayerHaveStatusEffect", "heat exhausted") then
ui.BorderSizePixel = 2
end
end)
else
ui.value.Visible = true
ui.BorderSizePixel = 0
local color = Color3.fromRGB(46, 153, 46)
if Modules.network:invoke("doesPlayerHaveStatusEffect", "empower", nil, "haste") then
color = Color3.fromRGB(45, 191, 162)
elseif Modules.network:invoke("doesPlayerHaveStatusEffect", "heat exhausted") then
color = Color3.fromRGB(255, 255, 127)
end
ui.value.BackgroundColor3 = color
end
else
if ui.Active then
Modules.tween(ui.value,{"Size"},UDim2.new(1,0,1,0),0.1, Enum.EasingStyle.Linear)
ui.Active = false
Modules.tween(ui.value,{"BackgroundTransparency"},1,0.5)
Modules.tween(ui,{"BackgroundTransparency"},1,0.5)
end
end
end
end
spawn(function()
local startTime = tick()
repeat wait() until (player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart:FindFirstChild("stamina") and player.Character.PrimaryPart:FindFirstChild("maxStamina")) or tick() - startTime > 10
updateStamina()
player.Character.PrimaryPart.stamina.Changed:connect(updateStamina)
end)
end
return module
================================================
FILE: src/StarterGui/statusBars.lua
================================================
-- Player name, health, mana, etc. display
-- berezaa
local module = {}
local characterPrimaryPart
local frame = script.Parent.gameUI.bottomRight.statusBars
local healthUI = frame.healthBar.value
local damageUI = frame.healthBar.damageValue
local tweenService = game:GetService("TweenService")
local damageHealthTween
-- the higher this number the more 'bursty' damage looks
local healthBurstFactor = 1
function module.init(Modules)
for _, child in pairs(game.Lighting:GetChildren()) do
if child.Name == "DamageColor" or child.Name == "DamageBlur" then
child:Destroy()
end
end
local ColorEffect = Instance.new("ColorCorrectionEffect")
ColorEffect.Name = "DamageColor"
ColorEffect.Parent = game.Lighting
local BlurEffect = Instance.new("BlurEffect")
BlurEffect.Name = "DamageBlur"
BlurEffect.Parent = game.Lighting
BlurEffect.Size = 0
local tween = Modules.tween
local damagedScreen = frame.Parent.Parent.vin_low_health
--frame.header.username.value.Text = game.Players.LocalPlayer.Name
function module.manaWarning()
spawn(function()
for _ = 1, 3 do
-- frame.manaBar.frame.Visible = true
frame.manaBar.ImageColor3 = Color3.fromRGB(134, 17, 17)
wait()
-- frame.manaBar.frame.Visible = false
frame.manaBar.ImageColor3 = Color3.fromRGB(16, 16, 16)
wait()
end
end)
end
repeat wait() until game.Players.LocalPlayer.Character
local character = game.Players.LocalPlayer.Character
while not character.PrimaryPart and character.Parent and character:IsDescendantOf(workspace) do
local primaryPart = character:WaitForChild("hitbox", 1)
if primaryPart then
character.PrimaryPart = primaryPart
break
end
end
characterPrimaryPart = character.PrimaryPart
characterPrimaryPart:WaitForChild("health")
local lastHealth = characterPrimaryPart.health.Value
local function healthRefresh(ini)
local delta = characterPrimaryPart.health.Value - lastHealth
local change = characterPrimaryPart.health.Value - lastHealth
lastHealth = characterPrimaryPart.health.Value
local humanoidHealth = characterPrimaryPart.health.Value
local humanoidMaxHealth = characterPrimaryPart.maxHealth.Value
local percent = math.clamp(characterPrimaryPart.health.Value / characterPrimaryPart.maxHealth.Value, 0, 1)
local manaPercent = math.clamp(characterPrimaryPart.mana.Value / characterPrimaryPart.maxMana.Value, 0, 1)
local percentDelta = delta/humanoidMaxHealth
local healthPercent = humanoidHealth/humanoidMaxHealth
damagedScreen.Visible = healthPercent < 0.4 and healthPercent > 0
if damagedScreen.Visible then
damagedScreen.UIScale.Scale = 1 + healthPercent * 10
end
if ini then
frame.healthBar.value.Size = UDim2.new(math.max(percent,0),0,1,0)
frame.manaBar.value.Size = UDim2.new(math.max(manaPercent,0),0,1,0)
else
tween(frame.healthBar.value, {"Size"}, UDim2.new(math.max(percent,0),0,1,0), 0.3)
tween(frame.manaBar.value, {"Size"}, UDim2.new(math.max(manaPercent,0),0,1,0), 0.3)
end
frame.healthBar.title.Text = math.floor(characterPrimaryPart.health.Value + 0.5) .. "/" .. math.floor(characterPrimaryPart.maxHealth.Value + 0.5)
frame.manaBar.title.Text = math.floor(characterPrimaryPart.mana.Value + 0.5) .. "/" .. math.floor(characterPrimaryPart.maxMana.Value + 0.5)
if delta < 0 then
local thresh = percentDelta^2 + math.clamp(1-healthPercent, 0, 1)^3
local duration = 0.15 + thresh^3
tween(ColorEffect,{"TintColor","Contrast","Saturation"},{Color3.fromRGB(255,255 - thresh * 150,255 - thresh * 150),thresh*2, thresh},duration/3)
tween(BlurEffect,{"Size"},thresh * 5,duration/3)
spawn(function()
wait(duration/2)
tween(ColorEffect,{"TintColor","Contrast","Saturation"},{Color3.fromRGB(255,255,255),0, 0},duration/2)
tween(BlurEffect,{"Size"},0,duration/2)
end)
spawn(function()
local healthpercent = characterPrimaryPart.health.Value/characterPrimaryPart.maxHealth.Value
local changepercent = math.abs(change/characterPrimaryPart.maxHealth.Value)
local repre = frame.healthBar.instant:Clone()
repre.Name = "instantClone"
repre.Size = UDim2.new(changepercent, 5, 1, 0)
repre.Position = UDim2.new(healthpercent, -5, 0.5, 0)
repre.Parent = frame.healthBar
repre.Visible = true
tween(repre, {"Size", "ImageTransparency"}, {UDim2.new(changepercent, 5, 1 + changepercent * 30, 0), 1}, 0.25 + changepercent * 2)
game.Debris:AddItem(repre, 0.25 + changepercent * 2)
--[[
for i=1,3 do
frame.healthBar.ImageColor3 = Color3.new(1,1,1)
wait(0.08)
frame.healthBar.ImageColor3 = Color3.fromRGB(16, 16, 16)
wait(0.08)
end
]]
end)
local healthDelta = -change
if healthDelta > 0 then
if damageHealthTween and damageHealthTween.PlaybackState == Enum.PlaybackState.Playing then
damageHealthTween:Cancel()
end
healthUI.Size = UDim2.new(humanoidHealth / humanoidMaxHealth, 0, 1, 0)
damageUI.Position = UDim2.new(math.clamp(humanoidHealth / humanoidMaxHealth - 0.1, 0, 1), 0, 0, 0)
damageUI.Size = UDim2.new(math.clamp(healthDelta / humanoidMaxHealth + 0.1, 0, 1), 0, 1, 0)
damageUI.bar.ImageColor3 = Color3.fromRGB(255, 255, 150 + 50 * (healthDelta / humanoidMaxHealth))
--damageUI.bar.ImageTransparency = 1
local tweenInfo = TweenInfo.new(
0.5 * (healthDelta / humanoidMaxHealth) ^ (1 / 3) * healthBurstFactor,
Enum.EasingStyle.Quart,
Enum.EasingDirection.Out,
0,
false,
0
)
damageHealthTween = tweenService:Create(
damageUI.bar,
tweenInfo,
{ImageColor3 = Color3.fromRGB(255, 44, 44)}
--{ImageColor3 = Color3.fromRGB(255, 44, 44); ImageTransparency = 0}
)
local connection
connection = damageHealthTween.Completed:connect(function(playbackState)
connection:disconnect()
if playbackState == Enum.PlaybackState.Completed then
tweenInfo = TweenInfo.new(
0.5 * (1 - healthDelta / humanoidMaxHealth) ^ (1 / 3) * healthBurstFactor,
Enum.EasingStyle.Quad,
Enum.EasingDirection.Out,
0,
false,
0
)
damageHealthTween = tweenService:Create(
damageUI,
tweenInfo,
{Size = UDim2.new(0, 0, 1, 0)}
)
damageHealthTween:Play()
end
end)
damageHealthTween:Play()
end
else
if characterPrimaryPart.health.Value - lastHealth > 5 and delta > 0.01 then
local thresh = 0.3 + math.abs(delta)
local duration = thresh / 1.4
tween(ColorEffect,{"TintColor","Contrast"},{Color3.fromRGB(255 - thresh * 150,255,255 - thresh * 150),-thresh/3},duration/2)
spawn(function()
wait(duration/1.5)
tween(ColorEffect,{"TintColor","Contrast"},{Color3.fromRGB(255,255,255),0},duration/2)
end)
end
end
end
characterPrimaryPart.health.Changed:connect(healthRefresh)
characterPrimaryPart.mana.Changed:connect(healthRefresh)
characterPrimaryPart.maxHealth.Changed:connect(healthRefresh)
characterPrimaryPart.maxMana.Changed:connect(healthRefresh)
healthRefresh(true)
local function inputUpdate()
--[[
if Modules.input.mode.Value == "mobile" then
frame.UIListLayout.FillDirection = Enum.FillDirection.Vertical
frame.UIListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Right
frame.Position = UDim2.new(1, -295,1, -74)
frame.AnchorPoint = Vector2.new(1,0)
else
frame.UIListLayout.FillDirection = Enum.FillDirection.Horizontal
frame.UIListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
frame.Size = UDim2.new(0.65,0,0,50)
frame.Position = UDim2.new(0.5, 0, 1, -120)
frame.AnchorPoint = Vector2.new(0.5,0)
end
]]
end
Modules.input.mode.changed:connect(inputUpdate)
inputUpdate()
end
return module
================================================
FILE: src/StarterGui/statusEffects.lua
================================================
-- local status effect display
-- berezaa
local module = {}
local ui = script.Parent.gameUI.statusEffects
function module.init(Modules)
local tween = Modules.tween
local utilities = Modules.utilities
local replicatedStorage = game:GetService("ReplicatedStorage")
local itemLookup = require(replicatedStorage:WaitForChild("itemData"))
local abilityLookup = require(replicatedStorage:WaitForChild("abilityLookup"))
local function updateStatusEffects(newValue)
for i, child in pairs(ui.contents:GetChildren()) do
if child:IsA("GuiObject") then
child:Destroy()
end
end
local character = game.Players.LocalPlayer.Character
if character and character.PrimaryPart then
local success, statusEffectData = utilities.safeJSONDecode(newValue)
if success then
for i, statusEffect in pairs(statusEffectData) do
if (not statusEffect.hideInStatusBar) and (not statusEffect.statusEffectModifier.hideInStatusBar) then
local indicator = ui.sample:Clone()
local sourceType = statusEffect.sourceType
local sourceId = statusEffect.sourceId
local variant = statusEffect.variant
local executionData = {variant = variant}
if not statusEffect.icon then
if sourceType == "item" then
local itemData = itemLookup[sourceId]
indicator.itemIcon.Image = itemData.image
indicator.itemIcon.Visible = true
elseif sourceType == "ability" then
local abilityData = abilityLookup[sourceId](nil, executionData)
indicator.Image = abilityData.image
indicator.itemIcon.Visible = false
end
else
local abilityData = abilityLookup[sourceId]
indicator.itemIcon.Image = statusEffect.icon
indicator.itemIcon.Visible = true
end
indicator.Parent = ui.contents
indicator.Visible = true
if statusEffect.ticksNeeded then
local durationLeft = (statusEffect.ticksNeeded - statusEffect.ticksMade) / Modules.configuration.getConfigurationValue("activeStatusEffectTickTimePerSecond")
indicator.progress.Size = UDim2.new(1, 0, 1 - (durationLeft / statusEffect.statusEffectModifier.duration), 0)
tween(indicator.progress, {"Size"}, UDim2.new(1,0,1,0), durationLeft - 0.5, Enum.EasingStyle.Linear)
game.Debris:AddItem(indicator, durationLeft)
else
end
end
end
end
end
--[{"sourceId":2,"duration":45,"id":"regenerate","durationLeft":45,"sourceType":"ability"}]
end
updateStatusEffects(game.Players.LocalPlayer.Character.PrimaryPart.statusEffectsV2.Value)
game.Players.LocalPlayer.Character.PrimaryPart.statusEffectsV2.changed:connect(updateStatusEffects)
end
return module
================================================
FILE: src/StarterGui/storage.lua
================================================
local module = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local itemData = require(replicatedStorage.itemData)
-- todo: remove this disgusting absolute reference
local player = game.Players.LocalPlayer
local playerGui = player.PlayerGui
local ui = playerGui.gameUI.menu_storage
local content = ui.Frame.content
local lastStorageDataReceived = nil
local storageSlotPairing = {}
local lastSelected
function module.init(Modules)
local uiCreator = Modules.uiCreator
local function onStorageItemMouseEnter(storageItem)
lastSelected = storageItem
local storageSlotData = storageSlotPairing[storageItem]
if storageSlotData then
local itemBaseData = itemData[storageSlotData.id]
if itemBaseData then
network:invoke("populateItemHoverFrame", itemBaseData, "storage", storageSlotData)
end
end
end
local function onStorageItemMouseLeave(storageItem)
if lastSelected == storageItem then
-- clears last selected
network:invoke("populateItemHoverFrame")
end
end
local storageItemTemplate = ui:WaitForChild("storageItemTemplate")
local function onStorageItemDoubleClicked(storageItem)
if storageSlotPairing[storageItem] then
-- transfer item back to storage
local success, reason = network:invokeServer("playerRequest_transferStorageToInventory", storageSlotPairing[storageItem])
end
end
local function onGetStorageSlotDataFromStorageItem(storageItem)
return storageSlotPairing[storageItem]
end
local function updateStorage(storageData)
if storageData then
lastStorageDataReceived = storageData
end
local currentSelected = game.GuiService.SelectedObject
local selectionName, selectionParent
if currentSelected and currentSelected:IsDescendantOf(ui) then
selectionName = currentSelected.Name
selectionParent = currentSelected.Parent
end
if lastStorageDataReceived then
storageSlotPairing = {}
for i, storageItem in pairs(content:GetChildren()) do
if storageItem:FindFirstChild("item") then
storageItem:Destroy()
end
end
local currCells = 0
for i, storageSlotData in pairs(lastStorageDataReceived) do
local storageItemBaseData = itemData[storageSlotData.id]
if storageItemBaseData then
local storageItem = storageItemTemplate:Clone()
if storageItemBaseData.canBeBound then
local tag = Instance.new("BoolValue")
tag.Name = "bindable"
tag.Parent = storageItem
end
storageItem.ImageTransparency = 0
storageItem.item.Image = storageItemBaseData.image
storageItem.item.ImageColor3 = Color3.new(1,1,1)
if storageSlotData.dye then
storageItem.item.ImageColor3 = Color3.fromRGB(storageSlotData.dye.r, storageSlotData.dye.g, storageSlotData.dye.b)
end
storageItem.item:WaitForChild("duplicateCount").Text = (storageSlotData.stacks and storageItemBaseData.canStack and storageSlotData.stacks > 1) and tostring(storageSlotData.stacks) or ""
storageItem.Parent = content
storageItem.LayoutOrder = i
storageItem.Name = tostring(i)
local storageDataCopy = utilities.copyTable(storageSlotData)
storageDataCopy.position = i
storageSlotPairing[storageItem.item] = storageDataCopy
local titleColor
if storageSlotData then
titleColor = Modules.itemAcquistion.getTitleColorForInventorySlotData(storageSlotData)
end
titleColor = titleColor or storageItemBaseData.nameColor
storageItem.frame.ImageColor3 = titleColor or Color3.fromRGB(106, 105, 107)
storageItem.shine.ImageColor3 = titleColor or Color3.fromRGB(179, 178, 185)
storageItem.shine.ImageTransparency = titleColor and 0.47 or 0.66
storageItem.item.MouseEnter:connect(function() onStorageItemMouseEnter(storageItem.item) end)
storageItem.item.SelectionGained:connect(function() onStorageItemMouseEnter(storageItem.item) end)
storageItem.item.MouseLeave:connect(function() onStorageItemMouseLeave(storageItem.item) end)
storageItem.item.SelectionLost:connect(function() onStorageItemMouseLeave(storageItem.item) end)
uiCreator.drag.setIsDragDropFrame(storageItem.item)
uiCreator.setIsDoubleClickFrame(storageItem.item, 0.2, onStorageItemDoubleClicked)
currCells = currCells + 1
end
end
for i = 1, 20 do
if not content:FindFirstChild(tostring(i)) then
local storageItem = ui.storageItemTemplate:Clone()
storageItem.item.duplicateCount.Text = ""
storageItem.item.Image = ""
storageItem.item.Visible = true
storageItem.frame.Visible = false
storageItem.shine.Visible = false
storageItem.ImageTransparency = 0.5
storageItem.shadow.ImageTransparency = 0.5
storageItem.Name = tostring(i)
storageItem.LayoutOrder = i
storageItem.Parent = content
uiCreator.drag.setIsDragDropFrame(storageItem.item)
end
end
content.CanvasSize = UDim2.new(0, 0, 0, math.ceil(math.max(currCells, 20) / 5) * 66)
if selectionParent and selectionName then
local newSelection = selectionParent:FindFirstChild(selectionName)
if newSelection then
game.GuiService.SelectedObject = newSelection
end
end
end
end
function module.open()
if ui.Visible then
Modules.playerMenu.closeSelected()
if ui.Parent.Parent.Visible then
Modules.focus.toggle(ui.Parent.Parent)
end
else
--ui.Visible = true
--[[
ui.Parent.Parent.UIScale.Scale = (Modules.input.menuScale or 1) * 0.75
Modules.tween(ui.Parent.Parent.UIScale, {"Scale"}, (Modules.input.menuScale or 1), 1, Enum.EasingStyle.Bounce)
]]
local globalData = network:invoke("getCacheValueByNameTag", "globalData")
if globalData and globalData.itemStorage then
updateStorage(globalData.itemStorage)
Modules.playerMenu.selectMenu(ui, Modules[script.Name])
end
end
end
function module.close()
Modules.playerMenu.closeSelected()
if ui.Parent.Parent.Visible then
Modules.focus.toggle(ui.Parent.Parent)
end
end
local function onPropogationRequestToSelf(propogationNameTag, propogationData)
if propogationNameTag == "globalData" and ui.Visible and propogationData.itemStorage then
updateStorage(propogationData.itemStorage)
end
end
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:create("getStorageSlotDataFromStorageItem", "BindableFunction", "OnInvoke", onGetStorageSlotDataFromStorageItem)
network:create("openStorage", "BindableFunction", "OnInvoke", function()
module.open()
end)
network:create("closeStorage", "BindableFunction", "OnInvoke", function()
module.close()
end)
end
return module
================================================
FILE: src/StarterGui/taxi.lua
================================================
local module = {}
local ui = script.Parent.gameUI.dialogueFrame.contents.taxi
function module.init(Modules)
local network = Modules.network
local utilities = Modules.utilities
local locations = network:invoke("getCacheValueByNameTag", "locations")
local buttonCount
local contents = ui.contents
local buttonCount = 0
for placeIdString, placeData in pairs(locations) do
local placeId = tonumber(placeIdString)
if placeId ~= game.PlaceId then
local originPlaceId = utilities.originPlaceId(placeId)
local button = ui.sample:Clone()
button.Name = placeIdString
button.LayoutOrder = math.floor((os.time() - placeData.visited) ^ 0.25)
button.location.Text = "-"
button.BGHolder.BG.Image = "https://www.roblox.com/Thumbs/Asset.ashx?width=768&height=432&assetId="..originPlaceId
button.BGHolder.BG.Visible = true
spawn(function()
local info = game.MarketplaceService:GetProductInfo(originPlaceId ,Enum.InfoType.Asset)
if info then
button.location.Text = info.Name
end
end)
button.location.Visible = true
button.Parent = contents
button.Active = true
button.Selectable = true
button.Visible = true
buttonCount = buttonCount + 1
button.location.TextWrapped = true
button.Activated:connect(function()
network:invoke("dialogueMoveToId", "spawns", {taxiLocation = placeIdString, taxiLocationName = button.location.Text})
end)
end
end
if buttonCount < 12 then
for i = buttonCount, 11 do
local button = ui.sample:Clone()
button.Name = "empty"
button.BGHolder.BG.Visible = false
button.location.Visible = false
button.empty.Visible = true
button.LayoutOrder = 999
local tooltip = Instance.new("StringValue")
tooltip.Name = "tooltip"
tooltip.Value = "Undiscovered location"
tooltip.Parent = button
button.Parent = contents
button.Active = true
button.Selectable = true
button.Visible = true
buttonCount = buttonCount + 1
button.Activated:connect(function()
network:invoke("dialogueMoveToId", "undiscovered")
end)
end
end
local rows = math.ceil(buttonCount / 2)
contents.CanvasSize = UDim2.new(0, 0, 0, rows * (contents.UIGridLayout.CellSize.Y.Offset + contents.UIGridLayout.CellPadding.Y.Offset) + 10)
end
return module
================================================
FILE: src/StarterGui/textInputPrompt.lua
================================================
local module = {}
local ui = script.Parent.gameUI.textInputPrompt
function module.init(Modules)
local network = Modules.network
local connection
local currentPrompt
function module.hide()
if connection then
connection:disconnect()
currentPrompt = nil
end
if ui.Visible then
Modules.focus.toggle(ui)
end
end
function module.prompt(info)
local prompt = info.prompt or "....."
if connection then
connection:disconnect()
end
currentPrompt = prompt
ui.Frame.code.TextBox.Text = ""
ui.Frame.code.TextBox.PlaceholderText = prompt
if not ui.Visible then
Modules.focus.toggle(ui)
end
local input
connection = ui.Frame.send.Activated:connect(function()
input = ui.Frame.code.TextBox.Text
end)
repeat wait() until input or currentPrompt ~= prompt
if input then
module.hide()
return input
end
end
network:create("textInputPrompt", "BindableFunction", "OnInvoke", module.prompt)
ui.Frame.close.Activated:connect(module.hide)
end
return module
================================================
FILE: src/StarterGui/trading.lua
================================================
-- local trading ui
-- written by berezaa & polymorphic
-- jesus christ if there is a competition for most cursed script it may very well be this one
local module = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local mapping = modules.load("mapping")
local configuration = modules.load("configuration")
local itemLookup = require(replicatedStorage.itemData)
local player = game.Players.LocalPlayer
local tradeFrame = script.Parent.gameUI.menu_trade
local inventorySlotPairing = {}
-- Called after all modules are required
function module.init(Modules)
local yourTrade = tradeFrame.yourTrade
local theirTrade = tradeFrame.theirTrade
local lastTradeSessionData = nil
local lastSelected
local myInventoryTransferDataCollection = nil
local function onGoldInputAmountChanged()
if yourTrade.amount.Visible then
if yourTrade.amount.TextBox.Text ~= "" then
local currentGold = network:invoke("getCacheValueByNameTag", "gold")
-- clamp to your current gold amount
yourTrade.amount.TextBox.Text = tostring(math.clamp(tonumber(yourTrade.amount.TextBox.Text) or 0, 0, tonumber(currentGold)))
yourTrade.add.ImageColor3 = Color3.new(0, 1, 0)
yourTrade.add.inner.Text = ">"
else
yourTrade.add.ImageColor3 = Color3.new(1, 0, 0)
yourTrade.add.inner.Text = "-"
end
end
end
local function onTradeSessionChanged(tradeSessionData)
end
local function onPlayerDragInventoryItemIntoTradeWindow(inventorySlotData)
if myInventoryTransferDataCollection then
end
end
local function tradeEnded()
end
-- returns my playerTradeData, other player's playerTradeData
local function getMyPlayerTradeData(tradeSessionData)
return tradeSessionData.playerTradeSessionData_player1.player == player and tradeSessionData.playerTradeSessionData_player1 or tradeSessionData.playerTradeSessionData_player2, tradeSessionData.playerTradeSessionData_player1.player == player and tradeSessionData.playerTradeSessionData_player2 or tradeSessionData.playerTradeSessionData_player1
end
-- Modules.trading.startTrade
function module.startTrade(tradeSessionData)
-- we don't know if our client is the trade starter (player1)
local playerTradeData, otherPlayerTradeData = getMyPlayerTradeData(tradeSessionData)
theirTrade.title.Text = otherPlayerTradeData.player.Name
yourTrade.title.Text = "Your offer"
-- show tradeFrame, and set lastTradeSessionData to keep track
tradeFrame.Visible = true
lastTradeSessionData = tradeSessionData
-- reset myInventoryTransferDataCollection
myInventoryTransferDataCollection = {}
-- hide equipment
Modules.equipment.hide()
tradeFrame.Visible = true
-- tradeFrame.Parent.Visible = true
if not tradeFrame.Parent.Parent.Visible then
Modules.playerMenu.open()
end
end
function module.endTrade(wasInitiatedByClient)
tradeFrame.Visible = false
Modules.equipment.show()
-- if the client is ending the trade, then let the server know we're not interested anymore
if wasInitiatedByClient and lastTradeSessionData then
network:invokeServer("playerRequest_cancelTrade", lastTradeSessionData.guid)
end
-- reset internals
myInventoryTransferDataCollection = nil
lastTradeSessionData = nil
end
module.tradeCollection = {}
function module.clearLocalTradeSlot(button)
inventorySlotPairing[button] = nil
module.tradeCollection[button.Name] = nil
end
function module.setLocalTradeSlot(i, inventorySlotData)
local inventoryReal = itemLookup[inventorySlotData.id]
-- check for existing conflicts
for e, tradeSlotData in pairs(module.tradeCollection) do
if tradeSlotData and tradeSlotData.position == inventorySlotData.position then
local tradeReal = itemLookup[tradeSlotData.id]
if inventoryReal and tradeReal then
if inventoryReal.category == tradeReal.category then
module.tradeCollection[tostring(e)] = nil
end
end
end
end
inventorySlotData.stacks = inventorySlotData.stacks or 1
module.tradeCollection[tostring(i)] = inventorySlotData
network:invokeServer("playerRequest_updatePlayerTradeSessionData", lastTradeSessionData.guid, "inventoryTransferDataCollection", module.tradeCollection)
end
function module.processDoubleClickFromInventory(inventorySlotData)
if inventorySlotData then
local inventoryReal = itemLookup[inventorySlotData.id]
if inventoryReal then
local takenButtons = {}
for i, tradeSlotData in pairs(module.tradeCollection) do
if tradeSlotData then
takenButtons[i] = true
if tradeSlotData.id == inventorySlotData.id and tradeSlotData.position == inventorySlotData.position then
-- item already in the trade, remove it.
module.tradeCollection[i] = nil
return network:invokeServer("playerRequest_updatePlayerTradeSessionData", lastTradeSessionData.guid, "inventoryTransferDataCollection", module.tradeCollection)
end
end
end
for i,button in pairs(tradeFrame.yourTrade.contents:GetChildren()) do
if button:IsA("GuiButton") and not takenButtons[button.Name] then
module.tradeCollection[button.Name] = inventorySlotData
return network:invokeServer("playerRequest_updatePlayerTradeSessionData", lastTradeSessionData.guid, "inventoryTransferDataCollection", module.tradeCollection)
end
end
end
end
end
function module.swap(button1, button2)
local button1pairing = inventorySlotPairing[button1]
local button2pairing = inventorySlotPairing[button2]
module.tradeCollection[button1.Name] = button2pairing
module.tradeCollection[button2.Name] = button1pairing
network:invokeServer("playerRequest_updatePlayerTradeSessionData", lastTradeSessionData.guid, "inventoryTransferDataCollection", module.tradeCollection)
end
local function onTradeSessionChanged(tradeSessionData)
-- if lastTradeSessionData == nil or lastTradeSessionData.guid ~= tradeSessionData.guid then
if lastTradeSessionData ~= tradeSessionData then
module.startTrade(tradeSessionData)
end
-- wooo!!!
-- todo: make this do something pretty
if tradeSessionData.state == "canceled" or tradeSessionData.state == "completed" then
module.tradeCollection = {}
module.endTrade()
end
tradeFrame.countdown.Visible = false
if tradeSessionData.state == "countdown" then
tradeFrame.countdown.Text = "6"
tradeFrame.countdown.Visible = true
spawn(function()
local count = 6
while lastTradeSessionData.state == "countdown" and tradeFrame.countdown.Text == tostring(count) and count > 0 do
count = count - 1
tradeFrame.countdown.Text = tostring(count)
wait(1)
end
if count <= 0 then
tradeFrame.countdown.Visible = false
end
end)
end
local yourPlayerTradeSessionData = (tradeSessionData.playerTradeSessionData_player1.player == player and tradeSessionData.playerTradeSessionData_player1) or (tradeSessionData.playerTradeSessionData_player2.player == player and tradeSessionData.playerTradeSessionData_player2) or {}
local theirPlayerTradeSessionData = (tradeSessionData.playerTradeSessionData_player1.player == player and tradeSessionData.playerTradeSessionData_player2) or (tradeSessionData.playerTradeSessionData_player2.player == player and tradeSessionData.playerTradeSessionData_player1) or {}
-- gold
-- tradeFrame.yourTrade.gold.Text = yourPlayerTradeSessionData.gold or 0
-- tradeFrame.theirTrade.gold.Text = theirPlayerTradeSessionData.gold or 0
Modules.money.setLabelAmount(tradeFrame.yourTrade.gold, yourPlayerTradeSessionData.gold or 0)
Modules.money.setLabelAmount(tradeFrame.theirTrade.gold, theirPlayerTradeSessionData.gold or 0)
-- trade state
tradeFrame.yourTrade.approved.Visible = false
tradeFrame.yourTrade.denied.Visible = false
tradeFrame.theirTrade.approved.Visible = false
tradeFrame.theirTrade.denied.Visible = false
tradeFrame.yourTrade.ImageColor3 = Color3.fromRGB(189, 189, 189)
tradeFrame.theirTrade.ImageColor3 = Color3.fromRGB(139, 139, 139)
if theirPlayerTradeSessionData.state == "approved" then
tradeFrame.theirTrade.approved.Visible = true
tradeFrame.theirTrade.ImageColor3 = Color3.fromRGB(136, 192, 132)
elseif theirPlayerTradeSessionData.state == "denied" then
tradeFrame.theirTrade.denied.Visible = true
tradeFrame.theirTrade.ImageColor3 = Color3.fromRGB(188, 109, 111)
end
if yourPlayerTradeSessionData.state == "approved" then
tradeFrame.yourTrade.approved.Visible = true
tradeFrame.yourTrade.ImageColor3 = Color3.fromRGB(97, 141, 98)
elseif yourPlayerTradeSessionData.state == "denied" then
tradeFrame.yourTrade.denied.Visible = true
tradeFrame.yourTrade.ImageColor3 = Color3.fromRGB(138, 87, 88)
end
local yourCollection = yourPlayerTradeSessionData.inventoryTransferDataCollection or {}
local theirCollection = theirPlayerTradeSessionData.inventoryTransferDataCollection or {}
module.tradeCollection = yourCollection
local yourButtons = tradeFrame.yourTrade.contents:GetChildren()
for i, button in pairs(yourButtons) do
if button:IsA("ImageButton") then
button.Image = ""
inventorySlotPairing[button] = nil
button.duplicateCount.Visible = false
local buttonItemData = yourCollection[button.Name] or {}
if buttonItemData then
local itemBaseData = itemLookup[buttonItemData.id]
if itemBaseData then
button.Image = itemBaseData.image
button.ImageColor3 = Color3.new(1,1,1)
if buttonItemData.dye then
button.ImageColor3 = Color3.fromRGB(buttonItemData.dye.r, buttonItemData.dye.g, buttonItemData.dye.b)
end
inventorySlotPairing[button] = buttonItemData
end
if buttonItemData.stacks and buttonItemData.stacks > 1 then
button.duplicateCount.Text = tostring(buttonItemData.stacks)
button.duplicateCount.Visible = true
end
end
end
end
local theirButtons = tradeFrame.theirTrade.contents:GetChildren()
for i, button in pairs(theirButtons) do
if button:IsA("ImageButton") then
button.Image = ""
inventorySlotPairing[button] = nil
button.duplicateCount.Visible = false
local buttonItemData = theirCollection[button.Name] or {}
if buttonItemData then
local itemBaseData = itemLookup[buttonItemData.id]
if itemBaseData then
button.Image = itemBaseData.image
button.ImageColor3 = Color3.new(1,1,1)
if buttonItemData.dye then
button.ImageColor3 = Color3.fromRGB(buttonItemData.dye.r, buttonItemData.dye.g, buttonItemData.dye.b)
end
inventorySlotPairing[button] = buttonItemData
end
if buttonItemData.stacks and buttonItemData.stacks > 1 then
button.duplicateCount.Text = tostring(buttonItemData.stacks)
button.duplicateCount.Visible = true
end
end
end
end
end
local function setYourGoldAmount(amount)
-- yourTrade.gold.Text = "$"..tostring(amount)
Modules.money.setLabelAmount(tradeFrame.yourTrade.gold, tonumber(amount))
-- >>> network: communicate trade change to server
network:invokeServer("playerRequest_updatePlayerTradeSessionData", lastTradeSessionData.guid, "gold", tonumber(amount))
end
local function onMouseButton1Click_addMoneyButton()
if yourTrade.amount.Visible then
-- change the amount of money you are giving
if yourTrade.amount.TextBox.Text ~= "" then
local currentGold = network:invoke("getCacheValueByNameTag", "gold")
-- clamp to your current gold amount
local amount = tostring(math.clamp(tonumber(yourTrade.amount.TextBox.Text) or 0, 0, tonumber(currentGold)))
if amount then
setYourGoldAmount(amount)
end
end
yourTrade.amount.Visible = false
yourTrade.add.ImageColor3 = Color3.new(1,1,0)
yourTrade.add.inner.Text = "+"
else
-- open the change money input
yourTrade.amount.TextBox.Text = ""
yourTrade.amount.Visible = true
yourTrade.add.ImageColor3 = Color3.new(1,0,0)
yourTrade.add.inner.Text = "-"
end
end
local function onPlayerRequest_requestOpenTradeWithPlayerReceived(playerTradeInitiator, tradeSessionId)
local success = network:invoke("promptAction", playerTradeInitiator.Name .. " wants to trade! Would you like to trade with them?")
if success then
network:invokeServer("playerRequest_acceptTradeRequest", tradeSessionId)
end
end
tradeFrame.buttons.leave.Activated:connect(function()
module.endTrade(true)
end)
tradeFrame.buttons.accept.Activated:connect(function()
network:invokeServer("playerRequest_updatePlayerTradeSessionData", lastTradeSessionData.guid, "state", "approved")
end)
tradeFrame.buttons.deny.Activated:connect(function()
network:invokeServer("playerRequest_updatePlayerTradeSessionData", lastTradeSessionData.guid, "state", "denied")
end)
yourTrade.add.MouseButton1Click:connect(onMouseButton1Click_addMoneyButton)
yourTrade.amount.TextBox:GetPropertyChangedSignal("Text"):connect(onGoldInputAmountChanged)
network:connect("signal_playerTradeRequest", "OnClientEvent", onPlayerRequest_requestOpenTradeWithPlayerReceived)
network:connect("signal_tradeSessionChanged", "OnClientEvent", onTradeSessionChanged)
--postInit
spawn(function()
local network = Modules.network
local function onInventoryItemMouseEnter(inventoryItem)
lastSelected = inventoryItem
local inventorySlotData = inventorySlotPairing[inventoryItem]
if inventorySlotData then
local itemBaseData = itemLookup[inventorySlotData.id]
if itemBaseData then
network:invoke("populateItemHoverFrame", itemBaseData, "inventory", inventorySlotData)
end
end
end
local function onInventoryItemMouseLeave(inventoryItem)
if lastSelected == inventoryItem then
-- clears last selected
network:invoke("populateItemHoverFrame")
end
end
for i,guiObject in pairs(tradeFrame:GetDescendants()) do
if guiObject:IsA("GuiObject") and guiObject:FindFirstChild("draggableFrame") then
guiObject.MouseEnter:connect(function() onInventoryItemMouseEnter(guiObject) end)
guiObject.MouseLeave:connect(function() onInventoryItemMouseLeave(guiObject) end)
guiObject.SelectionGained:connect(function() onInventoryItemMouseEnter(guiObject) end)
guiObject.SelectionLost:connect(function() onInventoryItemMouseLeave(guiObject) end)
end
end
end)
end
return module
================================================
FILE: src/StarterGui/uiCreator.lua
================================================
-- uiCreator controls most dyanmic ui elements (text labels, item notifcations, etc.) as well as button draggingb between menus
local module = {}
module.drag = {}
-- service declarations
local textService = game:GetService("TextService")
local tweenService = game:GetService("TweenService")
local userInputService = game:GetService("UserInputService")
local replicatedStorage = game:GetService("ReplicatedStorage")
-- module requirements
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local mapping = modules.load("mapping")
local tween = modules.load("tween")
local localization = modules.load("localization")
local itemData = require(replicatedStorage:WaitForChild("itemData"))
local itemAttributes = require(replicatedStorage:WaitForChild("itemAttributes"))
local BASE_TWEEN_INFO = TweenInfo.new(0.3, Enum.EasingStyle.Quad, Enum.EasingDirection.Out, 0, false, 0)
local interactionPromptCache = {}
local player = game.Players.LocalPlayer
local playerGui = player.PlayerGui
local dragDropFrameCollection = {}
local currentDragFrameOriginator = nil
-- ui references
local ui = playerGui.gameUI
local menu_inventory = ui.menu_inventory
local menu_trade = ui.menu_trade
local menu_enchant = ui.menu_enchant
local menu_shop = ui.menu_shop
local menu_storage = ui.menu_storage
local menu_equipment = ui.menu_equipment
local menu_abilities = ui.menu_abilities
local interactionPromptsFrame = ui.interactionPrompts
local effects = script.Parent.effects
local dragDropMask = ui.dragDropMask
network:create("setGameUIEnabled", "BindableEvent", "Event", function(enabled)
ui.Enabled = enabled
end)
local Modules
function module.init(mods)
Modules = mods
local function inputUpdate()
if Modules.input.mode.Value == "mobile" then
ui.interactionPrompts.Position = UDim2.new(1,-110,1,-200)
ui.interactionPrompts.UIScale.Scale = Modules.input.menuScale or 1
else
ui.interactionPrompts.Position = UDim2.new(1,-110,1,-130)
ui.interactionPrompts.UIScale.Scale = 1
end
end
inputUpdate()
Modules.input.mode.Changed:connect(inputUpdate)
end
local IS_PROCESSING_INVENTORY_SLOT_SWITCH = false
local lastMoneyUpdateTime
function module.showCurrency(amount)
if not Modules then
return false
end
utilities.playSound("coins")
local template = effects.moneyObtained:Clone()
local count = template:FindFirstChild("count")
if count then
template.backdrop.UIScale.Scale = 1.15 + math.clamp(count.Value/150,0,0.75)
tween(template.backdrop.UIScale, {"Scale"}, 1, 0.5)
else
template.Size = UDim2.new(0,0,0,42)
count = Instance.new("IntValue")
count.Name = "count"
count.Value = 0
count.Parent = template
end
-- display coin effect
local iconImage = "rbxassetid://2535600080"
local iconAmount = 1
if amount >= 1e6 then
iconImage = "rbxassetid://2536432897"
if amount >= 5e8 then
iconAmount = 4
elseif amount >= 1e8 then
iconAmount = 3
elseif amount >= 1e7 then
iconAmount = 2
end
elseif amount >= 1e3 then
-- silver
iconImage = "rbxassetid://2535600034"
if amount >= 5e5 then
iconAmount = 4
elseif amount >= 1e5 then
iconAmount = 3
elseif amount >= 1e4 then
iconAmount = 2
end
else
-- bronze
iconImage = "rbxassetid://2535600080"
if amount >= 5e2 then
iconAmount = 4
elseif amount >= 1e2 then
iconAmount = 3
elseif amount >= 1e1 then
iconAmount = 2
end
end
for _ = 1, iconAmount do
local coin = effects.coin:Clone()
local finalPosition = UDim2.new(0.5, math.random(-100,100), 0.5, math.random(-50,50))
coin.Parent = template
coin.Visible = true
coin.Image = iconImage
coin.ImageTransparency = 1
coin.Size = UDim2.new(0,20,0,20)
tween(coin, {"Position", "ImageTransparency", "Size"}, {finalPosition, 0, UDim2.new(0,32,0,32)}, 1, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
delay(1, function()
if coin and coin.Parent then
tween(coin, {"ImageTransparency"}, {1}, 0.5)
game.Debris:AddItem(coin, 0.5)
end
end)
end
count.Value = count.Value + 1
local moneyUpdateTime = tick()
lastMoneyUpdateTime = moneyUpdateTime
template.amount.Value = template.amount.Value + amount
local totalAmount = template.amount.Value
Modules.money.setLabelAmount(template.backdrop.money, totalAmount)
local xSize = template.backdrop.money.amount.AbsoluteSize.X + 32 + 16
template.backdrop.money.Size = UDim2.new(0, xSize, template.backdrop.money.Size.Y.Scale, template.backdrop.money.Size.Y.Offset)
template.Visible = true
template.Parent = interactionPromptsFrame
local duration = 5
local goalSize = UDim2.new(0, xSize + 35, 0, 42)
tween(template,{"Size"},goalSize,0.5)
spawn(function()
wait(duration)
-- if lastMoneyUpdateTime == moneyUpdateTime then
tween(template,{"Size"},UDim2.new(0,0,0,42),0.5)
wait(0.5)
if lastMoneyUpdateTime == moneyUpdateTime then
template:Destroy()
end
-- end
end)
end
function module.showLootUnlock(monsterViewport, realItem, tabColor, flareColor)
flareColor = flareColor or tabColor
local template = effects.monsterBook:Clone()
template.backdrop.contents.thumbnail.Image = realItem.image
template.backdrop.contents.holder:ClearAllChildren()
monsterViewport:Clone().Parent = template.backdrop.contents.holder
template.backdrop.ImageColor3 = tabColor
template.flare.ImageColor3 = flareColor
local goalSize = template.Size
template.Visible = true
template.Parent = interactionPromptsFrame
local indicator = template:WaitForChild("flare"):clone()
indicator.Parent = template
indicator.Size = UDim2.new(1,6,1,0)
indicator.Position = UDim2.new(1,0,0.5,0)
indicator.Visible = true
for i=1,4 do
local flare = template:WaitForChild("flare"):clone()
flare.Parent = template
flare.Visible = true
flare.Size = UDim2.new(1,4,1,4)
flare.Position = UDim2.new(0.5,0,0.5,0)
flare.AnchorPoint = Vector2.new(0.5,0.5)
local x = (260 - 40*i)
local y = (14 - 2*i)
local EndSize = UDim2.new(1,x,1,y)
tween(flare,{"Size","ImageTransparency"},{EndSize, 1},0.7*i)
end
tween(template,{"Size"},goalSize,0.5)
spawn(function()
wait(10)
tween(template,{"Size"},UDim2.new(0,0,0,60),0.5)
wait(0.5)
template:Destroy()
end)
end
function module.showItemPickup(realItem, amount, metadata)
amount = metadata.stacks or amount or 1
local itemname = realItem.name .. ((metadata and metadata.upgrades and metadata.upgrades > 0 and " +"..(metadata.successfulUpgrades or 0)) or "") or "Unknown"
metadata = metadata or {}
metadata.id = metadata.id or realItem.id
local attributeColor
if metadata.attribute then
local attribute = itemAttributes[metadata.attribute]
if attribute then
attributeColor = attribute.color
if attribute.prefix then
itemname = attribute.prefix .. " " .. itemname
end
end
end
local template = effects.itemObtained:Clone()
template.Size = UDim2.new(0,0,0,60)
template.Name = itemname
template.backdrop.contents.item.attribute.Visible = false
if attributeColor then
template.backdrop.contents.item.attribute.ImageColor3 = attributeColor
template.backdrop.contents.item.attribute.Visible = true
end
template.backdrop.contents.title.Text = itemname
-- template.backdrop.contents.thumbnail.Image = realItem.image
template.backdrop.contents.item.thumbnail.Image = realItem.image
template.amount.Value = template.amount.Value + amount
local currentAmount = template.amount.Value
template.backdrop.contents.item.thumbnail.duplicateCount.Text = currentAmount
template.backdrop.contents.item.thumbnail.duplicateCount.Visible = currentAmount > 1
local titleColor, itemTier
if itemData then
titleColor, itemTier = Modules.itemAcquistion.getTitleColorForInventorySlotData(metadata)
end
template.backdrop.contents.item.shine.Visible = titleColor ~= nil and itemTier and itemTier > 1
template.backdrop.contents.item.shine.ImageColor3 = titleColor or Color3.fromRGB(179, 178, 185)
template.backdrop.contents.item.frame.ImageColor3 = (itemTier and itemTier > 1 and titleColor) or Color3.fromRGB(106, 105, 107)
template.backdrop.contents.item.shine.ImageColor3 = titleColor or Color3.fromRGB(179, 178, 185)
template.backdrop.contents.title.TextColor3 = titleColor or Color3.new(1,1,1)
template.backdrop.contents.item.thumbnail.ImageColor3 = Color3.new(1,1,1)
local dye = metadata and metadata.dye
if dye then
template.backdrop.contents.item.thumbnail.ImageColor3 = Color3.fromRGB(dye.r, dye.g, dye.b)
end
template.Visible = true
template.Parent = interactionPromptsFrame
Modules.fx.setFlash(template.backdrop.contents.item.frame, template.backdrop.contents.item.shine.Visible)
local extents = game.TextService:GetTextSize(template.backdrop.contents.title.Text,18,Enum.Font.SourceSansBold,Vector2.new(90,36))
local goalSize = UDim2.new(0,125+extents.X,0,60)
template.backdrop.contents.title.Size = UDim2.new(0,extents.X+40,1,0)
local indicator
local duration = 2
if (realItem.rarity and realItem.rarity == "Legendary") then
duration = duration + 2.5
indicator = template:WaitForChild("flare"):clone()
indicator.Parent = template
indicator.Size = UDim2.new(1,8,1,0)
indicator.AnchorPoint = Vector2.new(0.5,0.5)
indicator.Position = UDim2.new(0.5,0,0.5,0)
indicator.ImageColor3 = Color3.fromRGB(174, 34, 234)
indicator.Visible = true
for i=1,6 do
local flare = template:WaitForChild("flare"):clone()
flare.Parent = template
flare.Visible = true
flare.ImageColor3 = Color3.fromRGB(174, 34, 234)
flare.Size = UDim2.new(1,4,1,4)
flare.Position = UDim2.new(0.5,0,0.5,0)
flare.AnchorPoint = Vector2.new(0.5,0.5)
local x = (1000 - 53*i)
local y = (28 - 3*i)
local EndPosition = UDim2.new(1,y/2,0.5,0)
local EndSize = UDim2.new(1,x,1,y)
tween(flare,{"Size","ImageTransparency"},{EndSize, 1},0.7*i)
end
elseif (realItem.rarity and realItem.rarity == "Rare") or (realItem.category and realItem.category == "equipment") then
duration = duration + 1.5
indicator = template:WaitForChild("flare"):clone()
indicator.Parent = template
indicator.Size = UDim2.new(1,8,1,0)
indicator.AnchorPoint = Vector2.new(0.5,0.5)
indicator.Position = UDim2.new(0.5,0,0.5,0)
indicator.Visible = true
for i=1,4 do
local flare = template:WaitForChild("flare"):clone()
flare.Parent = template
flare.Visible = true
flare.Size = UDim2.new(1,4,1,4)
flare.Position = UDim2.new(0.5,0,0.5,0)
flare.AnchorPoint = Vector2.new(0.5,0.5)
local x = (500 - 40*i)
local y = (14 - 2*i)
local EndSize = UDim2.new(1,x,1,y)
tween(flare,{"Size","ImageTransparency"},{EndSize, 1},0.7*i)
end
end
tween(template,{"Size"},goalSize,0.5)
spawn(function()
wait(duration)
if indicator then
tween(indicator, {"ImageTransparency"}, 1, 0.5)
end
if template.Parent and currentAmount == template.amount.Value then
tween(template,{"Size"},UDim2.new(0,0,0,60),0.5)
if template.Parent and currentAmount == template.amount.Value then
wait(0.5)
template:Destroy()
end
end
end)
end
local interactionPromptTextLabelTemplate = effects:WaitForChild("interactionPromptTextLabel")
function module.createTextFragmentLabels(parent, textFragments)
local textOffsetX = 0
local textOffsetY = 0
local originalTextSize
local container = Instance.new("Frame")
local textYSize = 0
local originalTextFragmentSize
local lines = 1
for i, textFragmentData in ipairs(textFragments) do
local textColor = textFragmentData.textColor3 or Color3.fromRGB(15,15,15)
local font = textFragmentData.font or Enum.Font.SourceSans
-- local autoLocalize = (textFragmentData.autoLocalize == nil and true) or textFragmentData.autoLocalize
local autoLocalize = false
-- automatically pull from localization module
if textFragmentData.autoLocalize == nil or textFragmentData.autoLocalize then
textFragmentData.text = localization.translate(textFragmentData.text, parent)
end
local textTransparency = textFragmentData.textTransparency or 0
local textSize = textFragmentData.textSize or 18
local textFragmentSize = textService:GetTextSize(textFragmentData.text, textSize, font, Vector2.new())
-- standardize Y size so you can have big-text effects without offsetting the text /ber
local standardTextYSize
if originalTextFragmentSize then
textFragmentSize = Vector2.new(textFragmentSize.X, originalTextFragmentSize.Y)
else
originalTextFragmentSize = textFragmentSize
end
standardTextYSize = textSize
-- multi-line support
if textOffsetX + textFragmentSize.X + 3 > parent.AbsoluteSize.X then
-- split the fragment's text into smaller pieces by word
local fragmentFragments = {}
for word in string.gmatch(textFragmentData.text, "%S+") do
table.insert(fragmentFragments, word)
end
local currentFragmentQueue = {}
local currentFragmentQueueXSize = 0
for i, word in pairs(fragmentFragments) do
local wordSize = textService:GetTextSize(word, textSize, font, Vector2.new())
if textOffsetX + currentFragmentQueueXSize + wordSize.X + 3 > parent.AbsoluteSize.X then
-- exceeded line!
-- dump the queue into a text label, then push this word into next queue
lines = lines + 1
if #currentFragmentQueue > 0 then
local putTogetherFragment = ""
for ii, wordFragment in pairs(currentFragmentQueue) do
putTogetherFragment = putTogetherFragment .. wordFragment
if ii ~= #currentFragmentQueue then
putTogetherFragment = putTogetherFragment .. " "
end
end
local textFragmentTextLabel = interactionPromptTextLabelTemplate:Clone()
textFragmentTextLabel.AutoLocalize = autoLocalize
textFragmentTextLabel.TextSize = textSize
textFragmentTextLabel.TextColor3 = textColor
textFragmentTextLabel.Position = UDim2.new(0, textOffsetX, 0, textOffsetY)
textFragmentTextLabel.Size = UDim2.new(0, currentFragmentQueueXSize, 0, standardTextYSize )
textFragmentTextLabel.Text = putTogetherFragment
textFragmentTextLabel.Font = font
textFragmentTextLabel.Parent = container
textFragmentTextLabel.TextTransparency = textTransparency
end
textOffsetY = textOffsetY + standardTextYSize
textOffsetX = 0
currentFragmentQueue = {}
currentFragmentQueueXSize = wordSize.X
table.insert(currentFragmentQueue, word)
else
currentFragmentQueueXSize = currentFragmentQueueXSize + wordSize.X + 3
table.insert(currentFragmentQueue, word)
end
end
if #currentFragmentQueue > 0 then
local putTogetherFragment = ""
for ii, wordFragment in pairs(currentFragmentQueue) do
putTogetherFragment = putTogetherFragment .. wordFragment
if ii ~= #currentFragmentQueue then
putTogetherFragment = putTogetherFragment .. " "
end
end
local textFragmentTextLabel = interactionPromptTextLabelTemplate:Clone()
textFragmentTextLabel.TextSize = textSize
textFragmentTextLabel.TextColor3 = textColor
textFragmentTextLabel.Position = UDim2.new(0, textOffsetX, 0, textOffsetY)
textFragmentTextLabel.Size = UDim2.new(0, currentFragmentQueueXSize, 0, standardTextYSize)
textFragmentTextLabel.Text = putTogetherFragment
textFragmentTextLabel.Font = font
textFragmentTextLabel.Parent = container
textFragmentTextLabel.TextTransparency = textTransparency
textOffsetX = textOffsetX + currentFragmentQueueXSize + 3
end
if textYSize <= 0 then
textYSize = standardTextYSize
end
else
if textYSize <= 0 then
textYSize = standardTextYSize
end
local textFragmentTextLabel = interactionPromptTextLabelTemplate:Clone()
textFragmentTextLabel.TextSize = textSize
textFragmentTextLabel.TextColor3 = textColor
-- ugly hack for item tooltip stats im sorry
textFragmentTextLabel.Position = UDim2.new(0, textOffsetX, textYSize ~= standardTextYSize and 0.5 or 0, textOffsetY)
textFragmentTextLabel.AnchorPoint = Vector2.new(0,textYSize ~= standardTextYSize and 0.5 or 0)
textFragmentTextLabel.Size = UDim2.new(0, textFragmentSize.X, 0, standardTextYSize )
textFragmentTextLabel.Text = textFragmentData.text
textFragmentTextLabel.Font = font
textFragmentTextLabel.Parent = container
textFragmentTextLabel.TextTransparency = textTransparency
if #textFragments > 1 then
textOffsetX = textOffsetX + textFragmentSize.X + 3
else
textOffsetX = textOffsetX + textFragmentSize.X
end
end
end
container.Size = UDim2.new(1, 0, 0, textYSize * lines)
container.BackgroundTransparency = 1
container.Parent = parent
return container, textOffsetY, textOffsetX
end
network:create("createTextFragmentLabels", "BindableFunction", "OnInvoke", module.createTextFragmentLabels)
local function buildInteractionPromptText(promptInteractionInterface, textFragments, eventsData, eventSignal, noAnimation)
local textOffsetX = 0
local textOffsetY = 0
local interactionPromptCopy = promptInteractionInterface.manifest
-- clear previous ui in here
for i, v in pairs(interactionPromptCopy.curve.contents:GetChildren()) do
if not v:isA("UIPadding") then
v:Destroy()
end
end
if interactionPromptCopy["pick up"].Visible then
textOffsetX = 10
interactionPromptCopy.curve.Size = UDim2.new(1,-25,0,28)
interactionPromptCopy.curve.Position = UDim2.new(0.5,25,0.5,0)
interactionPromptCopy.LayoutOrder = 10
else
interactionPromptCopy.curve.Size = UDim2.new(1,0,0,28)
interactionPromptCopy.curve.Position = UDim2.new(0.5,0,0.5,0)
end
for i, textFragmentData in pairs(textFragments) do
local textFragmentSize = textService:GetTextSize(textFragmentData.text, interactionPromptTextLabelTemplate.TextSize, interactionPromptTextLabelTemplate.Font, Vector2.new())
local textColor = textFragmentData.textColor3 or Color3.fromRGB(170, 170, 170)
local text = textFragmentData.text or ""
if not textFragmentData.eventType or (textFragmentData.eventType == "key" and textFragmentData.id) then
local textFragmentTextLabel = interactionPromptTextLabelTemplate:Clone()
textFragmentTextLabel.TextColor3 = textColor
textFragmentTextLabel.Position = UDim2.new(0, textOffsetX, 0, textOffsetY)
textFragmentTextLabel.Size = UDim2.new(0, textFragmentSize.X, 0, textFragmentSize.Y)
textFragmentTextLabel.Text = text
textFragmentTextLabel.Parent = interactionPromptCopy.curve.contents
if textFragmentData.eventType == "key" and textFragmentData.id and textFragmentData.keyCode and not eventsData[textFragmentData.id] then
eventsData[textFragmentData.id] = userInputService.InputBegan:connect(function(inputObject)
if inputObject.UserInputType == Enum.UserInputType.Keyboard and inputObject.KeyCode == textFragmentData.keyCode then
if eventSignal then
eventSignal:Fire(textFragmentData.id)
end
end
end)
end
if #textFragments > 1 then
textOffsetX = textOffsetX + textFragmentSize.X + 3
else
textOffsetX = textOffsetX + textFragmentSize.X
end
end
end
if not noAnimation or promptInteractionInterface.isHiding then
promptInteractionInterface.isHiding = false
local y = 18 + 10
if interactionPromptCopy["pick up"].Visible then
textOffsetX = textOffsetX + 40
interactionPromptCopy.LayoutOrder = 10
y = 40
end
if noAnimation then
interactionPromptCopy.Size = UDim2.new(0, textOffsetX + 15, 0, y)
else
local y = 18 + 10
if interactionPromptCopy["pick up"].Visible then
interactionPromptCopy.Parent = interactionPromptsFrame
interactionPromptCopy.LayoutOrder = 10
y = 40
else
interactionPromptCopy.Parent = interactionPromptsFrame
end
local openAnimation = tweenService:Create(interactionPromptCopy, BASE_TWEEN_INFO, {Size = UDim2.new(0, textOffsetX + 15, 0, y)})
openAnimation:Play()
end
end
end
local interactionPromptTemplate = effects:WaitForChild("interactionPrompt")
function module.createInteractionPrompt(properties, ...)
properties = properties or {}
local promptId = properties.itemName or properties.promptId
local value = properties.value
--" x"..tostring(value) or ""
local textFragments = {...}
local textDisplayedTime = tick()
local interactionPromptCopy, eventsData, eventSignal, doShowNoAnimation
if promptId and interactionPromptCache[promptId] then
interactionPromptCopy = interactionPromptCache[promptId].interactionPromptCopy
eventsData = interactionPromptCache[promptId].eventsData
eventSignal = interactionPromptCache[promptId].eventSignal
interactionPromptCache[promptId].textDisplayedTime = textDisplayedTime
if properties.itemName and value then
local existingValue = interactionPromptCache[promptId].value or 0
value = value + existingValue
interactionPromptCache[promptId].value = value
interactionPromptCopy.curve.UIScale.Scale = 1.15 + math.clamp(value/150,0,0.75)
tween(interactionPromptCopy.curve.UIScale, {"Scale"}, 1, 0.5)
end
else
interactionPromptCopy = interactionPromptTemplate:Clone()
eventsData = {}
eventSignal = Instance.new("BindableEvent")
doShowNoAnimation = false
if promptId and not interactionPromptCache[promptId] then
interactionPromptCache[promptId] = {}
interactionPromptCache[promptId].interactionPromptCopy = interactionPromptCopy
interactionPromptCache[promptId].eventsData = eventsData
interactionPromptCache[promptId].eventSignal = eventSignal
interactionPromptCache[promptId].value = value
interactionPromptCache[promptId].textDisplayedTime = textDisplayedTime
end
end
if value and value ~= 1 then
table.insert(textFragments,{text = "x"..tostring(value); textColor3 = Color3.fromRGB(120,120,120)})
end
local promptInteractionInterface = {} do
promptInteractionInterface.manifest = interactionPromptCopy
promptInteractionInterface.eventSignal = eventSignal
promptInteractionInterface.isHiding = false
local function __intCleanup()
if promptId == nil or interactionPromptCache[promptId].textDisplayedTime == textDisplayedTime then
-- wipe connections
if eventsData then
for i, v in pairs(eventsData) do
v:disconnect()
end
eventsData = nil
end
if eventSignal then
eventSignal:Destroy()
eventSignal = nil
end
-- delete the actual ui
if interactionPromptCopy then
interactionPromptCopy:Destroy()
interactionPromptCopy = nil
end
if promptId then
interactionPromptCache[promptId] = nil
end
end
end
local y = 18 + 10
interactionPromptCopy["pick up"].Visible = false
interactionPromptCopy.mobilePrompt.Visible = false
if properties.promptId then
y = 40
interactionPromptCopy.LayoutOrder = 10
interactionPromptCopy["pick up"].Visible = true
interactionPromptCopy.mobilePrompt.Visible = true
end
function promptInteractionInterface:close(noAnimation)
if promptId == nil or interactionPromptCache[promptId].textDisplayedTime == textDisplayedTime then
if not noAnimation and interactionPromptCopy then
local closeAnimation = tweenService:Create(interactionPromptCopy, BASE_TWEEN_INFO, {Size = UDim2.new(0, 0, 0, y)})
closeAnimation.Completed:connect(function()
__intCleanup()
end)
closeAnimation:Play()
elseif noAnimation then
__intCleanup()
end
end
end
function promptInteractionInterface:hide(noAnimation)
promptInteractionInterface.isHiding = true
local closeAnimation = tweenService:Create(interactionPromptCopy, BASE_TWEEN_INFO, {Size = UDim2.new(0, 0, 0, y)})
closeAnimation:Play()
end
function promptInteractionInterface:setExpireTime(timeToExpire, hideInstead, noAnimation)
delay(timeToExpire, function()
if promptId == nil or interactionPromptCache[promptId].textDisplayedTime == textDisplayedTime then
if not hideInstead then
promptInteractionInterface:close(noAnimation)
else
promptInteractionInterface:hide(noAnimation)
end
end
end)
end
function promptInteractionInterface:setBackgroundColor3(backgroundColor3)
if interactionPromptCopy then
interactionPromptCopy.curve.ImageColor3 = backgroundColor3
end
end
function promptInteractionInterface:updateTextFragments(doShowNoAnimation, ...)
if interactionPromptCopy then
buildInteractionPromptText(promptInteractionInterface, {...}, eventsData, eventSignal, doShowNoAnimation)
end
end
end
-- eventTypes -- 'key', 'click'
-- todo: implement multiple lines
buildInteractionPromptText(promptInteractionInterface, textFragments, eventsData, eventSignal, doShowNoAnimation)
return promptInteractionInterface
end
function module.showEtcItemPickup(realItem, value, metadata)
local prompt = module.createInteractionPrompt({itemName = realItem.name; value = metadata.stacks or 1;},
{text = "Obtained"; textColor3 = Color3.fromRGB(120,120,120)},
{text = realItem.name; textColor3 = Color3.fromRGB(143, 120, 255)}
)
prompt:setBackgroundColor3(Color3.fromRGB(190, 190, 190))
prompt:setExpireTime(4)
end
local function isPositionInsideFrame(absPosition, frame)
local relative = (frame.AbsolutePosition - absPosition) / frame.AbsoluteSize
return
relative.X >= -0.55 and relative.X <= 0.55
and relative.Y >= -0.55 and relative.Y <= 0.55
end
local function processSwap(buttonFrom, buttonTo, isRightClickTrigger, extraData)
if IS_PROCESSING_INVENTORY_SLOT_SWITCH then return false end
if not buttonFrom then return false end
if (buttonTo == buttonFrom and not (extraData and extraData.originSlotData)) then return false end
-- if buttonFrom.Image == "" then return false end
IS_PROCESSING_INVENTORY_SLOT_SWITCH = true
if buttonFrom:IsDescendantOf(menu_trade.yourTrade) then
if buttonTo:IsDescendantOf(menu_trade.yourTrade) then
Modules.trading.swap(buttonFrom, buttonTo)
else
Modules.trading.clearLocalTradeSlot(buttonFrom)
end
elseif buttonFrom:IsDescendantOf(menu_storage) then
if buttonTo and buttonTo:IsDescendantOf(menu_inventory) then
local buttonFromStorageSlotData = network:invoke("getStorageSlotDataFromStorageItem", buttonFrom)
if buttonFromStorageSlotData then
local success, reason = network:invokeServer("playerRequest_transferStorageToInventory", buttonFromStorageSlotData)
end
end
elseif buttonFrom:IsDescendantOf(menu_inventory) then
if buttonTo then
if buttonTo:IsDescendantOf(menu_enchant) then
local buttonFromInventorySlot, buttonFromType = network:invoke("getInventorySlotDataByInventorySlotUI", buttonFrom)
if buttonFromInventorySlot and buttonFromType == "ability" then
Modules.enchant.dragItem(buttonFromInventorySlot)
end
elseif buttonTo:IsDescendantOf(menu_storage) then
local buttonFromInventorySlot, buttonFromType = network:invoke("getInventorySlotDataByInventorySlotUI", buttonFrom)
if buttonFromInventorySlot and buttonFromType == "item" then
local success, reason = network:invokeServer("playerRequest_transferInventoryToStorage", buttonFromInventorySlot)
end
elseif buttonTo:IsDescendantOf(menu_inventory) then
local buttonFromInventorySlot, buttonFromType = network:invoke("getInventorySlotDataByInventorySlotUI", buttonFrom)
local buttonToInventorySlot, buttonToType = network:invoke("getInventorySlotDataByInventorySlotUI", buttonTo)
if buttonFromInventorySlot and buttonFromType == "item" then
local fromBaseItemData = itemData[buttonFromInventorySlot.id]
local toBaseItemData = buttonToInventorySlot and itemData[buttonToInventorySlot.id] or nil
if fromBaseItemData then
if toBaseItemData then
if fromBaseItemData.category ~= "equipment" and toBaseItemData.category ~= "equipment" and fromBaseItemData.id == toBaseItemData.id then
-- from and to are same item, merge stacks
local currentCategory = network:invoke("getCurrentInventoryCategory")
local success = network:invokeServer("requestSplitInventorySlotDataStack", currentCategory, buttonFromInventorySlot.position, buttonToInventorySlot.position, buttonFromInventorySlot.stacks)
else
-- from and to are different items, swap them
local buttonFrom_image = buttonFrom.Image
local buttonFrom_stacks = buttonFrom.duplicateCount.Text
buttonFrom.Image = buttonTo.Image
buttonFrom.duplicateCount.Text = buttonTo.duplicateCount.Text
buttonTo.Image = buttonFrom_image
buttonTo.duplicateCount.Text = buttonFrom_stacks
local currentCategory = network:invoke("getCurrentInventoryCategory")
local success = network:invokeServer("switchInventorySlotData", currentCategory, buttonFromInventorySlot.position, buttonToInventorySlot.position)
end
else
if isRightClickTrigger and fromBaseItemData.canStack then
local currentCategory = network:invoke("getCurrentInventoryCategory")
local success = network:invokeServer("requestSplitInventorySlotDataStack", currentCategory, buttonFromInventorySlot.position, tonumber(buttonTo.Parent.Name), 1)
else
-- to is empty
local buttonFrom_image = buttonFrom.Image
local buttonFrom_stacks = buttonFrom.duplicateCount.Text
buttonFrom.Image = ""
buttonFrom.duplicateCount.Text = ""
buttonTo.Image = buttonFrom_image
buttonTo.duplicateCount.Text = buttonFrom_stacks
-- switching item with blank
local currentCategory = network:invoke("getCurrentInventoryCategory")
local success = network:invokeServer("switchInventorySlotData", currentCategory, buttonFromInventorySlot.position, tonumber(buttonTo.Parent.Name))
end
end
else
-- from is empty, no thanks.
end
end
elseif buttonTo:IsDescendantOf(ui.bottomRight.hotbarFrame) then
-- inventory to hotbarFrame
local inventorySlotData, buttonFromType = network:invoke("getInventorySlotDataByInventorySlotUI", buttonFrom)
if inventorySlotData then
if buttonFromType == "item" then
local inventoryItemBaseData = itemData[inventorySlotData.id]
if inventoryItemBaseData and inventoryItemBaseData.category == "consumable" and inventoryItemBaseData.canBeBound then
local num = string.gsub(buttonTo.Name,"[^.0-9]+","")
if tonumber(num) == 10 then num = 0 end
network:invokeServer("registerHotbarSlotData", mapping.dataType.item, inventorySlotData.id, tonumber(num))
end
end
end
elseif buttonTo:IsDescendantOf(menu_equipment) then
-- inventory to equip
local inventorySlotData, buttonFromType = network:invoke("getInventorySlotDataByInventorySlotUI", buttonFrom)
local equipmentSlotData = network:invoke("getEquipmentSlotDataByEquipmentSlotUI", buttonTo)
if inventorySlotData and buttonFromType == "item" then
if equipmentSlotData then
-- check if item is valid
local itemFromBaseData = itemData[inventorySlotData.id]
-- part of the following check is commented out by Davdiii
-- i think we can trust the server to do this validation, can't we?
-- i'm not altogether too concerned about the moment it'll take for
-- the server to tell us off if we're wrong, besides we wait anyway
if itemFromBaseData and itemFromBaseData.isEquippable --[[and buttonTo.Parent.Name == mapping.getMappingByValue("equipmentPosition", itemFromBaseData.equipmentSlot)]] then
-- instantly update client frame, server will force refresh if it was wrong.
-- this rewards good behaviour, and punishes bad behaviour with delays
local buttonFrom_image = buttonFrom.Image
local currentCategory = network:invoke("getCurrentInventoryCategory")
local success = network:invokeServer("transferInventoryToEquipment", currentCategory, inventorySlotData.position, mapping.equipmentPosition[buttonTo.Parent.Name])
if success then
buttonFrom.Image = buttonTo.Image
buttonTo.Image = buttonFrom_image
end
elseif itemFromBaseData.applyScroll then
local itemBaseData_enchantment = itemData[inventorySlotData.id]
local continue = true
if itemBaseData_enchantment.dye then
local equipmentBaseData = itemData[equipmentSlotData.id]
if not Modules.dyePreview.prompt(itemBaseData_enchantment, equipmentBaseData) then
continue = false
end
end
if continue then
local pos = buttonTo.AbsolutePosition + buttonTo.AbsoluteSize/2
local playerInput = {}
if itemBaseData_enchantment and itemBaseData_enchantment.playerInputFunction then
playerInput = itemBaseData_enchantment.playerInputFunction()
end
local success, scrollApplied, newInventorySlotData, status = network:invokeServer("playerRequest_enchantEquipment", inventorySlotData, equipmentSlotData, "equipment", playerInput)
if status then
spawn(function()
wait(0.5)
network:fire("alert", status)
end)
end
if success and scrollApplied and newInventorySlotData then
spawn(function()
wait(0.5)
local ringInfo = {
color = Modules.itemAcquistion.getTitleColorForInventorySlotData(newInventorySlotData) or Color3.new(1,1,1);
}
Modules.fx.ring(ringInfo, pos)
end)
end
end
end
else
local inventoryItemBaseData = itemData[inventorySlotData.id]
-- as expressed above, Davidii commented out part of this check
-- let the server do this validation! what's the big deal?
if inventoryItemBaseData.isEquippable --[[and buttonTo.Parent.Name == mapping.getMappingByValue("equipmentPosition", inventoryItemBaseData.equipmentSlot)]] then
-- inventory slot is empty
local currentCategory = network:invoke("getCurrentInventoryCategory")
local success = network:invokeServer("transferInventoryToEquipment", currentCategory, tonumber(buttonFrom.Parent.Name), mapping.equipmentPosition[buttonTo.Parent.Name])
end
end
end
elseif buttonTo:IsDescendantOf(menu_shop) then
local inventorySlotData, buttonFromType = network:invoke("getInventorySlotDataByInventorySlotUI", buttonFrom)
if inventorySlotData and buttonFromType == "item" then
local inventoryItemBaseData = itemData[inventorySlotData.id]
if inventoryItemBaseData then
network:invoke("shop_setCurrentItem", inventorySlotData, true)
end
end
elseif buttonTo:IsDescendantOf(menu_trade.yourTrade) then
local inventorySlotData, buttonFromType = network:invoke("getInventorySlotDataByInventorySlotUI", buttonFrom)
if inventorySlotData and buttonFromType == "item" then
Modules.trading.setLocalTradeSlot(buttonTo.Name, inventorySlotData)
end
end
else
-- buttonTo is nil, dragged onto overworld
local inventorySlotData, buttonFromType = network:invoke("getInventorySlotDataByInventorySlotUI", buttonFrom)
if inventorySlotData and buttonFromType == "item" then
local inventoryReal = itemData[inventorySlotData.id] or {name = "...wait what????????"}
local message = "Are you sure you want to drop your "..inventoryReal.name.."?"
if inventoryReal.soulbound then
message = "DESTROY your "..inventoryReal.name.."?"
end
local accepted = Modules.prompting_Fullscreen.prompt(message)
if accepted then
if inventoryReal.soulbound and not Modules.prompting_Fullscreen.prompt("⚠ ARE YOU SURE you want to DESTROY your " ..inventoryReal.name.."? This action cannot be undone! ⚠") then
return false
end
local success, errorMessage = network:invokeServer("playerRequest_dropItem", inventorySlotData)
end
end
end
elseif buttonFrom:IsDescendantOf(ui.bottomRight.hotbarFrame) then
if buttonTo then
if buttonTo:IsDescendantOf(ui.bottomRight.hotbarFrame) then
local fromData = (extraData and extraData.originSlotData) or network:invoke("getHotbarSlotDataByHotbarSlotUI", buttonFrom)
local toData = network:invoke("getHotbarSlotDataByHotbarSlotUI", buttonTo)
local toNum = string.gsub(buttonTo.Name,"[^.0-9]+","")
if tonumber(toNum) == 10 then toNum = 0 end
network:invokeServer("registerHotbarSlotData", fromData.dataType, fromData.id, tonumber(toNum))
local fromNum = string.gsub(buttonFrom.Name,"[^.0-9]+","")
if tonumber(fromNum) == 10 then fromNum = 0 end
if buttonTo ~= buttonFrom then
if toData then
network:invokeServer("registerHotbarSlotData", toData.dataType, toData.id, tonumber(fromNum))
else
network:invokeServer("registerHotbarSlotData", nil, nil, tonumber(fromNum))
end
end
end
else
local hotbarSlotData = network:invoke("getHotbarSlotDataByHotbarSlotUI", buttonFrom)
if hotbarSlotData then
-- dragged into overworld
network:invokeServer("registerHotbarSlotData", nil, nil, hotbarSlotData.position)
end
end
elseif buttonFrom:IsDescendantOf(menu_equipment) then
if buttonTo then
if buttonTo:IsDescendantOf(menu_enchant) then
local equipmentSlotData = network:invoke("getEquipmentSlotDataByEquipmentSlotUI", buttonFrom)
if equipmentSlotData then
Modules.enchant.dragItem(equipmentSlotData, "equipment")
end
elseif buttonTo:IsDescendantOf(menu_inventory) then
-- equip to inventory
local inventorySlotData, buttonToType = network:invoke("getInventorySlotDataByInventorySlotUI", buttonTo)
local equipmentSlotData = network:invoke("getEquipmentSlotDataByEquipmentSlotUI", buttonFrom)
if inventorySlotData and buttonToType == "item" then
if equipmentSlotData then
-- check if item is valid
local itemFromBaseData = itemData[inventorySlotData.id]
if itemFromBaseData and itemFromBaseData.isEquippable and buttonFrom.Parent.Name == mapping.getMappingByValue("equipmentPosition", itemFromBaseData.equipmentSlot) then
-- instantly update client frame, server will force refresh if it was wrong.
-- this rewards good behaviour, and punishes bad behaviour with delays
local buttonTo_image = buttonTo.Image
buttonTo.Image = buttonFrom.Image
buttonFrom.Image = buttonTo_image
local currentCategory = network:invoke("getCurrentInventoryCategory")
local success = network:invokeServer("transferInventoryToEquipment", currentCategory, inventorySlotData.position, mapping.equipmentPosition[buttonFrom.Parent.Name])
end
end
else
local equipmentItemBaseData = itemData[equipmentSlotData.id]
local currentCategory = network:invoke("getCurrentInventoryCategory")
local success = network:invokeServer("transferInventoryToEquipment", currentCategory, tonumber(buttonTo.Parent.Name), mapping.equipmentPosition[buttonFrom.Parent.Name])
end
end
else
-- dragged into overworld
end
elseif buttonFrom:IsDescendantOf(menu_abilities) then
if buttonTo and buttonTo:IsDescendantOf(ui.bottomRight.hotbarFrame) then
local abilityData = network:invoke("getAbilitySlotDataByAbilitySlotUI", buttonFrom)
if abilityData.id then
local num = string.gsub(buttonTo.Name,"[^.0-9]+","")
if tonumber(num) == 10 then num = 0 end
network:invokeServer("registerHotbarSlotData", mapping.dataType.ability, abilityData.id, tonumber(num))
end
end
end
IS_PROCESSING_INVENTORY_SLOT_SWITCH = false
end
module.processSwap = processSwap
local function getDropTarget(exclusion)
local targetDrop
local function recur(guiObject)
if guiObject:IsA("GuiObject") and guiObject.Visible then
if guiObject:FindFirstChild("draggableFrame") and guiObject ~= exclusion and isPositionInsideFrame(dragDropMask.AbsolutePosition, guiObject) then
targetDrop = guiObject
end
for i, gui in pairs(guiObject:GetChildren()) do
recur(gui)
end
end
end
for i, gui in pairs(ui:GetChildren()) do
recur(gui)
end
return targetDrop
end
local function isDragDropFrame(frame)
for _, _frame in pairs(dragDropFrameCollection) do
if _frame == frame then
return true
end
end
return false
end
local runService = game:GetService("RunService")
local function update(input)
if currentDragFrameOriginator then
if dragDropMask.ImageTransparency ~= 0 then
currentDragFrameOriginator.ImageTransparency = 0.6
if currentDragFrameOriginator:IsDescendantOf(menu_inventory) then
currentDragFrameOriginator.duplicateCount.Visible = false
end
dragDropMask.Image = currentDragFrameOriginator.Image
dragDropMask.ImageTransparency = 0
end
dragDropMask.Position = UDim2.new(0, input.Position.X - 25, 0, input.Position.Y - 25)
end
end
function module.drag.setIsDragDropFrame(frame)
if not frame:IsA("ImageLabel") and not frame:IsA("ImageButton") then
error("Only ImageButtons and ImageLabels can be DragDropFrames")
return
end
if isDragDropFrame(frame) then return end
local function onInputBegan_UI(inputObject)
if not frame.Active then
return false
end
if (inputObject.UserInputType == Enum.UserInputType.MouseButton1 or inputObject.UserInputType == Enum.UserInputType.Touch) and inputObject.UserInputState == Enum.UserInputState.Begin then
if frame.ImageTransparency < 1 then
local extraData = {}
currentDragFrameOriginator = frame
local startTime = tick()
local startPosition = inputObject.Position
repeat runService.RenderStepped:wait()
if inputObject.UserInputType == Enum.UserInputType.Touch then
update(inputObject)
end
-- input ended naturally
if inputObject.UserInputState == Enum.UserInputState.End then
if currentDragFrameOriginator == frame then
local finalPosition = inputObject.Position
if tick() - startTime > 0.1 and utilities.magnitude(startPosition - finalPosition) > 26 then
-- local dropTarget = getDropTarget(frame)
local dropTarget = getDropTarget()
spawn(function()
if processSwap(frame, dropTarget, nil, extraData) then
-- was accepted by client ???
else
-- was denied by client ???
end
end)
end
end
end
until inputObject.UserInputState == Enum.UserInputState.End or inputObject.UserInputState == Enum.UserInputState.Cancel
if currentDragFrameOriginator == frame then
currentDragFrameOriginator = nil
if frame:IsDescendantOf(menu_inventory) then
frame.duplicateCount.Visible = true
end
-- reset stuff!
dragDropMask.ImageTransparency = 1
dragDropMask.Position = UDim2.new(-1, -100, -1, -100)
frame.ImageTransparency = 0
end
end
end
end
frame.InputBegan:connect(onInputBegan_UI)
table.insert(dragDropFrameCollection, frame)
end
function module.setIsDoubleClickFrame(imageButton, timePeriod, callback)
local timeOfLastClick
imageButton.MouseButton1Click:connect(function() network:invoke("populateItemHoverFrame") callback(imageButton) end)
if imageButton and imageButton.Parent then
local mouseEnterScale = Instance.new("UIScale")
mouseEnterScale.Parent = imageButton
local z = imageButton.Parent.ZIndex
local bc
if imageButton.Parent:IsA("ImageLabel") or imageButton.Parent:IsA("ImageButton") then
bc = imageButton.Parent.ImageColor3
end
local shine
if imageButton.Parent:FindFirstChild("shine") then
shine = imageButton.Parent.shine.ImageTransparency
end
imageButton.MouseEnter:connect(function()
imageButton.Parent.ZIndex = z + 1
tween(mouseEnterScale, {"Scale"}, {1.1}, 0.4)
if bc then
tween(imageButton.Parent, {"ImageColor3"}, {Color3.new(bc.r * 0.65, bc.g * 0.65, bc.b * 0.65)}, 0.6)
end
if shine then
tween(imageButton.Parent.shine, {"ImageTransparency"}, {shine/1.5}, 0.6)
end
end)
imageButton.MouseLeave:connect(function()
imageButton.Parent.ZIndex = z
tween(mouseEnterScale, {"Scale"}, {1.0}, 0.4)
if bc then
tween(imageButton.Parent, {"ImageColor3"}, {bc}, 0.6)
end
if shine then
tween(imageButton.Parent.shine, {"ImageTransparency"}, {shine}, 0.6)
end
end)
end
end
local isEnchanting = false
function module.setIsEnchantingFrame(imageButton, callback)
local timeOfLastClick
local isEnchanting = false
local function onInputBegan_ButtonClicked()
-- register first click!
if not timeOfLastClick then
timeOfLastClick = tick()
-- exit
return
end
-- calculate time since last click
local timeSinceLastClick = tick() - timeOfLastClick
if timeSinceLastClick < 0.1 then
-- double clicked
isEnchanting = true
end
-- reset time since last click
timeOfLastClick = nil
end
imageButton.MouseButton1Click:connect(onInputBegan_ButtonClicked)
end
local function onInputChanged(inputObject)
if currentDragFrameOriginator and inputObject.UserInputType == Enum.UserInputType.MouseMovement then
update(inputObject)
end
end
local function onInputBegan(inputObject)
if currentDragFrameOriginator and inputObject.UserInputType == Enum.UserInputType.MouseButton2 then
local dropTarget = getDropTarget(currentDragFrameOriginator)
if dropTarget then
processSwap(currentDragFrameOriginator, dropTarget, true)
end
end
end
userInputService.InputChanged:connect(onInputChanged)
userInputService.InputBegan:connect(onInputBegan)
for i, obj in pairs(ui:GetDescendants()) do
if obj.Name == "draggableFrame" then
module.drag.setIsDragDropFrame(obj.Parent)
end
end
return module
================================================
FILE: src/StarterGui/unstucker.lua
================================================
-- just a couple of things that should be run every time the player respawns
-- essentially, why should we ever start a live arrested or casting? I say no
-- this fixes a bug with Magic Bomb where if you die during it, the ability
-- just breaks entirely and you end up stuck for all eternity. Let's not.
--
-- Davidii
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
spawn(function()
--network:fire("setIsPlayerCastingAbility", false)
network:invoke("setCharacterArrested", false)
end)
return {}
================================================
FILE: src/StarterGui/verifications.lua
================================================
local module = {}
local player = game:GetService("Players").LocalPlayer
local gui = player.PlayerGui.gameUI.verify
function module.init(Modules)
local network = Modules.network
game.Players.LocalPlayer.Chatted:Connect(function(text)
if text == "/verify" then
Modules.focus.toggle(gui)
end
end)
gui.Frame.send.Activated:connect(function()
local success = network:invokeServer("playerRequest_redeemcode", gui.Frame.code.TextBox.Text)
if success then
local textObject = {
text = "You have been verified!";
textColor3 = Color3.new(0,0,0);
backgroundColor3 = Color3.fromRGB(0,255,150);
backgroundTransparency = 0;
textStrokeTransparency = 1;
}
Modules.notifications.alert(textObject, 3)
else
local textObject = {
text = "Invalid verification code.";
textColor3 = Color3.new(0,0,0);
backgroundColor3 = Color3.fromRGB(255,100,0);
backgroundTransparency = 0;
textStrokeTransparency = 1;
}
Modules.notifications.alert(textObject, 3)
end
Modules.focus.toggle(gui)
end)
gui.Frame.close.Activated:connect(function()
Modules.focus.toggle(gui)
end)
end
return module
================================================
FILE: src/StarterPlayer/StarterCharacterScripts/Animate.client.lua
================================================
--[[
local userInputService = game:GetService("UserInputService")
-- todo: implement particle support for sprinting
-- todo: do not play when jumping, or not sprinting
local tweenService = game:GetService("TweenService")
local camera = workspace.CurrentCamera
local player = game.Players.LocalPlayer
local character = player.Character
local TWEEN_INFO = TweenInfo.new(1 / 3, Enum.EasingStyle.Quad, Enum.EasingDirection.Out, 0, false, 0)
local cameraSprinting = tweenService:Create(camera, TWEEN_INFO, {FieldOfView = 80})
local cameraWalking = tweenService:Create(camera, TWEEN_INFO, {FieldOfView = 70})
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local animations = require(replicatedStorage.playerAnimationData)
local currentlyPlayingAnimations = {}
local function playAnimation(animationName, blendingId, doPlayRepeat)
if not animationName then return end
if not blendingId then return end
if not currentlyPlayingAnimations[blendingId] or (doPlayRepeat or currentlyPlayingAnimations[blendingId] ~= animations[animationName].animationTrack) then
if currentlyPlayingAnimations[blendingId] then
currentlyPlayingAnimations[blendingId]:Stop()
currentlyPlayingAnimations[blendingId] = nil
end
local animationTrack = animations[animationName].animationTrack
currentlyPlayingAnimations[blendingId] = animationTrack
-- play it
animationTrack:Play(0.15, 1, animations[animationName].speed or 1)
end
end
local function onHumanoid_Jumping(isActive)
if isActive then
playAnimation("jumping", "action", true)
end
end
local function onCharacterAdded(character)
local humanoid = character:WaitForChild("Humanoid")
humanoid.Jumping:connect(function(isActive)
states.isInAir = true
character.RightFoot.ParticleEmitter.Enabled = false
character.LeftFoot.ParticleEmitter.Enabled = false
end)
humanoid.FreeFalling:connect(function(isActive)
if not states.isActive then
states.isInAir = false
if states.isSprinting then
character.RightFoot.ParticleEmitter.Enabled = true
character.LeftFoot.ParticleEmitter.Enabled = true
end
end
end)
end
local function main()
network:create("characterStateChanged", "BindableEvent")
userInputService.InputBegan:connect(onInputBegan)
userInputService.InputEnded:connect(onInputEnded)
end
main()
--]]
================================================
FILE: src/StarterPlayer/StarterCharacterScripts/Health.client.lua
================================================
-- No regen allowed! --
================================================
FILE: src/StarterPlayer/StarterCharacterScripts/LocalTeleportStuff.client.lua
================================================
local Player = game.Players.LocalPlayer
-- populates player scripts in case the stuff expected to be there is not there
if Player.PlayerScripts:FindFirstChild("assetsLoaded") == nil then
--Player.PlayerScripts:ClearAllChildren()
for e,Script in pairs(game.StarterPlayer.StarterPlayerScripts:GetChildren()) do
if Player.PlayerScripts:FindFirstChild(Script.Name) == nil then
Script.Parent = Player.PlayerScripts
end
end
end
================================================
FILE: src/StarterPlayer/StarterCharacterScripts/Sound.client.lua
================================================
-- S I L E N C E --
-- :D
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/ChatScript/ChatMain/ChannelsBar.lua
================================================
-- // FileName: ChannelsBar.lua
-- // Written by: Xsitsu
-- // Description: Manages creating, destroying, and displaying ChannelTabs.
local module = {}
local PlayerGui = game:GetService("Players").LocalPlayer:WaitForChild("PlayerGui")
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local modulesFolder = script.Parent
local moduleChannelsTab = require(modulesFolder:WaitForChild("ChannelsTab"))
local MessageSender = require(modulesFolder:WaitForChild("MessageSender"))
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:CreateGuiObjects(targetParent)
local BaseFrame = Instance.new("Frame")
BaseFrame.Selectable = false
BaseFrame.Size = UDim2.new(1, 0, 1, 0)
BaseFrame.BackgroundTransparency = 1
BaseFrame.Parent = targetParent
local ScrollingBase = Instance.new("Frame")
ScrollingBase.Selectable = false
ScrollingBase.Name = "ScrollingBase"
ScrollingBase.BackgroundTransparency = 1
ScrollingBase.ClipsDescendants = true
ScrollingBase.Size = UDim2.new(1, 0, 1, 0)
ScrollingBase.Position = UDim2.new(0, 0, 0, 0)
ScrollingBase.Parent = BaseFrame
local ScrollerSizer = Instance.new("Frame")
ScrollerSizer.Selectable = false
ScrollerSizer.Name = "ScrollerSizer"
ScrollerSizer.BackgroundTransparency = 1
ScrollerSizer.Size = UDim2.new(1, 0, 1, 0)
ScrollerSizer.Position = UDim2.new(0, 0, 0, 0)
ScrollerSizer.Parent = ScrollingBase
local ScrollerFrame = Instance.new("Frame")
ScrollerFrame.Selectable = false
ScrollerFrame.Name = "ScrollerFrame"
ScrollerFrame.BackgroundTransparency = 1
ScrollerFrame.Size = UDim2.new(1, 0, 1, 0)
ScrollerFrame.Position = UDim2.new(0, 0, 0, 0)
ScrollerFrame.Parent = ScrollerSizer
local LeaveConfirmationFrameBase = Instance.new("Frame")
LeaveConfirmationFrameBase.Selectable = false
LeaveConfirmationFrameBase.Size = UDim2.new(1, 0, 1, 0)
LeaveConfirmationFrameBase.Position = UDim2.new(0, 0, 0, 0)
LeaveConfirmationFrameBase.ClipsDescendants = true
LeaveConfirmationFrameBase.BackgroundTransparency = 1
LeaveConfirmationFrameBase.Parent = BaseFrame
local LeaveConfirmationFrame = Instance.new("Frame")
LeaveConfirmationFrame.Selectable = false
LeaveConfirmationFrame.Name = "LeaveConfirmationFrame"
LeaveConfirmationFrame.Size = UDim2.new(1, 0, 1, 0)
LeaveConfirmationFrame.Position = UDim2.new(0, 0, 1, 0)
LeaveConfirmationFrame.BackgroundTransparency = 0.6
LeaveConfirmationFrame.BorderSizePixel = 0
LeaveConfirmationFrame.BackgroundColor3 = Color3.new(0, 0, 0)
LeaveConfirmationFrame.Parent = LeaveConfirmationFrameBase
local InputBlocker = Instance.new("TextButton")
InputBlocker.Selectable = false
InputBlocker.Size = UDim2.new(1, 0, 1, 0)
InputBlocker.BackgroundTransparency = 1
InputBlocker.Text = ""
InputBlocker.Parent = LeaveConfirmationFrame
local LeaveConfirmationButtonYes = Instance.new("TextButton")
LeaveConfirmationButtonYes.Selectable = false
LeaveConfirmationButtonYes.Size = UDim2.new(0.25, 0, 1, 0)
LeaveConfirmationButtonYes.BackgroundTransparency = 1
LeaveConfirmationButtonYes.Font = ChatSettings.DefaultFont
LeaveConfirmationButtonYes.TextSize = 18
LeaveConfirmationButtonYes.TextStrokeTransparency = 0.75
LeaveConfirmationButtonYes.Position = UDim2.new(0, 0, 0, 0)
LeaveConfirmationButtonYes.TextColor3 = Color3.new(0, 1, 0)
LeaveConfirmationButtonYes.Text = "Confirm"
LeaveConfirmationButtonYes.Parent = LeaveConfirmationFrame
local LeaveConfirmationButtonNo = LeaveConfirmationButtonYes:Clone()
LeaveConfirmationButtonNo.Parent = LeaveConfirmationFrame
LeaveConfirmationButtonNo.Position = UDim2.new(0.75, 0, 0, 0)
LeaveConfirmationButtonNo.TextColor3 = Color3.new(1, 0, 0)
LeaveConfirmationButtonNo.Text = "Cancel"
local LeaveConfirmationNotice = Instance.new("TextLabel")
LeaveConfirmationNotice.Selectable = false
LeaveConfirmationNotice.Size = UDim2.new(0.5, 0, 1, 0)
LeaveConfirmationNotice.Position = UDim2.new(0.25, 0, 0, 0)
LeaveConfirmationNotice.BackgroundTransparency = 1
LeaveConfirmationNotice.TextColor3 = Color3.new(1, 1, 1)
LeaveConfirmationNotice.TextStrokeTransparency = 0.75
LeaveConfirmationNotice.Text = "Leave channel ?"
LeaveConfirmationNotice.Font = ChatSettings.DefaultFont
LeaveConfirmationNotice.TextSize = 18
LeaveConfirmationNotice.Parent = LeaveConfirmationFrame
local LeaveTarget = Instance.new("StringValue")
LeaveTarget.Name = "LeaveTarget"
LeaveTarget.Parent = LeaveConfirmationFrame
local outPos = LeaveConfirmationFrame.Position
LeaveConfirmationButtonYes.MouseButton1Click:connect(function()
MessageSender:SendMessage(string.format("/leave %s", LeaveTarget.Value), nil)
LeaveConfirmationFrame:TweenPosition(outPos, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.2, true)
end)
LeaveConfirmationButtonNo.MouseButton1Click:connect(function()
LeaveConfirmationFrame:TweenPosition(outPos, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.2, true)
end)
local scale = 0.7
local scaleOther = (1 - scale) / 2
local pageButtonImage = "rbxasset://textures/ui/Chat/TabArrowBackground.png"
local pageButtonArrowImage = "rbxasset://textures/ui/Chat/TabArrow.png"
--// ToDo: Remove these lines when the assets are put into trunk.
--// These grab unchanging versions hosted on the site, and not from the content folder.
pageButtonImage = "rbxassetid://471630199"
pageButtonArrowImage = "rbxassetid://471630112"
local PageLeftButton = Instance.new("ImageButton", BaseFrame)
PageLeftButton.Selectable = ChatSettings.GamepadNavigationEnabled
PageLeftButton.Name = "PageLeftButton"
PageLeftButton.SizeConstraint = Enum.SizeConstraint.RelativeYY
PageLeftButton.Size = UDim2.new(scale, 0, scale, 0)
PageLeftButton.BackgroundTransparency = 1
PageLeftButton.Position = UDim2.new(0, 4, scaleOther, 0)
PageLeftButton.Visible = false
PageLeftButton.Image = pageButtonImage
local ArrowLabel = Instance.new("ImageLabel", PageLeftButton)
ArrowLabel.Name = "ArrowLabel"
ArrowLabel.BackgroundTransparency = 1
ArrowLabel.Size = UDim2.new(0.4, 0, 0.4, 0)
ArrowLabel.Image = pageButtonArrowImage
local PageRightButtonPositionalHelper = Instance.new("Frame", BaseFrame)
PageRightButtonPositionalHelper.Selectable = false
PageRightButtonPositionalHelper.BackgroundTransparency = 1
PageRightButtonPositionalHelper.Name = "PositionalHelper"
PageRightButtonPositionalHelper.Size = PageLeftButton.Size
PageRightButtonPositionalHelper.SizeConstraint = PageLeftButton.SizeConstraint
PageRightButtonPositionalHelper.Position = UDim2.new(1, 0, scaleOther, 0)
local PageRightButton = PageLeftButton:Clone()
PageRightButton.Parent = PageRightButtonPositionalHelper
PageRightButton.Name = "PageRightButton"
PageRightButton.Size = UDim2.new(1, 0, 1, 0)
PageRightButton.SizeConstraint = Enum.SizeConstraint.RelativeXY
PageRightButton.Position = UDim2.new(-1, -4, 0, 0)
local positionOffset = UDim2.new(0.05, 0, 0, 0)
PageRightButton.ArrowLabel.Position = UDim2.new(0.3, 0, 0.3, 0) + positionOffset
PageLeftButton.ArrowLabel.Position = UDim2.new(0.3, 0, 0.3, 0) - positionOffset
PageLeftButton.ArrowLabel.Rotation = 180
self.GuiObject = BaseFrame
self.GuiObjects.BaseFrame = BaseFrame
self.GuiObjects.ScrollerSizer = ScrollerSizer
self.GuiObjects.ScrollerFrame = ScrollerFrame
self.GuiObjects.PageLeftButton = PageLeftButton
self.GuiObjects.PageRightButton = PageRightButton
self.GuiObjects.LeaveConfirmationFrame = LeaveConfirmationFrame
self.GuiObjects.LeaveConfirmationNotice = LeaveConfirmationNotice
self.GuiObjects.PageLeftButtonArrow = PageLeftButton.ArrowLabel
self.GuiObjects.PageRightButtonArrow = PageRightButton.ArrowLabel
self:AnimGuiObjects()
PageLeftButton.MouseButton1Click:connect(function() self:ScrollChannelsFrame(-1) end)
PageRightButton.MouseButton1Click:connect(function() self:ScrollChannelsFrame(1) end)
self:ScrollChannelsFrame(0)
end
function methods:UpdateMessagePostedInChannel(channelName)
local tab = self:GetChannelTab(channelName)
if (tab) then
tab:UpdateMessagePostedInChannel()
else
warn("ChannelsTab '" .. channelName .. "' does not exist!")
end
end
function methods:AddChannelTab(channelName)
if (self:GetChannelTab(channelName)) then
error("Channel tab '" .. channelName .. "'already exists!")
end
local tab = moduleChannelsTab.new(channelName)
tab.GuiObject.Parent = self.GuiObjects.ScrollerFrame
self.ChannelTabs[channelName:lower()] = tab
self.NumTabs = self.NumTabs + 1
self:OrganizeChannelTabs()
if (ChatSettings.RightClickToLeaveChannelEnabled) then
tab.NameTag.MouseButton2Click:connect(function()
self.LeaveConfirmationNotice.Text = string.format("Leave channel %s?", tab.ChannelName)
self.LeaveConfirmationFrame.LeaveTarget.Value = tab.ChannelName
self.LeaveConfirmationFrame:TweenPosition(UDim2.new(0, 0, 0, 0), Enum.EasingDirection.In, Enum.EasingStyle.Quad, 0.2, true)
end)
end
return tab
end
function methods:RemoveChannelTab(channelName)
if (not self:GetChannelTab(channelName)) then
error("Channel tab '" .. channelName .. "'does not exist!")
end
local indexName = channelName:lower()
self.ChannelTabs[indexName]:Destroy()
self.ChannelTabs[indexName] = nil
self.NumTabs = self.NumTabs - 1
self:OrganizeChannelTabs()
end
function methods:GetChannelTab(channelName)
return self.ChannelTabs[channelName:lower()]
end
function methods:OrganizeChannelTabs()
local order = {}
table.insert(order, self:GetChannelTab(ChatSettings.GeneralChannelName))
table.insert(order, self:GetChannelTab("System"))
for tabIndexName, tab in pairs(self.ChannelTabs) do
if (tab.ChannelName ~= ChatSettings.GeneralChannelName and tab.ChannelName ~= "System") then
table.insert(order, tab)
end
end
for index, tab in pairs(order) do
tab.GuiObject.Position = UDim2.new(index - 1, 0, 0, 0)
end
--// Dynamic tab resizing
self.GuiObjects.ScrollerSizer.Size = UDim2.new(1 / math.max(1, math.min(ChatSettings.ChannelsBarFullTabSize, self.NumTabs)), 0, 1, 0)
self:ScrollChannelsFrame(0)
end
function methods:ResizeChannelTabText(textSize)
for i, tab in pairs(self.ChannelTabs) do
tab:SetTextSize(textSize)
end
end
function methods:ScrollChannelsFrame(dir)
if (self.ScrollChannelsFrameLock) then return end
self.ScrollChannelsFrameLock = true
local tabNumber = ChatSettings.ChannelsBarFullTabSize
local newPageNum = self.CurPageNum + dir
if (newPageNum < 0) then
newPageNum = 0
elseif (newPageNum > 0 and newPageNum + tabNumber > self.NumTabs) then
newPageNum = self.NumTabs - tabNumber
end
self.CurPageNum = newPageNum
local tweenTime = 0.15
local endPos = UDim2.new(-self.CurPageNum, 0, 0, 0)
self.GuiObjects.PageLeftButton.Visible = (self.CurPageNum > 0)
self.GuiObjects.PageRightButton.Visible = (self.CurPageNum + tabNumber < self.NumTabs)
if dir == 0 then
self.ScrollChannelsFrameLock = false
return
end
local function UnlockFunc()
self.ScrollChannelsFrameLock = false
end
self:WaitUntilParentedCorrectly()
self.GuiObjects.ScrollerFrame:TweenPosition(endPos, Enum.EasingDirection.InOut, Enum.EasingStyle.Quad, tweenTime, true, UnlockFunc)
end
function methods:FadeOutBackground(duration)
for channelName, channelObj in pairs(self.ChannelTabs) do
channelObj:FadeOutBackground(duration)
end
self.AnimParams.Background_TargetTransparency = 1
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeInBackground(duration)
for channelName, channelObj in pairs(self.ChannelTabs) do
channelObj:FadeInBackground(duration)
end
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeOutText(duration)
for channelName, channelObj in pairs(self.ChannelTabs) do
channelObj:FadeOutText(duration)
end
end
function methods:FadeInText(duration)
for channelName, channelObj in pairs(self.ChannelTabs) do
channelObj:FadeInText(duration)
end
end
function methods:AnimGuiObjects()
self.GuiObjects.PageLeftButton.ImageTransparency = self.AnimParams.Background_CurrentTransparency
self.GuiObjects.PageRightButton.ImageTransparency = self.AnimParams.Background_CurrentTransparency
self.GuiObjects.PageLeftButtonArrow.ImageTransparency = self.AnimParams.Background_CurrentTransparency
self.GuiObjects.PageRightButtonArrow.ImageTransparency = self.AnimParams.Background_CurrentTransparency
end
function methods:InitializeAnimParams()
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_CurrentTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
end
function methods:Update(dtScale)
for channelName, channelObj in pairs(self.ChannelTabs) do
channelObj:Update(dtScale)
end
self.AnimParams.Background_CurrentTransparency = CurveUtil:Expt(
self.AnimParams.Background_CurrentTransparency,
self.AnimParams.Background_TargetTransparency,
self.AnimParams.Background_NormalizedExptValue,
dtScale
)
self:AnimGuiObjects()
end
--// ToDo: Move to common modules
function methods:WaitUntilParentedCorrectly()
while (not self.GuiObject:IsDescendantOf(game:GetService("Players").LocalPlayer)) do
self.GuiObject.AncestryChanged:wait()
end
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new()
local obj = setmetatable({}, methods)
obj.GuiObject = nil
obj.GuiObjects = {}
obj.ChannelTabs = {}
obj.NumTabs = 0
obj.CurPageNum = 0
obj.ScrollChannelsFrameLock = false
obj.AnimParams = {}
obj:InitializeAnimParams()
ChatSettings.SettingsChanged:connect(function(setting, value)
if (setting == "ChatChannelsTabTextSize") then
obj:ResizeChannelTabText(value)
end
end)
return obj
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/ChatScript/ChatMain/ChannelsTab.lua
================================================
-- // FileName: ChannelsTab.lua
-- // Written by: Xsitsu
-- // Description: Channel tab button for selecting current channel and also displaying if currently selected.
local module = {}
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local modulesFolder = script.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
local function CreateGuiObjects()
local BaseFrame = Instance.new("Frame")
BaseFrame.Selectable = false
BaseFrame.Size = UDim2.new(1, 0, 1, 0)
BaseFrame.BackgroundTransparency = 1
local gapOffsetX = 1
local gapOffsetY = 1
local BackgroundFrame = Instance.new("Frame")
BackgroundFrame.Selectable = false
BackgroundFrame.Name = "BackgroundFrame"
BackgroundFrame.Size = UDim2.new(1, -gapOffsetX * 2, 1, -gapOffsetY * 2)
BackgroundFrame.Position = UDim2.new(0, gapOffsetX, 0, gapOffsetY)
BackgroundFrame.BackgroundTransparency = 1
BackgroundFrame.Parent = BaseFrame
local UnselectedFrame = Instance.new("Frame")
UnselectedFrame.Selectable = false
UnselectedFrame.Name = "UnselectedFrame"
UnselectedFrame.Size = UDim2.new(1, 0, 1, 0)
UnselectedFrame.Position = UDim2.new(0, 0, 0, 0)
UnselectedFrame.BorderSizePixel = 0
UnselectedFrame.BackgroundColor3 = ChatSettings.ChannelsTabUnselectedColor
UnselectedFrame.BackgroundTransparency = 0.6
UnselectedFrame.Parent = BackgroundFrame
local SelectedFrame = Instance.new("Frame")
SelectedFrame.Selectable = false
SelectedFrame.Name = "SelectedFrame"
SelectedFrame.Size = UDim2.new(1, 0, 1, 0)
SelectedFrame.Position = UDim2.new(0, 0, 0, 0)
SelectedFrame.BorderSizePixel = 0
SelectedFrame.BackgroundColor3 = ChatSettings.ChannelsTabSelectedColor
SelectedFrame.BackgroundTransparency = 1
SelectedFrame.Parent = BackgroundFrame
local SelectedFrameBackgroundImage = Instance.new("ImageLabel")
SelectedFrameBackgroundImage.Selectable = false
SelectedFrameBackgroundImage.Name = "BackgroundImage"
SelectedFrameBackgroundImage.BackgroundTransparency = 1
SelectedFrameBackgroundImage.BorderSizePixel = 0
SelectedFrameBackgroundImage.Size = UDim2.new(1, 0, 1, 0)
SelectedFrameBackgroundImage.Position = UDim2.new(0, 0, 0, 0)
SelectedFrameBackgroundImage.ScaleType = Enum.ScaleType.Slice
SelectedFrameBackgroundImage.Parent = SelectedFrame
SelectedFrameBackgroundImage.BackgroundTransparency = 0.6 - 1
local rate = 1.2 * 1
SelectedFrameBackgroundImage.BackgroundColor3 = Color3.fromRGB(78 * rate, 84 * rate, 96 * rate)
local borderXOffset = 2
local blueBarYSize = 4
local BlueBarLeft = Instance.new("ImageLabel")
BlueBarLeft.Selectable = false
BlueBarLeft.Size = UDim2.new(0.5, -borderXOffset, 0, blueBarYSize)
BlueBarLeft.BackgroundTransparency = 1
BlueBarLeft.ScaleType = Enum.ScaleType.Slice
BlueBarLeft.SliceCenter = Rect.new(3,3,32,21)
BlueBarLeft.Parent = SelectedFrame
local BlueBarRight = BlueBarLeft:Clone()
BlueBarRight.Parent = SelectedFrame
BlueBarLeft.Position = UDim2.new(0, borderXOffset, 1, -blueBarYSize)
BlueBarRight.Position = UDim2.new(0.5, 0, 1, -blueBarYSize)
BlueBarLeft.Image = "rbxasset://textures/ui/Settings/Slider/SelectedBarLeft.png"
BlueBarRight.Image = "rbxasset://textures/ui/Settings/Slider/SelectedBarRight.png"
BlueBarLeft.Name = "BlueBarLeft"
BlueBarRight.Name = "BlueBarRight"
local NameTag = Instance.new("TextButton")
NameTag.Selectable = ChatSettings.GamepadNavigationEnabled
NameTag.Size = UDim2.new(1, 0, 1, 0)
NameTag.Position = UDim2.new(0, 0, 0, 0)
NameTag.BackgroundTransparency = 1
NameTag.Font = ChatSettings.DefaultFont
NameTag.TextSize = ChatSettings.ChatChannelsTabTextSize
NameTag.TextColor3 = Color3.new(1, 1, 1)
NameTag.TextStrokeTransparency = 0.75
NameTag.Parent = BackgroundFrame
local NameTagNonSelect = NameTag:Clone()
local NameTagSelect = NameTag:Clone()
NameTagNonSelect.Parent = UnselectedFrame
NameTagSelect.Parent = SelectedFrame
NameTagNonSelect.Font = Enum.Font.SourceSans
NameTagNonSelect.Active = false
NameTagSelect.Active = false
local NewMessageIconFrame = Instance.new("Frame")
NewMessageIconFrame.Selectable = false
NewMessageIconFrame.Size = UDim2.new(0, 18, 0, 18)
NewMessageIconFrame.Position = UDim2.new(0.8, -9, 0.5, -9)
NewMessageIconFrame.BackgroundTransparency = 1
NewMessageIconFrame.Parent = BackgroundFrame
local NewMessageIcon = Instance.new("ImageLabel")
NewMessageIcon.Selectable = false
NewMessageIcon.Size = UDim2.new(1, 0, 1, 0)
NewMessageIcon.BackgroundTransparency = 1
NewMessageIcon.Image = "rbxasset://textures/ui/Chat/MessageCounter.png"
NewMessageIcon.Visible = false
NewMessageIcon.Parent = NewMessageIconFrame
local NewMessageIconText = Instance.new("TextLabel")
NewMessageIconText.Selectable = false
NewMessageIconText.BackgroundTransparency = 1
NewMessageIconText.Size = UDim2.new(0, 13, 0, 9)
NewMessageIconText.Position = UDim2.new(0.5, -7, 0.5, -7)
NewMessageIconText.Font = ChatSettings.DefaultFont
NewMessageIconText.TextSize = 14
NewMessageIconText.TextColor3 = Color3.new(1, 1, 1)
NewMessageIconText.Text = ""
NewMessageIconText.Parent = NewMessageIcon
return BaseFrame, NameTag, NameTagNonSelect, NameTagSelect, NewMessageIcon, UnselectedFrame, SelectedFrame
end
function methods:Destroy()
self.GuiObject:Destroy()
end
function methods:UpdateMessagePostedInChannel(ignoreActive)
if (self.Active and (ignoreActive ~= true)) then return end
local count = self.UnreadMessageCount + 1
self.UnreadMessageCount = count
local label = self.NewMessageIcon
label.Visible = true
label.TextLabel.Text = (count < 100) and tostring(count) or "!"
local tweenTime = 0.15
local tweenPosOffset = UDim2.new(0, 0, -0.1, 0)
local curPos = label.Position
local outPos = curPos + tweenPosOffset
local easingDirection = Enum.EasingDirection.Out
local easingStyle = Enum.EasingStyle.Quad
label.Position = UDim2.new(0, 0, -0.15, 0)
label:TweenPosition(UDim2.new(0, 0, 0, 0), easingDirection, easingStyle, tweenTime, true)
end
function methods:SetActive(active)
self.Active = active
self.UnselectedFrame.Visible = not active
self.SelectedFrame.Visible = active
if (active) then
self.UnreadMessageCount = 0
self.NewMessageIcon.Visible = false
self.NameTag.Font = Enum.Font.SourceSansBold
else
self.NameTag.Font = Enum.Font.SourceSans
end
end
function methods:SetTextSize(textSize)
self.NameTag.TextSize = textSize
end
function methods:FadeOutBackground(duration)
self.AnimParams.Background_TargetTransparency = 1
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeInBackground(duration)
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeOutText(duration)
self.AnimParams.Text_TargetTransparency = 1
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
self.AnimParams.TextStroke_TargetTransparency = 1
self.AnimParams.TextStroke_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeInText(duration)
self.AnimParams.Text_TargetTransparency = 0
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
self.AnimParams.TextStroke_TargetTransparency = 0.75
self.AnimParams.TextStroke_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:AnimGuiObjects()
self.UnselectedFrame.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
self.SelectedFrame.BackgroundImage.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
self.SelectedFrame.BlueBarLeft.ImageTransparency = self.AnimParams.Background_CurrentTransparency
self.SelectedFrame.BlueBarRight.ImageTransparency = self.AnimParams.Background_CurrentTransparency
self.NameTagNonSelect.TextTransparency = self.AnimParams.Background_CurrentTransparency
self.NameTagNonSelect.TextStrokeTransparency = self.AnimParams.Background_CurrentTransparency
self.NameTag.TextTransparency = self.AnimParams.Text_CurrentTransparency
self.NewMessageIcon.ImageTransparency = self.AnimParams.Text_CurrentTransparency
self.WhiteTextNewMessageNotification.TextTransparency = self.AnimParams.Text_CurrentTransparency
self.NameTagSelect.TextTransparency = self.AnimParams.Text_CurrentTransparency
self.NameTag.TextStrokeTransparency = self.AnimParams.TextStroke_CurrentTransparency
self.WhiteTextNewMessageNotification.TextStrokeTransparency = self.AnimParams.TextStroke_CurrentTransparency
self.NameTagSelect.TextStrokeTransparency = self.AnimParams.TextStroke_CurrentTransparency
end
function methods:InitializeAnimParams()
self.AnimParams.Text_TargetTransparency = 0
self.AnimParams.Text_CurrentTransparency = 0
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
self.AnimParams.TextStroke_TargetTransparency = 0.75
self.AnimParams.TextStroke_CurrentTransparency = 0.75
self.AnimParams.TextStroke_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_CurrentTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
end
function methods:Update(dtScale)
self.AnimParams.Background_CurrentTransparency = CurveUtil:Expt(
self.AnimParams.Background_CurrentTransparency,
self.AnimParams.Background_TargetTransparency,
self.AnimParams.Background_NormalizedExptValue,
dtScale
)
self.AnimParams.Text_CurrentTransparency = CurveUtil:Expt(
self.AnimParams.Text_CurrentTransparency,
self.AnimParams.Text_TargetTransparency,
self.AnimParams.Text_NormalizedExptValue,
dtScale
)
self.AnimParams.TextStroke_CurrentTransparency = CurveUtil:Expt(
self.AnimParams.TextStroke_CurrentTransparency,
self.AnimParams.TextStroke_TargetTransparency,
self.AnimParams.TextStroke_NormalizedExptValue,
dtScale
)
self:AnimGuiObjects()
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new(channelName)
local obj = setmetatable({}, methods)
local BaseFrame, NameTag, NameTagNonSelect, NameTagSelect, NewMessageIcon, UnselectedFrame, SelectedFrame = CreateGuiObjects()
obj.GuiObject = BaseFrame
obj.NameTag = NameTag
obj.NameTagNonSelect = NameTagNonSelect
obj.NameTagSelect = NameTagSelect
obj.NewMessageIcon = NewMessageIcon
obj.UnselectedFrame = UnselectedFrame
obj.SelectedFrame = SelectedFrame
obj.BlueBarLeft = SelectedFrame.BlueBarLeft
obj.BlueBarRight = SelectedFrame.BlueBarRight
obj.BackgroundImage = SelectedFrame.BackgroundImage
obj.WhiteTextNewMessageNotification = obj.NewMessageIcon.TextLabel
obj.ChannelName = channelName
obj.UnreadMessageCount = 0
obj.Active = false
obj.GuiObject.Name = "Frame_" .. obj.ChannelName
if (string.len(channelName) > ChatSettings.MaxChannelNameLength) then
channelName = string.sub(channelName, 1, ChatSettings.MaxChannelNameLength - 3) .. "..."
end
--obj.NameTag.Text = channelName
obj.NameTag.Text = ""
obj.NameTagNonSelect.Text = channelName
obj.NameTagSelect.Text = channelName
obj.AnimParams = {}
obj:InitializeAnimParams()
obj:AnimGuiObjects()
obj:SetActive(false)
return obj
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/ChatScript/ChatMain/ChatBar.lua
================================================
-- // FileName: ChatBar.lua
-- // Written by: Xsitsu
-- // Description: Manages text typing and typing state.
local module = {}
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local TextService = game:GetService("TextService")
local LocalPlayer = Players.LocalPlayer
while not LocalPlayer do
Players.PlayerAdded:wait()
LocalPlayer = Players.LocalPlayer
end
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local modulesFolder = script.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
local commandModules = clientChatModules:WaitForChild("CommandModules")
local WhisperModule = require(commandModules:WaitForChild("Whisper"))
local MessageSender = require(modulesFolder:WaitForChild("MessageSender"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:CreateGuiObjects(targetParent)
self.ChatBarParentFrame = targetParent
local backgroundImagePixelOffset = 7
local textBoxPixelOffset = 5
local BaseFrame = Instance.new("Frame")
BaseFrame.Selectable = false
BaseFrame.Size = UDim2.new(1, 0, 1, 0)
BaseFrame.BackgroundTransparency = 0.6
BaseFrame.BorderSizePixel = 0
BaseFrame.BackgroundColor3 = ChatSettings.ChatBarBackGroundColor
BaseFrame.Parent = targetParent
local BoxFrame = Instance.new("Frame")
BoxFrame.Selectable = false
BoxFrame.Name = "BoxFrame"
BoxFrame.BackgroundTransparency = 0.6
BoxFrame.BorderSizePixel = 0
BoxFrame.BackgroundColor3 = ChatSettings.ChatBarBoxColor
BoxFrame.Size = UDim2.new(1, -backgroundImagePixelOffset * 2, 1, -backgroundImagePixelOffset * 2)
BoxFrame.Position = UDim2.new(0, backgroundImagePixelOffset, 0, backgroundImagePixelOffset)
BoxFrame.Parent = BaseFrame
local TextBoxHolderFrame = Instance.new("Frame")
TextBoxHolderFrame.BackgroundTransparency = 1
TextBoxHolderFrame.Size = UDim2.new(1, -textBoxPixelOffset * 2, 1, -textBoxPixelOffset * 2)
TextBoxHolderFrame.Position = UDim2.new(0, textBoxPixelOffset, 0, textBoxPixelOffset)
TextBoxHolderFrame.Parent = BoxFrame
local TextBox = Instance.new("TextBox")
TextBox.Selectable = ChatSettings.GamepadNavigationEnabled
TextBox.Name = "ChatBar"
TextBox.BackgroundTransparency = 1
TextBox.Size = UDim2.new(1, 0, 1, 0)
TextBox.Position = UDim2.new(0, 0, 0, 0)
TextBox.TextSize = ChatSettings.ChatBarTextSize
TextBox.Font = ChatSettings.ChatBarFont
TextBox.TextColor3 = ChatSettings.ChatBarTextColor
TextBox.TextTransparency = 0.4
TextBox.TextStrokeTransparency = 1
TextBox.ClearTextOnFocus = false
TextBox.TextXAlignment = Enum.TextXAlignment.Left
TextBox.TextYAlignment = Enum.TextYAlignment.Top
TextBox.TextWrapped = true
TextBox.Text = ""
TextBox.Parent = TextBoxHolderFrame
local MessageModeTextButton = Instance.new("TextButton")
MessageModeTextButton.Selectable = false
MessageModeTextButton.Name = "MessageMode"
MessageModeTextButton.BackgroundTransparency = 1
MessageModeTextButton.Position = UDim2.new(0, 0, 0, 0)
MessageModeTextButton.TextSize = ChatSettings.ChatBarTextSize
MessageModeTextButton.Font = ChatSettings.ChatBarFont
MessageModeTextButton.TextXAlignment = Enum.TextXAlignment.Left
MessageModeTextButton.TextWrapped = true
MessageModeTextButton.Text = ""
MessageModeTextButton.Size = UDim2.new(0, 0, 0, 0)
MessageModeTextButton.TextYAlignment = Enum.TextYAlignment.Center
MessageModeTextButton.TextColor3 = self:GetDefaultChannelNameColor()
MessageModeTextButton.Visible = true
MessageModeTextButton.Parent = TextBoxHolderFrame
local TextLabel = Instance.new("TextLabel")
TextLabel.Selectable = false
TextLabel.TextWrapped = true
TextLabel.BackgroundTransparency = 1
TextLabel.Size = TextBox.Size
TextLabel.Position = TextBox.Position
TextLabel.TextSize = TextBox.TextSize
TextLabel.Font = TextBox.Font
TextLabel.TextColor3 = TextBox.TextColor3
TextLabel.TextTransparency = TextBox.TextTransparency
TextLabel.TextStrokeTransparency = TextBox.TextStrokeTransparency
TextLabel.TextXAlignment = TextBox.TextXAlignment
TextLabel.TextYAlignment = TextBox.TextYAlignment
TextLabel.Text = "..."
TextLabel.Parent = TextBoxHolderFrame
self.GuiObject = BaseFrame
self.TextBox = TextBox
self.TextLabel = TextLabel
self.GuiObjects.BaseFrame = BaseFrame
self.GuiObjects.TextBoxFrame = BoxFrame
self.GuiObjects.TextBox = TextBox
self.GuiObjects.TextLabel = TextLabel
self.GuiObjects.MessageModeTextButton = MessageModeTextButton
self:AnimGuiObjects()
self:SetUpTextBoxEvents(TextBox, TextLabel, MessageModeTextButton)
if self.UserHasChatOff then
self:DoLockChatBar()
end
self.eGuiObjectsChanged:Fire()
end
-- Used to lock the chat bar when the user has chat turned off.
function methods:DoLockChatBar()
if self.TextLabel then
if LocalPlayer.UserId > 0 then
self.TextLabel.Text = ChatLocalization:Get(
"GameChat_ChatMessageValidator_SettingsError",
"To chat in game, turn on chat in your Privacy Settings."
)
else
self.TextLabel.Text = ChatLocalization:Get(
"GameChat_SwallowGuestChat_Message",
"Sign up to chat in game."
)
end
self:CalculateSize()
end
if self.TextBox then
self.TextBox.Active = false
self.TextBox.Focused:connect(function()
self.TextBox:ReleaseFocus()
end)
end
end
function methods:SetUpTextBoxEvents(TextBox, TextLabel, MessageModeTextButton)
-- Clean up events from a previous setup.
for name, conn in pairs(self.TextBoxConnections) do
conn:disconnect()
self.TextBoxConnections[name] = nil
end
--// Code for getting back into general channel from other target channel when pressing backspace.
self.TextBoxConnections.UserInputBegan = UserInputService.InputBegan:connect(function(inputObj, gpe)
if (inputObj.KeyCode == Enum.KeyCode.Backspace) then
if (self:IsFocused() and TextBox.Text == "") then
self:SetChannelTarget(ChatSettings.GeneralChannelName)
end
end
end)
self.TextBoxConnections.TextBoxChanged = TextBox.Changed:connect(function(prop)
if prop == "AbsoluteSize" then
self:CalculateSize()
return
end
if prop ~= "Text" then
return
end
self:CalculateSize()
if (string.len(TextBox.Text) > ChatSettings.MaximumMessageLength) then
TextBox.Text = string.sub(TextBox.Text, 1, ChatSettings.MaximumMessageLength)
return
end
if not self.InCustomState then
local customState = self.CommandProcessor:ProcessInProgressChatMessage(TextBox.Text, self.ChatWindow, self)
if customState then
self.InCustomState = true
self.CustomState = customState
end
else
self.CustomState:TextUpdated()
end
end)
local function UpdateOnFocusStatusChanged(isFocused)
if isFocused or TextBox.Text ~= "" then
TextLabel.Visible = false
else
TextLabel.Visible = true
end
end
self.TextBoxConnections.MessageModeClick = MessageModeTextButton.MouseButton1Click:connect(function()
if MessageModeTextButton.Text ~= "" then
self:SetChannelTarget(ChatSettings.GeneralChannelName)
end
end)
self.TextBoxConnections.TextBoxFocused = TextBox.Focused:connect(function()
if not self.UserHasChatOff then
self:CalculateSize()
UpdateOnFocusStatusChanged(true)
end
end)
self.TextBoxConnections.TextBoxFocusLost = TextBox.FocusLost:connect(function(enterPressed, inputObject)
self:CalculateSize()
if (inputObject and inputObject.KeyCode == Enum.KeyCode.Escape) then
TextBox.Text = ""
end
UpdateOnFocusStatusChanged(false)
end)
end
function methods:GetTextBox()
return self.TextBox
end
function methods:GetMessageModeTextButton()
return self.GuiObjects.MessageModeTextButton
end
-- Deprecated in favour of GetMessageModeTextButton
-- Retained for compatibility reasons.
function methods:GetMessageModeTextLabel()
return self:GetMessageModeTextButton()
end
function methods:IsFocused()
if self.UserHasChatOff then
return false
end
return self:GetTextBox():IsFocused()
end
function methods:GetVisible()
return self.GuiObject.Visible
end
function methods:CaptureFocus()
if not self.UserHasChatOff then
self:GetTextBox():CaptureFocus()
end
end
function methods:ReleaseFocus(didRelease)
self:GetTextBox():ReleaseFocus(didRelease)
end
function methods:ResetText()
self:GetTextBox().Text = ""
end
function methods:SetText(text)
self:GetTextBox().Text = text
end
function methods:GetEnabled()
return self.GuiObject.Visible
end
function methods:SetEnabled(enabled)
if self.UserHasChatOff then
-- The chat bar can not be removed if a user has chat turned off so that
-- the chat bar can display a message explaining that chat is turned off.
self.GuiObject.Visible = true
else
self.GuiObject.Visible = enabled
end
end
function methods:SetTextLabelText(text)
if not self.UserHasChatOff then
self.TextLabel.Text = text
end
end
function methods:SetTextBoxText(text)
self.TextBox.Text = text
end
function methods:GetTextBoxText()
return self.TextBox.Text
end
function methods:ResetSize()
self.TargetYSize = 0
self:TweenToTargetYSize()
end
local function measureSize(textObj)
return TextService:GetTextSize(
textObj.Text,
textObj.TextSize,
textObj.Font,
Vector2.new(textObj.AbsoluteSize.X, 10000)
)
end
function methods:CalculateSize()
if self.CalculatingSizeLock then
return
end
self.CalculatingSizeLock = true
local textSize = nil
local bounds = nil
if self:IsFocused() or self.TextBox.Text ~= "" then
textSize = self.TextBox.TextSize
bounds = measureSize(self.TextBox).Y
else
textSize = self.TextLabel.TextSize
bounds = measureSize(self.TextLabel).Y
end
local newTargetYSize = bounds - textSize
if (self.TargetYSize ~= newTargetYSize) then
self.TargetYSize = newTargetYSize
self:TweenToTargetYSize()
end
self.CalculatingSizeLock = false
end
function methods:TweenToTargetYSize()
local endSize = UDim2.new(1, 0, 1, self.TargetYSize)
local curSize = self.GuiObject.Size
local curAbsoluteSizeY = self.GuiObject.AbsoluteSize.Y
self.GuiObject.Size = endSize
local endAbsoluteSizeY = self.GuiObject.AbsoluteSize.Y
self.GuiObject.Size = curSize
local pixelDistance = math.abs(endAbsoluteSizeY - curAbsoluteSizeY)
local tweeningTime = math.min(1, (pixelDistance * (1 / self.TweenPixelsPerSecond))) -- pixelDistance * (seconds per pixels)
local success = pcall(function() self.GuiObject:TweenSize(endSize, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, tweeningTime, true) end)
if (not success) then
self.GuiObject.Size = endSize
end
end
function methods:SetTextSize(textSize)
if not self:IsInCustomState() then
if self.TextBox then
self.TextBox.TextSize = textSize
end
if self.TextLabel then
self.TextLabel.TextSize = textSize
end
end
end
function methods:GetDefaultChannelNameColor()
if ChatSettings.DefaultChannelNameColor then
return ChatSettings.DefaultChannelNameColor
end
return Color3.fromRGB(35, 76, 142)
end
function methods:SetChannelTarget(targetChannel)
local messageModeTextButton = self.GuiObjects.MessageModeTextButton
local textBox = self.TextBox
local textLabel = self.TextLabel
self.TargetChannel = targetChannel
if not self:IsInCustomState() then
if targetChannel ~= ChatSettings.GeneralChannelName then
messageModeTextButton.Size = UDim2.new(0, 1000, 1, 0)
messageModeTextButton.Text = string.format("[%s] ", targetChannel)
local channelNameColor = self:GetChannelNameColor(targetChannel)
if channelNameColor then
messageModeTextButton.TextColor3 = channelNameColor
else
messageModeTextButton.TextColor3 = self:GetDefaultChannelNameColor()
end
local xSize = messageModeTextButton.TextBounds.X
messageModeTextButton.Size = UDim2.new(0, xSize, 1, 0)
textBox.Size = UDim2.new(1, -xSize, 1, 0)
textBox.Position = UDim2.new(0, xSize, 0, 0)
textLabel.Size = UDim2.new(1, -xSize, 1, 0)
textLabel.Position = UDim2.new(0, xSize, 0, 0)
else
messageModeTextButton.Text = ""
messageModeTextButton.Size = UDim2.new(0, 0, 0, 0)
textBox.Size = UDim2.new(1, 0, 1, 0)
textBox.Position = UDim2.new(0, 0, 0, 0)
textLabel.Size = UDim2.new(1, 0, 1, 0)
textLabel.Position = UDim2.new(0, 0, 0, 0)
end
end
end
function methods:IsInCustomState()
return self.InCustomState
end
function methods:ResetCustomState()
if self.InCustomState then
self.CustomState:Destroy()
self.CustomState = nil
self.InCustomState = false
self.ChatBarParentFrame:ClearAllChildren()
self:CreateGuiObjects(self.ChatBarParentFrame)
self:SetTextLabelText(
ChatLocalization:Get(
"GameChat_ChatMain_ChatBarText",
'To chat click here or press "/" key'
)
)
end
end
function methods:EnterWhisperState(player)
self:ResetCustomState()
self:CaptureFocus()
if WhisperModule.CustomStateCreator then
self.CustomState = WhisperModule.CustomStateCreator(
player,
self.ChatWindow,
self,
ChatSettings
)
self.InCustomState = true
else
self:SetText("/w " .. player.Name)
end
end
function methods:GetCustomMessage()
if self.InCustomState then
return self.CustomState:GetMessage()
end
return nil
end
function methods:CustomStateProcessCompletedMessage(message)
if self.InCustomState then
return self.CustomState:ProcessCompletedMessage()
end
return false
end
function methods:FadeOutBackground(duration)
self.AnimParams.Background_TargetTransparency = 1
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
self:FadeOutText(duration)
end
function methods:FadeInBackground(duration)
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
self:FadeInText(duration)
end
function methods:FadeOutText(duration)
self.AnimParams.Text_TargetTransparency = 1
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeInText(duration)
self.AnimParams.Text_TargetTransparency = 0.4
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:AnimGuiObjects()
self.GuiObject.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
self.GuiObjects.TextBoxFrame.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
self.GuiObjects.TextLabel.TextTransparency = self.AnimParams.Text_CurrentTransparency
self.GuiObjects.TextBox.TextTransparency = self.AnimParams.Text_CurrentTransparency
self.GuiObjects.MessageModeTextButton.TextTransparency = self.AnimParams.Text_CurrentTransparency
end
function methods:InitializeAnimParams()
self.AnimParams.Text_TargetTransparency = 0.4
self.AnimParams.Text_CurrentTransparency = 0.4
self.AnimParams.Text_NormalizedExptValue = 1
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_CurrentTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = 1
end
function methods:Update(dtScale)
self.AnimParams.Text_CurrentTransparency = CurveUtil:Expt(
self.AnimParams.Text_CurrentTransparency,
self.AnimParams.Text_TargetTransparency,
self.AnimParams.Text_NormalizedExptValue,
dtScale
)
self.AnimParams.Background_CurrentTransparency = CurveUtil:Expt(
self.AnimParams.Background_CurrentTransparency,
self.AnimParams.Background_TargetTransparency,
self.AnimParams.Background_NormalizedExptValue,
dtScale
)
self:AnimGuiObjects()
end
function methods:SetChannelNameColor(channelName, channelNameColor)
self.ChannelNameColors[channelName] = channelNameColor
if self.GuiObjects.MessageModeTextButton.Text == channelName then
self.GuiObjects.MessageModeTextButton.TextColor3 = channelNameColor
end
end
function methods:GetChannelNameColor(channelName)
return self.ChannelNameColors[channelName]
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new(CommandProcessor, ChatWindow)
local obj = setmetatable({}, methods)
obj.GuiObject = nil
obj.ChatBarParentFrame = nil
obj.TextBox = nil
obj.TextLabel = nil
obj.GuiObjects = {}
obj.eGuiObjectsChanged = Instance.new("BindableEvent")
obj.GuiObjectsChanged = obj.eGuiObjectsChanged.Event
obj.TextBoxConnections = {}
obj.InCustomState = false
obj.CustomState = nil
obj.TargetChannel = nil
obj.CommandProcessor = CommandProcessor
obj.ChatWindow = ChatWindow
obj.TweenPixelsPerSecond = 500
obj.TargetYSize = 0
obj.AnimParams = {}
obj.CalculatingSizeLock = false
obj.ChannelNameColors = {}
obj.UserHasChatOff = false
obj:InitializeAnimParams()
ChatSettings.SettingsChanged:connect(function(setting, value)
if (setting == "ChatBarTextSize") then
obj:SetTextSize(value)
end
end)
coroutine.wrap(function()
local success, canLocalUserChat = pcall(function()
return Chat:CanUserChatAsync(LocalPlayer.UserId)
end)
local canChat = success and (RunService:IsStudio() or canLocalUserChat)
if canChat == false then
obj.UserHasChatOff = true
obj:DoLockChatBar()
end
end)()
return obj
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/ChatScript/ChatMain/ChatChannel.lua
================================================
-- // FileName: ChatChannel.lua
-- // Written by: Xsitsu
-- // Description: ChatChannel class for handling messages being added and removed from the chat channel.
local module = {}
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local modulesFolder = script.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:Destroy()
self.Destroyed = true
end
function methods:SetActive(active)
if active == self.Active then
return
end
if active == false then
self.MessageLogDisplay:Clear()
else
self.MessageLogDisplay:SetCurrentChannelName(self.Name)
for i = 1, #self.MessageLog do
self.MessageLogDisplay:AddMessage(self.MessageLog[i])
end
end
self.Active = active
end
function methods:UpdateMessageFiltered(messageData)
local searchIndex = 1
local searchTable = self.MessageLog
local messageObj = nil
while (#searchTable >= searchIndex) do
local obj = searchTable[searchIndex]
if (obj.ID == messageData.ID) then
messageObj = obj
break
end
searchIndex = searchIndex + 1
end
if messageObj then
messageObj.Message = messageData.Message
messageObj.IsFiltered = true
if self.Active then
self.MessageLogDisplay:UpdateMessageFiltered(messageObj)
end
else
-- We have not seen this filtered message before, but we should still add it to our log.
self:AddMessageToChannelByTimeStamp(messageData)
end
end
function methods:AddMessageToChannel(messageData)
table.insert(self.MessageLog, messageData)
if self.Active then
self.MessageLogDisplay:AddMessage(messageData)
end
if #self.MessageLog > ChatSettings.MessageHistoryLengthPerChannel then
self:RemoveLastMessageFromChannel()
end
end
function methods:InternalAddMessageAtTimeStamp(messageData)
for i = 1, #self.MessageLog do
if messageData.Time < self.MessageLog[i].Time then
table.insert(self.MessageLog, i, messageData)
return
end
end
table.insert(self.MessageLog, messageData)
end
function methods:AddMessagesToChannelByTimeStamp(messageLog, startIndex)
for i = startIndex, #messageLog do
self:InternalAddMessageAtTimeStamp(messageLog[i])
end
while #self.MessageLog > ChatSettings.MessageHistoryLengthPerChannel do
table.remove(self.MessageLog, 1)
end
if self.Active then
self.MessageLogDisplay:Clear()
for i = 1, #self.MessageLog do
self.MessageLogDisplay:AddMessage(self.MessageLog[i])
end
end
end
function methods:AddMessageToChannelByTimeStamp(messageData)
if #self.MessageLog >= 1 then
-- These are the fast cases to evalutate.
if self.MessageLog[1].Time > messageData.Time then
return
elseif messageData.Time >= self.MessageLog[#self.MessageLog].Time then
self:AddMessageToChannel(messageData)
return
end
for i = 1, #self.MessageLog do
if messageData.Time < self.MessageLog[i].Time then
table.insert(self.MessageLog, i, messageData)
if #self.MessageLog > ChatSettings.MessageHistoryLengthPerChannel then
self:RemoveLastMessageFromChannel()
end
if self.Active then
self.MessageLogDisplay:AddMessageAtIndex(messageData, i)
end
return
end
end
else
self:AddMessageToChannel(messageData)
end
end
function methods:RemoveLastMessageFromChannel()
table.remove(self.MessageLog, 1)
if self.Active then
self.MessageLogDisplay:RemoveLastMessage()
end
end
function methods:ClearMessageLog()
self.MessageLog = {}
if self.Active then
self.MessageLogDisplay:Clear()
end
end
function methods:RegisterChannelTab(tab)
self.ChannelTab = tab
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new(channelName, messageLogDisplay)
local obj = setmetatable({}, methods)
obj.Destroyed = false
obj.Active = false
obj.MessageLog = {}
obj.MessageLogDisplay = messageLogDisplay
obj.ChannelTab = nil
obj.Name = channelName
return obj
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/ChatScript/ChatMain/ChatWindow.lua
================================================
-- // FileName: ChatWindow.lua
-- // Written by: Xsitsu
-- // Description: Main GUI window piece. Manages ChatBar, ChannelsBar, and ChatChannels.
local module = {}
local Players = game:GetService("Players")
local Chat = game:GetService("Chat")
local LocalPlayer = Players.LocalPlayer
local PlayerGui = LocalPlayer:WaitForChild("PlayerGui")
local PHONE_SCREEN_WIDTH = 640
local TABLET_SCREEN_WIDTH = 1024
local DEVICE_PHONE = 1
local DEVICE_TABLET = 2
local DEVICE_DESKTOP = 3
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local modulesFolder = script.Parent
local moduleChatChannel = require(modulesFolder:WaitForChild("ChatChannel"))
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function getClassicChatEnabled()
if ChatSettings.ClassicChatEnabled ~= nil then
return ChatSettings.ClassicChatEnabled
end
return Players.ClassicChat
end
function getBubbleChatEnabled()
if ChatSettings.BubbleChatEnabled ~= nil then
return ChatSettings.BubbleChatEnabled
end
return Players.BubbleChat
end
function bubbleChatOnly()
return not getClassicChatEnabled() and getBubbleChatEnabled()
end
-- only merge property defined on target
function mergeProps(source, target)
if not source or not target then return end
for prop, value in pairs(source) do
if target[prop] ~= nil then
target[prop] = value
end
end
end
function methods:CreateGuiObjects(targetParent)
local userDefinedChatWindowStyle
pcall(function()
userDefinedChatWindowStyle= Chat:InvokeChatCallback(Enum.ChatCallbackType.OnCreatingChatWindow, nil)
end)
-- merge the userdefined settings with the ChatSettings
mergeProps(userDefinedChatWindowStyle, ChatSettings)
local BaseFrame = Instance.new("Frame")
BaseFrame.BackgroundTransparency = 1
BaseFrame.Active = ChatSettings.WindowDraggable
BaseFrame.Parent = targetParent
BaseFrame.AutoLocalize = false
local ChatBarParentFrame = Instance.new("Frame")
ChatBarParentFrame.Selectable = false
ChatBarParentFrame.Name = "ChatBarParentFrame"
ChatBarParentFrame.BackgroundTransparency = 1
ChatBarParentFrame.Parent = BaseFrame
local ChannelsBarParentFrame = Instance.new("Frame")
ChannelsBarParentFrame.Selectable = false
ChannelsBarParentFrame.Name = "ChannelsBarParentFrame"
ChannelsBarParentFrame.BackgroundTransparency = 1
ChannelsBarParentFrame.Position = UDim2.new(0, 0, 0, 0)
ChannelsBarParentFrame.Parent = BaseFrame
local ChatChannelParentFrame = Instance.new("Frame")
ChatChannelParentFrame.Selectable = false
ChatChannelParentFrame.Name = "ChatChannelParentFrame"
ChatChannelParentFrame.BackgroundTransparency = 1
ChatChannelParentFrame.BackgroundColor3 = ChatSettings.BackGroundColor
ChatChannelParentFrame.BackgroundTransparency = 0.6
ChatChannelParentFrame.BorderSizePixel = 0
ChatChannelParentFrame.Parent = BaseFrame
local ChatResizerFrame = Instance.new("ImageButton")
ChatResizerFrame.Selectable = false
ChatResizerFrame.Image = ""
ChatResizerFrame.BackgroundTransparency = 0.6
ChatResizerFrame.BorderSizePixel = 0
ChatResizerFrame.Visible = false
ChatResizerFrame.BackgroundColor3 = ChatSettings.BackGroundColor
ChatResizerFrame.Active = true
if bubbleChatOnly() then
ChatResizerFrame.Position = UDim2.new(1, -ChatResizerFrame.AbsoluteSize.X, 0, 0)
else
ChatResizerFrame.Position = UDim2.new(1, -ChatResizerFrame.AbsoluteSize.X, 1, -ChatResizerFrame.AbsoluteSize.Y)
end
ChatResizerFrame.Parent = BaseFrame
local ResizeIcon = Instance.new("ImageLabel")
ResizeIcon.Selectable = false
ResizeIcon.Size = UDim2.new(0.8, 0, 0.8, 0)
ResizeIcon.Position = UDim2.new(0.2, 0, 0.2, 0)
ResizeIcon.BackgroundTransparency = 1
ResizeIcon.Image = "rbxassetid://261880743"
ResizeIcon.Parent = ChatResizerFrame
local function GetScreenGuiParent()
--// Travel up parent list until you find the ScreenGui that the chat window is parented to
local screenGuiParent = BaseFrame
while (screenGuiParent and not screenGuiParent:IsA("ScreenGui")) do
screenGuiParent = screenGuiParent.Parent
end
return screenGuiParent
end
local deviceType = DEVICE_DESKTOP
local screenGuiParent = GetScreenGuiParent()
if (screenGuiParent.AbsoluteSize.X <= PHONE_SCREEN_WIDTH) then
deviceType = DEVICE_PHONE
elseif (screenGuiParent.AbsoluteSize.X <= TABLET_SCREEN_WIDTH) then
deviceType = DEVICE_TABLET
end
local checkSizeLock = false
local function doCheckSizeBounds()
if (checkSizeLock) then return end
checkSizeLock = true
if (not BaseFrame:IsDescendantOf(PlayerGui)) then return end
local screenGuiParent = GetScreenGuiParent()
local minWinSize = ChatSettings.MinimumWindowSize
local maxWinSize = ChatSettings.MaximumWindowSize
local forceMinY = ChannelsBarParentFrame.AbsoluteSize.Y + ChatBarParentFrame.AbsoluteSize.Y
local minSizePixelX = (minWinSize.X.Scale * screenGuiParent.AbsoluteSize.X) + minWinSize.X.Offset
local minSizePixelY = math.max((minWinSize.Y.Scale * screenGuiParent.AbsoluteSize.Y) + minWinSize.Y.Offset, forceMinY)
local maxSizePixelX = (maxWinSize.X.Scale * screenGuiParent.AbsoluteSize.X) + maxWinSize.X.Offset
local maxSizePixelY = (maxWinSize.Y.Scale * screenGuiParent.AbsoluteSize.Y) + maxWinSize.Y.Offset
local absSizeX = BaseFrame.AbsoluteSize.X
local absSizeY = BaseFrame.AbsoluteSize.Y
if (absSizeX < minSizePixelX) then
local offset = UDim2.new(0, minSizePixelX - absSizeX, 0, 0)
BaseFrame.Size = BaseFrame.Size + offset
elseif (absSizeX > maxSizePixelX) then
local offset = UDim2.new(0, maxSizePixelX - absSizeX, 0, 0)
BaseFrame.Size = BaseFrame.Size + offset
end
if (absSizeY < minSizePixelY) then
local offset = UDim2.new(0, 0, 0, minSizePixelY - absSizeY)
BaseFrame.Size = BaseFrame.Size + offset
elseif (absSizeY > maxSizePixelY) then
local offset = UDim2.new(0, 0, 0, maxSizePixelY - absSizeY)
BaseFrame.Size = BaseFrame.Size + offset
end
local xScale = BaseFrame.AbsoluteSize.X / screenGuiParent.AbsoluteSize.X
local yScale = BaseFrame.AbsoluteSize.Y / screenGuiParent.AbsoluteSize.Y
BaseFrame.Size = UDim2.new(xScale, 0, yScale, 0)
checkSizeLock = false
end
BaseFrame.Changed:connect(function(prop)
if (prop == "AbsoluteSize") then
doCheckSizeBounds()
end
end)
ChatResizerFrame.DragBegin:connect(function(startUdim)
BaseFrame.Draggable = false
end)
local function UpdatePositionFromDrag(atPos)
if ChatSettings.WindowDraggable == false and ChatSettings.WindowResizable == false then
return
end
local newSize = atPos - BaseFrame.AbsolutePosition + ChatResizerFrame.AbsoluteSize
BaseFrame.Size = UDim2.new(0, newSize.X, 0, newSize.Y)
if bubbleChatOnly() then
ChatResizerFrame.Position = UDim2.new(1, -ChatResizerFrame.AbsoluteSize.X, 0, 0)
else
ChatResizerFrame.Position = UDim2.new(1, -ChatResizerFrame.AbsoluteSize.X, 1, -ChatResizerFrame.AbsoluteSize.Y)
end
end
ChatResizerFrame.DragStopped:connect(function(endX, endY)
BaseFrame.Draggable = ChatSettings.WindowDraggable
--UpdatePositionFromDrag(Vector2.new(endX, endY))
end)
local resizeLock = false
ChatResizerFrame.Changed:connect(function(prop)
if (prop == "AbsolutePosition" and not BaseFrame.Draggable) then
if (resizeLock) then return end
resizeLock = true
UpdatePositionFromDrag(ChatResizerFrame.AbsolutePosition)
resizeLock = false
end
end)
local function CalculateChannelsBarPixelSize(textSize)
if (deviceType == DEVICE_PHONE) then
textSize = textSize or ChatSettings.ChatChannelsTabTextSizePhone
else
textSize = textSize or ChatSettings.ChatChannelsTabTextSize
end
local channelsBarTextYSize = textSize
local chatChannelYSize = math.max(32, channelsBarTextYSize + 8) + 2
return chatChannelYSize
end
local function CalculateChatBarPixelSize(textSize)
if (deviceType == DEVICE_PHONE) then
textSize = textSize or ChatSettings.ChatBarTextSizePhone
else
textSize = textSize or ChatSettings.ChatBarTextSize
end
local chatBarTextSizeY = textSize
local chatBarYSize = chatBarTextSizeY + (7 * 2) + (5 * 2)
return chatBarYSize
end
if bubbleChatOnly() then
ChatBarParentFrame.Position = UDim2.new(0, 0, 0, 0)
ChannelsBarParentFrame.Visible = false
ChannelsBarParentFrame.Active = false
ChatChannelParentFrame.Visible = false
ChatChannelParentFrame.Active = false
local useXScale = 0
local useXOffset = 0
local screenGuiParent = GetScreenGuiParent()
if (deviceType == DEVICE_PHONE) then
useXScale = ChatSettings.DefaultWindowSizePhone.X.Scale
useXOffset = ChatSettings.DefaultWindowSizePhone.X.Offset
elseif (deviceType == DEVICE_TABLET) then
useXScale = ChatSettings.DefaultWindowSizeTablet.X.Scale
useXOffset = ChatSettings.DefaultWindowSizeTablet.X.Offset
else
useXScale = ChatSettings.DefaultWindowSizeTablet.X.Scale
useXOffset = ChatSettings.DefaultWindowSizeTablet.X.Offset
end
local chatBarYSize = CalculateChatBarPixelSize()
BaseFrame.Size = UDim2.new(useXScale, useXOffset, 0, chatBarYSize)
BaseFrame.Position = ChatSettings.DefaultWindowPosition
else
local screenGuiParent = GetScreenGuiParent()
if (deviceType == DEVICE_PHONE) then
BaseFrame.Size = ChatSettings.DefaultWindowSizePhone
elseif (deviceType == DEVICE_TABLET) then
BaseFrame.Size = ChatSettings.DefaultWindowSizeTablet
else
BaseFrame.Size = ChatSettings.DefaultWindowSizeDesktop
end
BaseFrame.Position = ChatSettings.DefaultWindowPosition
end
if (deviceType == DEVICE_PHONE) then
ChatSettings.ChatWindowTextSize = ChatSettings.ChatWindowTextSizePhone
ChatSettings.ChatChannelsTabTextSize = ChatSettings.ChatChannelsTabTextSizePhone
ChatSettings.ChatBarTextSize = ChatSettings.ChatBarTextSizePhone
end
local function UpdateDraggable(enabled)
BaseFrame.Active = enabled
BaseFrame.Draggable = enabled
end
local function UpdateResizable(enabled)
ChatResizerFrame.Visible = enabled
ChatResizerFrame.Draggable = enabled
local frameSizeY = ChatBarParentFrame.Size.Y.Offset
if (enabled) then
ChatBarParentFrame.Size = UDim2.new(1, -frameSizeY - 2, 0, frameSizeY)
if not bubbleChatOnly() then
ChatBarParentFrame.Position = UDim2.new(0, 0, 1, -frameSizeY)
end
else
ChatBarParentFrame.Size = UDim2.new(1, 0, 0, frameSizeY)
if not bubbleChatOnly() then
ChatBarParentFrame.Position = UDim2.new(0, 0, 1, -frameSizeY)
end
end
end
local function UpdateChatChannelParentFrameSize()
local channelsBarSize = CalculateChannelsBarPixelSize()
local chatBarSize = CalculateChatBarPixelSize()
if (ChatSettings.ShowChannelsBar) then
ChatChannelParentFrame.Size = UDim2.new(1, 0, 1, -(channelsBarSize + chatBarSize + 2 + 2))
ChatChannelParentFrame.Position = UDim2.new(0, 0, 0, channelsBarSize + 2)
else
ChatChannelParentFrame.Size = UDim2.new(1, 0, 1, -(chatBarSize + 2 + 2))
ChatChannelParentFrame.Position = UDim2.new(0, 0, 0, 2)
end
end
local function UpdateChatChannelsTabTextSize(size)
local channelsBarSize = CalculateChannelsBarPixelSize(size)
ChannelsBarParentFrame.Size = UDim2.new(1, 0, 0, channelsBarSize)
UpdateChatChannelParentFrameSize()
end
local function UpdateChatBarTextSize(size)
local chatBarSize = CalculateChatBarPixelSize(size)
ChatBarParentFrame.Size = UDim2.new(1, 0, 0, chatBarSize)
if not bubbleChatOnly() then
ChatBarParentFrame.Position = UDim2.new(0, 0, 1, -chatBarSize)
end
ChatResizerFrame.Size = UDim2.new(0, chatBarSize, 0, chatBarSize)
ChatResizerFrame.Position = UDim2.new(1, -chatBarSize, 1, -chatBarSize)
UpdateChatChannelParentFrameSize()
UpdateResizable(ChatSettings.WindowResizable)
end
local function UpdateShowChannelsBar(enabled)
ChannelsBarParentFrame.Visible = enabled
UpdateChatChannelParentFrameSize()
end
UpdateChatChannelsTabTextSize(ChatSettings.ChatChannelsTabTextSize)
UpdateChatBarTextSize(ChatSettings.ChatBarTextSize)
UpdateDraggable(ChatSettings.WindowDraggable)
UpdateResizable(ChatSettings.WindowResizable)
UpdateShowChannelsBar(ChatSettings.ShowChannelsBar)
ChatSettings.SettingsChanged:connect(function(setting, value)
if (setting == "WindowDraggable") then
UpdateDraggable(value)
elseif (setting == "WindowResizable") then
UpdateResizable(value)
elseif (setting == "ChatChannelsTabTextSize") then
UpdateChatChannelsTabTextSize(value)
elseif (setting == "ChatBarTextSize") then
UpdateChatBarTextSize(value)
elseif (setting == "ShowChannelsBar") then
UpdateShowChannelsBar(value)
end
end)
self.GuiObject = BaseFrame
self.GuiObjects.BaseFrame = BaseFrame
self.GuiObjects.ChatBarParentFrame = ChatBarParentFrame
self.GuiObjects.ChannelsBarParentFrame = ChannelsBarParentFrame
self.GuiObjects.ChatChannelParentFrame = ChatChannelParentFrame
self.GuiObjects.ChatResizerFrame = ChatResizerFrame
self.GuiObjects.ResizeIcon = ResizeIcon
self:AnimGuiObjects()
end
function methods:GetChatBar()
return self.ChatBar
end
function methods:RegisterChatBar(ChatBar)
self.ChatBar = ChatBar
self.ChatBar:CreateGuiObjects(self.GuiObjects.ChatBarParentFrame)
end
function methods:RegisterChannelsBar(ChannelsBar)
self.ChannelsBar = ChannelsBar
self.ChannelsBar:CreateGuiObjects(self.GuiObjects.ChannelsBarParentFrame)
end
function methods:RegisterMessageLogDisplay(MessageLogDisplay)
self.MessageLogDisplay = MessageLogDisplay
self.MessageLogDisplay.GuiObject.Parent = self.GuiObjects.ChatChannelParentFrame
end
function methods:AddChannel(channelName)
if (self:GetChannel(channelName)) then
error("Channel '" .. channelName .. "' already exists!")
return
end
local channel = moduleChatChannel.new(channelName, self.MessageLogDisplay)
self.Channels[channelName:lower()] = channel
channel:SetActive(false)
local tab = self.ChannelsBar:AddChannelTab(channelName)
tab.NameTag.MouseButton1Click:connect(function()
self:SwitchCurrentChannel(channelName)
end)
channel:RegisterChannelTab(tab)
return channel
end
function methods:GetFirstChannel()
--// Channels are not indexed numerically, so this function is necessary.
--// Grabs and returns the first channel it happens to, or nil if none exist.
for i, v in pairs(self.Channels) do
return v
end
return nil
end
function methods:RemoveChannel(channelName)
if (not self:GetChannel(channelName)) then
error("Channel '" .. channelName .. "' does not exist!")
end
local indexName = channelName:lower()
local needsChannelSwitch = false
if (self.Channels[indexName] == self:GetCurrentChannel()) then
needsChannelSwitch = true
self:SwitchCurrentChannel(nil)
end
self.Channels[indexName]:Destroy()
self.Channels[indexName] = nil
self.ChannelsBar:RemoveChannelTab(channelName)
if (needsChannelSwitch) then
local generalChannelExists = (self:GetChannel(ChatSettings.GeneralChannelName) ~= nil)
local removingGeneralChannel = (indexName == ChatSettings.GeneralChannelName:lower())
local targetSwitchChannel = nil
if (generalChannelExists and not removingGeneralChannel) then
targetSwitchChannel = ChatSettings.GeneralChannelName
else
local firstChannel = self:GetFirstChannel()
targetSwitchChannel = (firstChannel and firstChannel.Name or nil)
end
self:SwitchCurrentChannel(targetSwitchChannel)
end
if not ChatSettings.ShowChannelsBar then
if self.ChatBar.TargetChannel == channelName then
self.ChatBar:SetChannelTarget(ChatSettings.GeneralChannelName)
end
end
end
function methods:GetChannel(channelName)
return channelName and self.Channels[channelName:lower()] or nil
end
function methods:GetTargetMessageChannel()
if (not ChatSettings.ShowChannelsBar) then
return self.ChatBar.TargetChannel
else
local curChannel = self:GetCurrentChannel()
return curChannel and curChannel.Name
end
end
function methods:GetCurrentChannel()
return self.CurrentChannel
end
function methods:SwitchCurrentChannel(channelName)
if (not ChatSettings.ShowChannelsBar) then
local targ = self:GetChannel(channelName)
if (targ) then
self.ChatBar:SetChannelTarget(targ.Name)
end
channelName = ChatSettings.GeneralChannelName
end
local cur = self:GetCurrentChannel()
local new = self:GetChannel(channelName)
if new == nil then
error(string.format("Channel '%s' does not exist.", channelName))
end
if (new ~= cur) then
if (cur) then
cur:SetActive(false)
local tab = self.ChannelsBar:GetChannelTab(cur.Name)
tab:SetActive(false)
end
if (new) then
new:SetActive(true)
local tab = self.ChannelsBar:GetChannelTab(new.Name)
tab:SetActive(true)
end
self.CurrentChannel = new
end
end
function methods:UpdateFrameVisibility()
self.GuiObject.Visible = (self.Visible and self.CoreGuiEnabled)
end
function methods:GetVisible()
return self.Visible
end
function methods:SetVisible(visible)
self.Visible = visible
self:UpdateFrameVisibility()
end
function methods:GetCoreGuiEnabled()
return self.CoreGuiEnabled
end
function methods:SetCoreGuiEnabled(enabled)
self.CoreGuiEnabled = enabled
self:UpdateFrameVisibility()
end
function methods:EnableResizable()
self.GuiObjects.ChatResizerFrame.Active = true
end
function methods:DisableResizable()
self.GuiObjects.ChatResizerFrame.Active = false
end
function methods:FadeOutBackground(duration)
self.ChannelsBar:FadeOutBackground(duration)
self.MessageLogDisplay:FadeOutBackground(duration)
self.ChatBar:FadeOutBackground(duration)
self.AnimParams.Background_TargetTransparency = 1
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeInBackground(duration)
self.ChannelsBar:FadeInBackground(duration)
self.MessageLogDisplay:FadeInBackground(duration)
self.ChatBar:FadeInBackground(duration)
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
end
function methods:FadeOutText(duration)
self.MessageLogDisplay:FadeOutText(duration)
self.ChannelsBar:FadeOutText(duration)
end
function methods:FadeInText(duration)
self.MessageLogDisplay:FadeInText(duration)
self.ChannelsBar:FadeInText(duration)
end
function methods:AnimGuiObjects()
self.GuiObjects.ChatChannelParentFrame.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
self.GuiObjects.ChatResizerFrame.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
self.GuiObjects.ResizeIcon.ImageTransparency = self.AnimParams.Background_CurrentTransparency
end
function methods:InitializeAnimParams()
self.AnimParams.Background_TargetTransparency = 0.6
self.AnimParams.Background_CurrentTransparency = 0.6
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
end
function methods:Update(dtScale)
self.ChatBar:Update(dtScale)
self.ChannelsBar:Update(dtScale)
self.MessageLogDisplay:Update(dtScale)
self.AnimParams.Background_CurrentTransparency = CurveUtil:Expt(
self.AnimParams.Background_CurrentTransparency,
self.AnimParams.Background_TargetTransparency,
self.AnimParams.Background_NormalizedExptValue,
dtScale
)
self:AnimGuiObjects()
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new()
local obj = setmetatable({}, methods)
obj.GuiObject = nil
obj.GuiObjects = {}
obj.ChatBar = nil
obj.ChannelsBar = nil
obj.MessageLogDisplay = nil
obj.Channels = {}
obj.CurrentChannel = nil
obj.Visible = true
obj.CoreGuiEnabled = true
obj.AnimParams = {}
obj:InitializeAnimParams()
return obj
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/ChatScript/ChatMain/CommandProcessor.lua
================================================
-- // FileName: ProcessCommands.lua
-- // Written by: TheGamer101
-- // Description: Module for processing commands using the client CommandModules
local module = {}
local methods = {}
methods.__index = methods
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local commandModules = clientChatModules:WaitForChild("CommandModules")
local commandUtil = require(commandModules:WaitForChild("Util"))
local modulesFolder = script.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
function methods:SetupCommandProcessors()
local commands = commandModules:GetChildren()
for i = 1, #commands do
if commands[i]:IsA("ModuleScript") then
if commands[i].Name ~= "Util" then
local commandProcessor = require(commands[i])
local processorType = commandProcessor[commandUtil.KEY_COMMAND_PROCESSOR_TYPE]
local processorFunction = commandProcessor[commandUtil.KEY_PROCESSOR_FUNCTION]
if processorType == commandUtil.IN_PROGRESS_MESSAGE_PROCESSOR then
table.insert(self.InProgressMessageProcessors, processorFunction)
elseif processorType == commandUtil.COMPLETED_MESSAGE_PROCESSOR then
table.insert(self.CompletedMessageProcessors, processorFunction)
end
end
end
end
end
function methods:ProcessCompletedChatMessage(message, ChatWindow)
for i = 1, #self.CompletedMessageProcessors do
local processedCommand = self.CompletedMessageProcessors[i](message, ChatWindow, ChatSettings)
if processedCommand then
return true
end
end
return false
end
function methods:ProcessInProgressChatMessage(message, ChatWindow, ChatBar)
for i = 1, #self.InProgressMessageProcessors do
local customState = self.InProgressMessageProcessors[i](message, ChatWindow, ChatBar, ChatSettings)
if customState then
return customState
end
end
return nil
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new()
local obj = setmetatable({}, methods)
obj.CompletedMessageProcessors = {}
obj.InProgressMessageProcessors = {}
obj:SetupCommandProcessors()
return obj
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/ChatScript/ChatMain/CurveUtil.lua
================================================
local CurveUtil = { }
local DEFAULT_THRESHOLD = 0.01
function CurveUtil:Expt(start, to, pct, dt_scale)
if math.abs(to - start) < DEFAULT_THRESHOLD then
return to
end
local y = CurveUtil:Expty(start,to,pct,dt_scale)
--rtv = start + (to - start) * timescaled_friction--
local delta = (to - start) * y
return start + delta
end
function CurveUtil:Expty(start, to, pct, dt_scale)
--y = e ^ (-a * timescale)--
local friction = 1 - pct
local a = -math.log(friction)
return 1 - math.exp(-a * dt_scale)
end
function CurveUtil:Sign(val)
if val > 0 then
return 1
elseif val < 0 then
return -1
else
return 0
end
end
function CurveUtil:BezierValForT(p0, p1, p2, p3, t)
local cp0 = (1 - t) * (1 - t) * (1 - t)
local cp1 = 3 * t * (1-t)*(1-t)
local cp2 = 3 * t * t * (1 - t)
local cp3 = t * t * t
return cp0 * p0 + cp1 * p1 + cp2 * p2 + cp3 * p3
end
CurveUtil._BezierPt2ForT = { x = 0; y = 0 }
function CurveUtil:BezierPt2ForT(
p0x, p0y,
p1x, p1y,
p2x, p2y,
p3x, p3y,
t)
CurveUtil._BezierPt2ForT.x = CurveUtil:BezierValForT(p0x,p1x,p2x,p3x,t)
CurveUtil._BezierPt2ForT.y = CurveUtil:BezierValForT(p0y,p1y,p2y,p3y,t)
return CurveUtil._BezierPt2ForT
end
function CurveUtil:YForPointOf2PtLine(pt1, pt2, x)
--(y - y1)/(x - x1) = m--
local m = (pt1.y - pt2.y) / (pt1.x - pt2.x)
--y - mx = b--
local b = pt1.y - m * pt1.x
return m * x + b
end
function CurveUtil:DeltaTimeToTimescale(s_frame_delta_time)
return s_frame_delta_time / (1.0 / 60.0)
end
function CurveUtil:SecondsToTick(sec)
return (1 / 60.0) / sec
end
function CurveUtil:ExptValueInSeconds(threshold, start, seconds)
return 1 - math.pow((threshold / start), 1 / (60.0 * seconds))
end
function CurveUtil:NormalizedDefaultExptValueInSeconds(seconds)
return self:ExptValueInSeconds(DEFAULT_THRESHOLD, 1, seconds)
end
return CurveUtil
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/ChatScript/ChatMain/MessageLabelCreator.lua
================================================
-- // FileName: MessageLabelCreator.lua
-- // Written by: Xsitsu
-- // Description: Module to handle taking text and creating stylized GUI objects for display in ChatWindow.
local OBJECT_POOL_SIZE = 50
local module = {}
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local messageCreatorModules = clientChatModules:WaitForChild("MessageCreatorModules")
local messageCreatorUtil = require(messageCreatorModules:WaitForChild("Util"))
local modulesFolder = script.Parent
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local moduleObjectPool = require(modulesFolder:WaitForChild("ObjectPool"))
local MessageSender = require(modulesFolder:WaitForChild("MessageSender"))
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
-- merge properties on both table to target
function mergeProps(source, target)
if not source then return end
for prop, value in pairs(source) do
target[prop] = value
end
end
function ReturnToObjectPoolRecursive(instance, objectPool)
local children = instance:GetChildren()
for i = 1, #children do
ReturnToObjectPoolRecursive(children[i], objectPool)
end
instance.Parent = nil
objectPool:ReturnInstance(instance)
end
function GetMessageCreators()
local typeToFunction = {}
local creators = messageCreatorModules:GetChildren()
for i = 1, #creators do
if creators[i]:IsA("ModuleScript") then
if creators[i].Name ~= "Util" then
local creator = require(creators[i])
typeToFunction[creator[messageCreatorUtil.KEY_MESSAGE_TYPE]] = creator[messageCreatorUtil.KEY_CREATOR_FUNCTION]
end
end
end
return typeToFunction
end
function methods:WrapIntoMessageObject(messageData, createdMessageObject)
local BaseFrame = createdMessageObject[messageCreatorUtil.KEY_BASE_FRAME]
local BaseMessage = nil
if messageCreatorUtil.KEY_BASE_MESSAGE then
BaseMessage = createdMessageObject[messageCreatorUtil.KEY_BASE_MESSAGE]
end
local UpdateTextFunction = createdMessageObject[messageCreatorUtil.KEY_UPDATE_TEXT_FUNC]
local GetHeightFunction = createdMessageObject[messageCreatorUtil.KEY_GET_HEIGHT]
local FadeInFunction = createdMessageObject[messageCreatorUtil.KEY_FADE_IN]
local FadeOutFunction = createdMessageObject[messageCreatorUtil.KEY_FADE_OUT]
local UpdateAnimFunction = createdMessageObject[messageCreatorUtil.KEY_UPDATE_ANIMATION]
local obj = {}
obj.ID = messageData.ID
obj.BaseFrame = BaseFrame
obj.BaseMessage = BaseMessage
obj.UpdateTextFunction = UpdateTextFunction or function() warn("NO MESSAGE RESIZE FUNCTION") end
obj.GetHeightFunction = GetHeightFunction
obj.FadeInFunction = FadeInFunction
obj.FadeOutFunction = FadeOutFunction
obj.UpdateAnimFunction = UpdateAnimFunction
obj.ObjectPool = self.ObjectPool
obj.Destroyed = false
function obj:Destroy()
ReturnToObjectPoolRecursive(self.BaseFrame, self.ObjectPool)
self.Destroyed = true
end
return obj
end
function methods:CreateMessageLabel(messageData, currentChannelName)
messageData.Channel = currentChannelName
local extraDeveloperFormatTable
pcall(function()
extraDeveloperFormatTable = Chat:InvokeChatCallback(Enum.ChatCallbackType.OnClientFormattingMessage, messageData)
end)
messageData.ExtraData = messageData.ExtraData or {}
mergeProps(extraDeveloperFormatTable, messageData.ExtraData)
local messageType = messageData.MessageType
if self.MessageCreators[messageType] then
local createdMessageObject = self.MessageCreators[messageType](messageData, currentChannelName)
if createdMessageObject then
return self:WrapIntoMessageObject(messageData, createdMessageObject)
end
elseif self.DefaultCreatorType then
local createdMessageObject = self.MessageCreators[self.DefaultCreatorType](messageData, currentChannelName)
if createdMessageObject then
return self:WrapIntoMessageObject(messageData, createdMessageObject)
end
else
error("No message creator available for message type: " ..messageType)
end
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new()
local obj = setmetatable({}, methods)
obj.ObjectPool = moduleObjectPool.new(OBJECT_POOL_SIZE)
obj.MessageCreators = GetMessageCreators()
obj.DefaultCreatorType = messageCreatorUtil.DEFAULT_MESSAGE_CREATOR
messageCreatorUtil:RegisterObjectPool(obj.ObjectPool)
return obj
end
function module:GetStringTextBounds(text, font, textSize, sizeBounds)
return messageCreatorUtil:GetStringTextBounds(text, font, textSize, sizeBounds)
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/ChatScript/ChatMain/MessageLogDisplay.lua
================================================
-- // FileName: MessageLogDisplay.lua
-- // Written by: Xsitsu, TheGamer101
-- // Description: ChatChannel window for displaying messages.
local module = {}
module.ScrollBarThickness = 4
--////////////////////////////// Include
--//////////////////////////////////////
local Chat = game:GetService("Chat")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local modulesFolder = script.Parent
local moduleMessageLabelCreator = require(modulesFolder:WaitForChild("MessageLabelCreator"))
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local FlagFixChatMessageLogPerformance = false do
local ok, value = pcall(function()
return UserSettings():IsUserFeatureEnabled("UserFixChatMessageLogPerformance")
end)
if ok then
FlagFixChatMessageLogPerformance = value
end
end
local MessageLabelCreator = moduleMessageLabelCreator.new()
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
local function CreateGuiObjects()
local BaseFrame = Instance.new("Frame")
BaseFrame.Selectable = false
BaseFrame.Size = UDim2.new(1, 0, 1, 0)
BaseFrame.BackgroundTransparency = 1
local Scroller = Instance.new("ScrollingFrame")
Scroller.Selectable = ChatSettings.GamepadNavigationEnabled
Scroller.Name = "Scroller"
Scroller.BackgroundTransparency = 1
Scroller.BorderSizePixel = 0
Scroller.Position = UDim2.new(0, 0, 0, 3)
Scroller.Size = UDim2.new(1, -4, 1, -6)
Scroller.CanvasSize = UDim2.new(0, 0, 0, 0)
Scroller.ScrollBarThickness = module.ScrollBarThickness
Scroller.Active = false
Scroller.Parent = BaseFrame
local Layout
if FlagFixChatMessageLogPerformance then
Layout = Instance.new("UIListLayout")
Layout.SortOrder = Enum.SortOrder.LayoutOrder
Layout.Parent = Scroller
end
return BaseFrame, Scroller, Layout
end
function methods:Destroy()
self.GuiObject:Destroy()
self.Destroyed = true
end
function methods:SetActive(active)
self.GuiObject.Visible = active
end
function methods:UpdateMessageFiltered(messageData)
local messageObject = nil
local searchIndex = 1
local searchTable = self.MessageObjectLog
while (#searchTable >= searchIndex) do
local obj = searchTable[searchIndex]
if obj.ID == messageData.ID then
messageObject = obj
break
end
searchIndex = searchIndex + 1
end
if messageObject then
local isScrolledDown = self:IsScrolledDown()
messageObject.UpdateTextFunction(messageData)
if FlagFixChatMessageLogPerformance then
self:PositionMessageLabelInWindow(messageObject, isScrolledDown)
else
self:ReorderAllMessages()
end
end
end
function methods:AddMessage(messageData)
self:WaitUntilParentedCorrectly()
local messageObject = MessageLabelCreator:CreateMessageLabel(messageData, self.CurrentChannelName)
if messageObject == nil then
return
end
table.insert(self.MessageObjectLog, messageObject)
self:PositionMessageLabelInWindow(messageObject)
end
function methods:AddMessageAtIndex(messageData, index)
local messageObject = MessageLabelCreator:CreateMessageLabel(messageData, self.CurrentChannelName)
if messageObject == nil then
return
end
table.insert(self.MessageObjectLog, index, messageObject)
local wasScrolledToBottom = self:IsScrolledDown()
self:ReorderAllMessages()
if wasScrolledToBottom then
self.Scroller.CanvasPosition = Vector2.new(0, math.max(0, self.Scroller.CanvasSize.Y.Offset - self.Scroller.AbsoluteSize.Y))
end
end
function methods:RemoveLastMessage()
self:WaitUntilParentedCorrectly()
local lastMessage = self.MessageObjectLog[1]
-- remove with FlagFixChatMessageLogPerformance
local posOffset = UDim2.new(0, 0, 0, lastMessage.BaseFrame.AbsoluteSize.Y)
lastMessage:Destroy()
table.remove(self.MessageObjectLog, 1)
if not FlagFixChatMessageLogPerformance then
for i, messageObject in pairs(self.MessageObjectLog) do
messageObject.BaseFrame.Position = messageObject.BaseFrame.Position - posOffset
end
self.Scroller.CanvasSize = self.Scroller.CanvasSize - posOffset
end
end
function methods:IsScrolledDown()
local yCanvasSize = self.Scroller.CanvasSize.Y.Offset
local yContainerSize = self.Scroller.AbsoluteWindowSize.Y
local yScrolledPosition = self.Scroller.CanvasPosition.Y
if FlagFixChatMessageLogPerformance then
return
yCanvasSize < yContainerSize or
yCanvasSize + yScrolledPosition >= yContainerSize - 5
else
return (yCanvasSize < yContainerSize or
yCanvasSize - yScrolledPosition <= yContainerSize + 5)
end
end
function methods:PositionMessageLabelInWindow(messageObject, isScrolledDown)
self:WaitUntilParentedCorrectly()
local baseFrame = messageObject.BaseFrame
if FlagFixChatMessageLogPerformance then
if isScrolledDown == nil then
isScrolledDown = self:IsScrolledDown()
end
baseFrame.LayoutOrder = messageObject.ID
else
baseFrame.Parent = self.Scroller
baseFrame.Position = UDim2.new(0, 0, 0, self.Scroller.CanvasSize.Y.Offset)
end
baseFrame.Size = UDim2.new(1, 0, 0, messageObject.GetHeightFunction(self.Scroller.AbsoluteSize.X))
if FlagFixChatMessageLogPerformance then
baseFrame.Parent = self.Scroller
end
if messageObject.BaseMessage then
if FlagFixChatMessageLogPerformance then
for i = 1, 10 do
if messageObject.BaseMessage.TextFits then
break
end
local trySize = self.Scroller.AbsoluteSize.X - i
baseFrame.Size = UDim2.new(1, 0, 0, messageObject.GetHeightFunction(trySize))
end
else
local trySize = self.Scroller.AbsoluteSize.X
local minTrySize = math.min(self.Scroller.AbsoluteSize.X - 10, 0)
while not messageObject.BaseMessage.TextFits do
trySize = trySize - 1
if trySize < minTrySize then
break
end
baseFrame.Size = UDim2.new(1, 0, 0, messageObject.GetHeightFunction(trySize))
end
end
end
if FlagFixChatMessageLogPerformance then
if isScrolledDown then
local scrollValue = self.Scroller.CanvasSize.Y.Offset - self.Scroller.AbsoluteSize.Y
self.Scroller.CanvasPosition = Vector2.new(0, math.max(0, scrollValue))
end
else
isScrolledDown = self:IsScrolledDown()
local add = UDim2.new(0, 0, 0, baseFrame.Size.Y.Offset)
self.Scroller.CanvasSize = self.Scroller.CanvasSize + add
if isScrolledDown then
self.Scroller.CanvasPosition = Vector2.new(0, math.max(0, self.Scroller.CanvasSize.Y.Offset - self.Scroller.AbsoluteSize.Y))
end
end
end
function methods:ReorderAllMessages()
self:WaitUntilParentedCorrectly()
--// Reordering / reparenting with a size less than 1 causes weird glitches to happen with scrolling as repositioning happens.
if self.GuiObject.AbsoluteSize.Y < 1 then return end
local oldCanvasPositon = self.Scroller.CanvasPosition
local wasScrolledDown = self:IsScrolledDown()
self.Scroller.CanvasSize = UDim2.new(0, 0, 0, 0)
for i, messageObject in pairs(self.MessageObjectLog) do
self:PositionMessageLabelInWindow(messageObject)
end
if not wasScrolledDown then
self.Scroller.CanvasPosition = oldCanvasPositon
end
end
function methods:Clear()
for i, v in pairs(self.MessageObjectLog) do
v:Destroy()
end
self.MessageObjectLog = {}
if not FlagFixChatMessageLogPerformance then
self.Scroller.CanvasSize = UDim2.new(0, 0, 0, 0)
end
end
function methods:SetCurrentChannelName(name)
self.CurrentChannelName = name
end
function methods:FadeOutBackground(duration)
--// Do nothing
end
function methods:FadeInBackground(duration)
--// Do nothing
end
function methods:FadeOutText(duration)
for i = 1, #self.MessageObjectLog do
if self.MessageObjectLog[i].FadeOutFunction then
self.MessageObjectLog[i].FadeOutFunction(duration, CurveUtil)
end
end
end
function methods:FadeInText(duration)
for i = 1, #self.MessageObjectLog do
if self.MessageObjectLog[i].FadeInFunction then
self.MessageObjectLog[i].FadeInFunction(duration, CurveUtil)
end
end
end
function methods:Update(dtScale)
for i = 1, #self.MessageObjectLog do
if self.MessageObjectLog[i].UpdateAnimFunction then
self.MessageObjectLog[i].UpdateAnimFunction(dtScale, CurveUtil)
end
end
end
--// ToDo: Move to common modules
function methods:WaitUntilParentedCorrectly()
while (not self.GuiObject:IsDescendantOf(game:GetService("Players").LocalPlayer)) do
self.GuiObject.AncestryChanged:wait()
end
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new()
local obj = setmetatable({}, methods)
obj.Destroyed = false
local BaseFrame, Scroller, Layout = CreateGuiObjects()
obj.GuiObject = BaseFrame
obj.Scroller = Scroller
obj.Layout = Layout
obj.MessageObjectLog = {}
obj.Name = "MessageLogDisplay"
obj.GuiObject.Name = "Frame_" .. obj.Name
obj.CurrentChannelName = ""
obj.GuiObject:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
spawn(function() obj:ReorderAllMessages() end)
end)
if FlagFixChatMessageLogPerformance then
local wasScrolledDown = true
obj.Layout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function()
local size = obj.Layout.AbsoluteContentSize
obj.Scroller.CanvasSize = UDim2.new(0, 0, 0, size.Y)
if wasScrolledDown then
local windowSize = obj.Scroller.AbsoluteWindowSize
obj.Scroller.CanvasPosition = Vector2.new(0, size.Y - windowSize.Y)
end
end)
obj.Scroller:GetPropertyChangedSignal("CanvasPosition"):Connect(function()
wasScrolledDown = obj:IsScrolledDown()
end)
end
return obj
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/ChatScript/ChatMain/MessageSender.lua
================================================
-- // FileName: MessageSender.lua
-- // Written by: Xsitsu
-- // Description: Module to centralize sending message functionality.
local module = {}
--////////////////////////////// Include
--//////////////////////////////////////
local modulesFolder = script.Parent
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:SendMessage(message, toChannel)
self.SayMessageRequest:FireServer(message, toChannel)
end
function methods:RegisterSayMessageFunction(func)
self.SayMessageRequest = func
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new()
local obj = setmetatable({}, methods)
obj.SayMessageRequest = nil
return obj
end
return module.new()
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/ChatScript/ChatMain/ObjectPool.lua
================================================
-- // FileName: ObjectPool.lua
-- // Written by: TheGamer101
-- // Description: An object pool class used to avoid unnecessarily instantiating Instances.
local module = {}
--////////////////////////////// Include
--//////////////////////////////////////
local modulesFolder = script.Parent
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:GetInstance(className)
if self.InstancePoolsByClass[className] == nil then
self.InstancePoolsByClass[className] = {}
end
local availableInstances = #self.InstancePoolsByClass[className]
if availableInstances > 0 then
local instance = self.InstancePoolsByClass[className][availableInstances]
table.remove(self.InstancePoolsByClass[className])
return instance
end
return Instance.new(className)
end
function methods:ReturnInstance(instance)
if self.InstancePoolsByClass[instance.ClassName] == nil then
self.InstancePoolsByClass[instance.ClassName] = {}
end
if #self.InstancePoolsByClass[instance.ClassName] < self.PoolSizePerType then
table.insert(self.InstancePoolsByClass[instance.ClassName], instance)
else
instance:Destroy()
end
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new(poolSizePerType)
local obj = setmetatable({}, methods)
obj.InstancePoolsByClass = {}
obj.Name = "ObjectPool"
obj.PoolSizePerType = poolSizePerType
return obj
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/ChatScript/ChatMain/init.lua
================================================
-- // FileName: ChatMain.lua
-- // Written by: Xsitsu
-- // Description: Main module to handle initializing chat window UI and hooking up events to individual UI pieces.
local moduleApiTable = {}
--// This section of code waits until all of the necessary RemoteEvents are found in EventFolder.
--// I have to do some weird stuff since people could potentially already have pre-existing
--// things in a folder with the same name, and they may have different class types.
--// I do the useEvents thing and set EventFolder to useEvents so I can have a pseudo folder that
--// the rest of the code can interface with and have the guarantee that the RemoteEvents they want
--// exist with their desired names.
local FILTER_MESSAGE_TIMEOUT = 60
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Chat = game:GetService("Chat")
local StarterGui = game:GetService("StarterGui")
local DefaultChatSystemChatEvents = ReplicatedStorage:WaitForChild("DefaultChatSystemChatEvents")
local EventFolder = ReplicatedStorage:WaitForChild("DefaultChatSystemChatEvents")
local clientChatModules = Chat:WaitForChild("ClientChatModules")
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
local messageCreatorModules = clientChatModules:WaitForChild("MessageCreatorModules")
local MessageCreatorUtil = require(messageCreatorModules:WaitForChild("Util"))
local ChatLocalization = nil
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local configuration = modules.load("configuration")
local numChildrenRemaining = 10 -- #waitChildren returns 0 because it's a dictionary
local waitChildren =
{
OnNewMessage = "RemoteEvent",
OnMessageDoneFiltering = "RemoteEvent",
OnNewSystemMessage = "RemoteEvent",
OnChannelJoined = "RemoteEvent",
OnChannelLeft = "RemoteEvent",
OnMuted = "RemoteEvent",
OnUnmuted = "RemoteEvent",
OnMainChannelSet = "RemoteEvent",
SayMessageRequest = "RemoteEvent",
GetInitDataRequest = "RemoteFunction",
}
-- waitChildren/EventFolder does not contain all the remote events, because the server version could be older than the client version.
-- In that case it would not create the new events.
-- These events are accessed directly from DefaultChatSystemChatEvents
local useEvents = {}
local FoundAllEventsEvent = Instance.new("BindableEvent")
function TryRemoveChildWithVerifyingIsCorrectType(child)
if (waitChildren[child.Name] and child:IsA(waitChildren[child.Name])) then
waitChildren[child.Name] = nil
useEvents[child.Name] = child
numChildrenRemaining = numChildrenRemaining - 1
end
end
for i, child in pairs(EventFolder:GetChildren()) do
TryRemoveChildWithVerifyingIsCorrectType(child)
end
if (numChildrenRemaining > 0) then
local con = EventFolder.ChildAdded:connect(function(child)
TryRemoveChildWithVerifyingIsCorrectType(child)
if (numChildrenRemaining < 1) then
FoundAllEventsEvent:Fire()
end
end)
FoundAllEventsEvent.Event:wait()
con:disconnect()
FoundAllEventsEvent:Destroy()
end
EventFolder = useEvents
--// Rest of code after waiting for correct events.
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local LocalPlayer = Players.LocalPlayer
while not LocalPlayer do
Players.ChildAdded:wait()
LocalPlayer = Players.LocalPlayer
end
local canChat = true
local ChatDisplayOrder = 6
if ChatSettings.ScreenGuiDisplayOrder ~= nil then
ChatDisplayOrder = ChatSettings.ScreenGuiDisplayOrder
end
local PlayerGui = LocalPlayer:WaitForChild("PlayerGui")
local GuiParent = Instance.new("ScreenGui")
GuiParent.Name = "Chat"
GuiParent.ResetOnSpawn = false
GuiParent.DisplayOrder = ChatDisplayOrder
GuiParent.Parent = PlayerGui
local DidFirstChannelsLoads = false
local modulesFolder = script
local moduleChatWindow = require(modulesFolder:WaitForChild("ChatWindow"))
local moduleChatBar = require(modulesFolder:WaitForChild("ChatBar"))
local moduleChannelsBar = require(modulesFolder:WaitForChild("ChannelsBar"))
local moduleMessageLabelCreator = require(modulesFolder:WaitForChild("MessageLabelCreator"))
local moduleMessageLogDisplay = require(modulesFolder:WaitForChild("MessageLogDisplay"))
local moduleChatChannel = require(modulesFolder:WaitForChild("ChatChannel"))
local moduleCommandProcessor = require(modulesFolder:WaitForChild("CommandProcessor"))
local ChatWindow = moduleChatWindow.new()
local ChannelsBar = moduleChannelsBar.new()
local MessageLogDisplay = moduleMessageLogDisplay.new()
local CommandProcessor = moduleCommandProcessor.new()
local ChatBar = moduleChatBar.new(CommandProcessor, ChatWindow)
ChatWindow:CreateGuiObjects(GuiParent)
ChatWindow:RegisterChatBar(ChatBar)
ChatWindow:RegisterChannelsBar(ChannelsBar)
ChatWindow:RegisterMessageLogDisplay(MessageLogDisplay)
MessageCreatorUtil:RegisterChatWindow(ChatWindow)
local MessageSender = require(modulesFolder:WaitForChild("MessageSender"))
MessageSender:RegisterSayMessageFunction(EventFolder.SayMessageRequest)
if (UserInputService.TouchEnabled) then
ChatBar:SetTextLabelText(ChatLocalization:Get("GameChat_ChatMain_ChatBarText",'Tap here to chat'))
else
ChatBar:SetTextLabelText(ChatLocalization:Get("GameChat_ChatMain_ChatBarTextTouch",'To chat click here or press "/" key'))
end
spawn(function()
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
local animationFps = ChatSettings.ChatAnimationFPS or 20.0
local updateWaitTime = 1.0 / animationFps
local lastTick = tick()
while true do
local currentTick = tick()
local tickDelta = currentTick - lastTick
local dtScale = CurveUtil:DeltaTimeToTimescale(tickDelta)
if dtScale ~= 0 then
ChatWindow:Update(dtScale)
end
lastTick = currentTick
wait(updateWaitTime)
end
end)
--////////////////////////////////////////////////////////////////////////////////////////////
--////////////////////////////////////////////////////////////// Code to do chat window fading
--////////////////////////////////////////////////////////////////////////////////////////////
function CheckIfPointIsInSquare(checkPos, topLeft, bottomRight)
return (topLeft.X <= checkPos.X and checkPos.X <= bottomRight.X and
topLeft.Y <= checkPos.Y and checkPos.Y <= bottomRight.Y)
end
local backgroundIsFaded = false
local textIsFaded = false
local lastTextFadeTime = 0
local lastBackgroundFadeTime = 0
local fadedChanged = Instance.new("BindableEvent")
local mouseStateChanged = Instance.new("BindableEvent")
local chatBarFocusChanged = Instance.new("BindableEvent")
function DoBackgroundFadeIn(setFadingTime)
lastBackgroundFadeTime = tick()
backgroundIsFaded = false
fadedChanged:Fire()
ChatWindow:FadeInBackground((setFadingTime or ChatSettings.ChatDefaultFadeDuration))
local currentChannelObject = ChatWindow:GetCurrentChannel()
if (currentChannelObject) then
local Scroller = MessageLogDisplay.Scroller
Scroller.ScrollingEnabled = true
Scroller.ScrollBarThickness = moduleMessageLogDisplay.ScrollBarThickness
end
end
function DoBackgroundFadeOut(setFadingTime)
lastBackgroundFadeTime = tick()
backgroundIsFaded = true
fadedChanged:Fire()
ChatWindow:FadeOutBackground((setFadingTime or ChatSettings.ChatDefaultFadeDuration))
local currentChannelObject = ChatWindow:GetCurrentChannel()
if (currentChannelObject) then
local Scroller = MessageLogDisplay.Scroller
Scroller.ScrollingEnabled = false
Scroller.ScrollBarThickness = 0
end
end
function DoTextFadeIn(setFadingTime)
lastTextFadeTime = tick()
textIsFaded = false
fadedChanged:Fire()
ChatWindow:FadeInText((setFadingTime or ChatSettings.ChatDefaultFadeDuration) * 0)
end
function DoTextFadeOut(setFadingTime)
lastTextFadeTime = tick()
textIsFaded = true
fadedChanged:Fire()
ChatWindow:FadeOutText((setFadingTime or ChatSettings.ChatDefaultFadeDuration))
end
function DoFadeInFromNewInformation()
DoTextFadeIn()
if ChatSettings.ChatShouldFadeInFromNewInformation then
DoBackgroundFadeIn()
end
end
function InstantFadeIn()
DoBackgroundFadeIn(0)
DoTextFadeIn(0)
end
function InstantFadeOut()
DoBackgroundFadeOut(0)
DoTextFadeOut(0)
end
local mouseIsInWindow = nil
function UpdateFadingForMouseState(mouseState)
mouseIsInWindow = mouseState
mouseStateChanged:Fire()
if (ChatBar:IsFocused()) then return end
if (mouseState) then
DoBackgroundFadeIn()
DoTextFadeIn()
else
DoBackgroundFadeIn()
end
end
spawn(function()
while true do
RunService.RenderStepped:wait()
while (mouseIsInWindow or ChatBar:IsFocused()) do
if (mouseIsInWindow) then
mouseStateChanged.Event:wait()
end
if (ChatBar:IsFocused()) then
chatBarFocusChanged.Event:wait()
end
end
if (not backgroundIsFaded) then
local timeDiff = tick() - lastBackgroundFadeTime
if (timeDiff > ChatSettings.ChatWindowBackgroundFadeOutTime) then
DoBackgroundFadeOut()
end
elseif (not textIsFaded) then
local timeDiff = tick() - lastTextFadeTime
if (timeDiff > ChatSettings.ChatWindowTextFadeOutTime) then
DoTextFadeOut()
end
else
fadedChanged.Event:wait()
end
end
end)
function getClassicChatEnabled()
if ChatSettings.ClassicChatEnabled ~= nil then
return ChatSettings.ClassicChatEnabled
end
return Players.ClassicChat
end
function getBubbleChatEnabled()
if ChatSettings.BubbleChatEnabled ~= nil then
return ChatSettings.BubbleChatEnabled
end
return Players.BubbleChat
end
function bubbleChatOnly()
return not getClassicChatEnabled() and getBubbleChatEnabled()
end
function UpdateMousePosition(mousePos)
if not (moduleApiTable.Visible and moduleApiTable.IsCoreGuiEnabled and (moduleApiTable.TopbarEnabled or ChatSettings.ChatOnWithTopBarOff)) then return end
if bubbleChatOnly() then
return
end
local windowPos = ChatWindow.GuiObject.AbsolutePosition
local windowSize = ChatWindow.GuiObject.AbsoluteSize
local newMouseState = CheckIfPointIsInSquare(mousePos, windowPos, windowPos + windowSize)
if (newMouseState ~= mouseIsInWindow) then
UpdateFadingForMouseState(newMouseState)
end
end
UserInputService.InputChanged:connect(function(inputObject)
if (inputObject.UserInputType == Enum.UserInputType.MouseMovement) then
local mousePos = Vector2.new(inputObject.Position.X, inputObject.Position.Y)
UpdateMousePosition(mousePos)
end
end)
UserInputService.TouchTap:connect(function(tapPos, gameProcessedEvent)
UpdateMousePosition(tapPos[1])
end)
UserInputService.TouchMoved:connect(function(inputObject, gameProcessedEvent)
local tapPos = Vector2.new(inputObject.Position.X, inputObject.Position.Y)
UpdateMousePosition(tapPos)
end)
--[[
UserInputService.Changed:connect(function(prop)
if prop == "MouseBehavior" then
if UserInputService.MouseBehavior == Enum.MouseBehavior.LockCenter then
local windowPos = ChatWindow.GuiObject.AbsolutePosition
local windowSize = ChatWindow.GuiObject.AbsoluteSize
local screenSize = GuiParent.AbsoluteSize
local centerScreenIsInWindow = CheckIfPointIsInSquare(screenSize/2, windowPos, windowPos + windowSize)
if centerScreenIsInWindow then
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end
end
end)
]]
--// Start and stop fading sequences / timers
UpdateFadingForMouseState(true)
UpdateFadingForMouseState(false)
--////////////////////////////////////////////////////////////////////////////////////////////
--///////////// Code to talk to topbar and maintain set/get core backwards compatibility stuff
--////////////////////////////////////////////////////////////////////////////////////////////
local Util = {}
do
function Util.Signal()
local sig = {}
local mSignaler = Instance.new('BindableEvent')
local mArgData = nil
local mArgDataCount = nil
function sig:fire(...)
mArgData = {...}
mArgDataCount = select('#', ...)
mSignaler:Fire()
end
function sig:connect(f)
if not f then error("connect(nil)", 2) end
return mSignaler.Event:connect(function()
f(unpack(mArgData, 1, mArgDataCount))
end)
end
function sig:wait()
mSignaler.Event:wait()
assert(mArgData, "Missing arg data, likely due to :TweenSize/Position corrupting threadrefs.")
return unpack(mArgData, 1, mArgDataCount)
end
return sig
end
end
function SetVisibility(val)
ChatWindow:SetVisible(val)
moduleApiTable.VisibilityStateChanged:fire(val)
moduleApiTable.Visible = val
if (moduleApiTable.IsCoreGuiEnabled) then
if (val) then
InstantFadeIn()
else
InstantFadeOut()
end
end
end
do
moduleApiTable.TopbarEnabled = true
moduleApiTable.MessageCount = 0
moduleApiTable.Visible = true
moduleApiTable.IsCoreGuiEnabled = true
function moduleApiTable:ToggleVisibility()
SetVisibility(not ChatWindow:GetVisible())
end
function moduleApiTable:SetVisible(visible)
if (ChatWindow:GetVisible() ~= visible) then
SetVisibility(visible)
end
end
function moduleApiTable:FocusChatBar()
ChatBar:CaptureFocus()
end
function moduleApiTable:EnterWhisperState(player)
ChatBar:EnterWhisperState(player)
end
function moduleApiTable:GetVisibility()
return ChatWindow:GetVisible()
end
function moduleApiTable:GetMessageCount()
return self.MessageCount
end
function moduleApiTable:TopbarEnabledChanged(enabled)
self.TopbarEnabled = enabled
self.CoreGuiEnabled:fire(game:GetService("StarterGui"):GetCoreGuiEnabled(Enum.CoreGuiType.Chat))
end
function moduleApiTable:IsFocused(useWasFocused)
return ChatBar:IsFocused()
end
moduleApiTable.ChatBarFocusChanged = Util.Signal()
moduleApiTable.VisibilityStateChanged = Util.Signal()
moduleApiTable.MessagesChanged = Util.Signal()
moduleApiTable.MessagePosted = Util.Signal()
moduleApiTable.CoreGuiEnabled = Util.Signal()
moduleApiTable.ChatMakeSystemMessageEvent = Util.Signal()
moduleApiTable.ChatWindowPositionEvent = Util.Signal()
moduleApiTable.ChatWindowSizeEvent = Util.Signal()
moduleApiTable.ChatBarDisabledEvent = Util.Signal()
function moduleApiTable:fChatWindowPosition()
return ChatWindow.GuiObject.Position
end
function moduleApiTable:fChatWindowSize()
return ChatWindow.GuiObject.Size
end
function moduleApiTable:fChatBarDisabled()
return not ChatBar:GetEnabled()
end
function moduleApiTable:SpecialKeyPressed(key, modifiers)
if (key == Enum.SpecialKey.ChatHotkey) then
if canChat then
DoChatBarFocus()
end
end
end
end
moduleApiTable.CoreGuiEnabled:connect(function(enabled)
moduleApiTable.IsCoreGuiEnabled = enabled
enabled = enabled and (moduleApiTable.TopbarEnabled or ChatSettings.ChatOnWithTopBarOff)
ChatWindow:SetCoreGuiEnabled(enabled)
if (not enabled) then
ChatBar:ReleaseFocus()
InstantFadeOut()
else
InstantFadeIn()
end
end)
function trimTrailingSpaces(str)
local lastSpace = #str
while lastSpace > 0 do
--- The pattern ^%s matches whitespace at the start of the string. (Starting from lastSpace)
if str:find("^%s", lastSpace) then
lastSpace = lastSpace - 1
else
break
end
end
return str:sub(1, lastSpace)
end
moduleApiTable.ChatMakeSystemMessageEvent:connect(function(valueTable)
if (valueTable["Text"] and type(valueTable["Text"]) == "string") then
while (not DidFirstChannelsLoads) do wait() end
local channel = ChatSettings.GeneralChannelName
local channelObj = ChatWindow:GetChannel(channel)
if (channelObj) then
local messageObject = {
ID = -1,
FromSpeaker = nil,
SpeakerUserId = 0,
OriginalChannel = channel,
IsFiltered = true,
MessageLength = string.len(valueTable.Text),
Message = trimTrailingSpaces(valueTable.Text),
MessageType = ChatConstants.MessageTypeSetCore,
Time = os.time(),
ExtraData = valueTable,
}
channelObj:AddMessageToChannel(messageObject)
ChannelsBar:UpdateMessagePostedInChannel(channel)
moduleApiTable.MessageCount = moduleApiTable.MessageCount + 1
moduleApiTable.MessagesChanged:fire(moduleApiTable.MessageCount)
end
end
end)
moduleApiTable.ChatBarDisabledEvent:connect(function(disabled)
if canChat then
ChatBar:SetEnabled(not disabled)
if (disabled) then
ChatBar:ReleaseFocus()
end
end
end)
moduleApiTable.ChatWindowSizeEvent:connect(function(size)
ChatWindow.GuiObject.Size = size
end)
moduleApiTable.ChatWindowPositionEvent:connect(function(position)
ChatWindow.GuiObject.Position = position
end)
--////////////////////////////////////////////////////////////////////////////////////////////
--///////////////////////////////////////////////// Code to hook client UI up to server events
--////////////////////////////////////////////////////////////////////////////////////////////
function DoChatBarFocus()
if (not ChatWindow:GetCoreGuiEnabled()) then return end
if (not ChatBar:GetEnabled()) then return end
if (not ChatBar:IsFocused() and ChatBar:GetVisible()) then
moduleApiTable:SetVisible(true)
InstantFadeIn()
ChatBar:CaptureFocus()
moduleApiTable.ChatBarFocusChanged:fire(true)
end
end
chatBarFocusChanged.Event:connect(function(focused)
moduleApiTable.ChatBarFocusChanged:fire(focused)
end)
function DoSwitchCurrentChannel(targetChannel)
if (ChatWindow:GetChannel(targetChannel)) then
ChatWindow:SwitchCurrentChannel(targetChannel)
end
end
function SendMessageToSelfInTargetChannel(message, channelName, extraData)
local channelObj = ChatWindow:GetChannel(channelName)
if (channelObj) then
local messageData =
{
ID = -1,
FromSpeaker = nil,
SpeakerUserId = 0,
OriginalChannel = channelName,
IsFiltered = true,
MessageLength = string.len(message),
Message = trimTrailingSpaces(message),
MessageType = ChatConstants.MessageTypeSystem,
Time = os.time(),
ExtraData = extraData,
}
channelObj:AddMessageToChannel(messageData)
end
end
function chatBarFocused()
if (not mouseIsInWindow) then
DoBackgroundFadeIn()
if (textIsFaded) then
DoTextFadeIn()
end
end
chatBarFocusChanged:Fire(true)
end
--// Event for making player say chat message.
function chatBarFocusLost(enterPressed, inputObject)
DoBackgroundFadeIn()
chatBarFocusChanged:Fire(false)
if (enterPressed) then
local message = ChatBar:GetTextBox().Text
if ChatBar:IsInCustomState() then
local customMessage = ChatBar:GetCustomMessage()
if customMessage then
message = customMessage
end
local messageSunk = ChatBar:CustomStateProcessCompletedMessage(message)
ChatBar:ResetCustomState()
if messageSunk then
return
end
end
message = string.sub(message, 1, ChatSettings.MaximumMessageLength)
ChatBar:GetTextBox().Text = ""
if message ~= "" then
--// Sends signal to eventually call Player:Chat() to handle C++ side legacy stuff.
moduleApiTable.MessagePosted:fire(message)
if not CommandProcessor:ProcessCompletedChatMessage(message, ChatWindow) then
if ChatSettings.DisallowedWhiteSpace then
for i = 1, #ChatSettings.DisallowedWhiteSpace do
if ChatSettings.DisallowedWhiteSpace[i] == "\t" then
message = string.gsub(message, ChatSettings.DisallowedWhiteSpace[i], " ")
else
message = string.gsub(message, ChatSettings.DisallowedWhiteSpace[i], "")
end
end
end
message = string.gsub(message, "\n", "")
message = string.gsub(message, "[ ]+", " ")
local targetChannel = ChatWindow:GetTargetMessageChannel()
if targetChannel then
MessageSender:SendMessage(message, targetChannel)
else
MessageSender:SendMessage(message, nil)
end
end
end
end
end
local ChatBarConnections = {}
function setupChatBarConnections()
for i = 1, #ChatBarConnections do
ChatBarConnections[i]:Disconnect()
end
ChatBarConnections = {}
local focusLostConnection = ChatBar:GetTextBox().FocusLost:connect(chatBarFocusLost)
table.insert(ChatBarConnections, focusLostConnection)
local focusGainedConnection = ChatBar:GetTextBox().Focused:connect(chatBarFocused)
table.insert(ChatBarConnections, focusGainedConnection)
end
setupChatBarConnections()
ChatBar.GuiObjectsChanged:connect(setupChatBarConnections)
function getEchoMessagesInGeneral()
if ChatSettings.EchoMessagesInGeneralChannel == nil then
return true
end
return ChatSettings.EchoMessagesInGeneralChannel
end
EventFolder.OnMessageDoneFiltering.OnClientEvent:connect(function(messageData)
if not ChatSettings.ShowUserOwnFilteredMessage then
if messageData.FromSpeaker == LocalPlayer.Name then
return
end
end
local channelName = messageData.OriginalChannel
local channelObj = ChatWindow:GetChannel(channelName)
if channelObj then
channelObj:UpdateMessageFiltered(messageData)
end
if getEchoMessagesInGeneral() and ChatSettings.GeneralChannelName and channelName ~= ChatSettings.GeneralChannelName then
local generalChannel = ChatWindow:GetChannel(ChatSettings.GeneralChannelName)
if generalChannel then
generalChannel:UpdateMessageFiltered(messageData)
end
end
end)
EventFolder.OnNewMessage.OnClientEvent:connect(function(messageData, channelName)
local channelObj = ChatWindow:GetChannel(channelName)
if (channelObj) then
channelObj:AddMessageToChannel(messageData)
if (messageData.FromSpeaker ~= LocalPlayer.Name) then
ChannelsBar:UpdateMessagePostedInChannel(channelName)
end
if getEchoMessagesInGeneral() and ChatSettings.GeneralChannelName and channelName ~= ChatSettings.GeneralChannelName then
local generalChannel = ChatWindow:GetChannel(ChatSettings.GeneralChannelName)
if generalChannel then
generalChannel:AddMessageToChannel(messageData)
end
end
moduleApiTable.MessageCount = moduleApiTable.MessageCount + 1
moduleApiTable.MessagesChanged:fire(moduleApiTable.MessageCount)
DoFadeInFromNewInformation()
end
end)
EventFolder.OnNewSystemMessage.OnClientEvent:connect(function(messageData, channelName)
channelName = channelName or "System"
local channelObj = ChatWindow:GetChannel(channelName)
if (channelObj) then
channelObj:AddMessageToChannel(messageData)
ChannelsBar:UpdateMessagePostedInChannel(channelName)
moduleApiTable.MessageCount = moduleApiTable.MessageCount + 1
moduleApiTable.MessagesChanged:fire(moduleApiTable.MessageCount)
DoFadeInFromNewInformation()
if getEchoMessagesInGeneral() and ChatSettings.GeneralChannelName and channelName ~= ChatSettings.GeneralChannelName then
local generalChannel = ChatWindow:GetChannel(ChatSettings.GeneralChannelName)
if generalChannel then
generalChannel:AddMessageToChannel(messageData)
end
end
end
end)
function HandleChannelJoined(channel, welcomeMessage, messageLog, channelNameColor, addHistoryToGeneralChannel,
addWelcomeMessageToGeneralChannel)
if ChatWindow:GetChannel(channel) then
--- If the channel has already been added, remove it first.
ChatWindow:RemoveChannel(channel)
end
if (channel == ChatSettings.GeneralChannelName) then
DidFirstChannelsLoads = true
end
if channelNameColor then
ChatBar:SetChannelNameColor(channel, channelNameColor)
end
local channelObj = ChatWindow:AddChannel(channel)
if (channelObj) then
if (channel == ChatSettings.GeneralChannelName) then
DoSwitchCurrentChannel(channel)
end
if (messageLog) then
local startIndex = 1
if #messageLog > ChatSettings.MessageHistoryLengthPerChannel then
startIndex = #messageLog - ChatSettings.MessageHistoryLengthPerChannel
end
for i = startIndex, #messageLog do
channelObj:AddMessageToChannel(messageLog[i])
end
if getEchoMessagesInGeneral() and addHistoryToGeneralChannel then
if ChatSettings.GeneralChannelName and channel ~= ChatSettings.GeneralChannelName then
local generalChannel = ChatWindow:GetChannel(ChatSettings.GeneralChannelName)
if generalChannel then
generalChannel:AddMessagesToChannelByTimeStamp(messageLog, startIndex)
end
end
end
end
if (welcomeMessage ~= "") then
local welcomeMessageObject = {
ID = -1,
FromSpeaker = nil,
SpeakerUserId = 0,
OriginalChannel = channel,
IsFiltered = true,
MessageLength = string.len(welcomeMessage),
Message = trimTrailingSpaces(welcomeMessage),
MessageType = ChatConstants.MessageTypeWelcome,
Time = os.time(),
ExtraData = nil,
}
channelObj:AddMessageToChannel(welcomeMessageObject)
if getEchoMessagesInGeneral() and addWelcomeMessageToGeneralChannel and not ChatSettings.ShowChannelsBar then
if channel ~= ChatSettings.GeneralChannelName then
local generalChannel = ChatWindow:GetChannel(ChatSettings.GeneralChannelName)
if generalChannel then
generalChannel:AddMessageToChannel(welcomeMessageObject)
end
end
end
end
DoFadeInFromNewInformation()
end
end
EventFolder.OnChannelJoined.OnClientEvent:connect(function(channel, welcomeMessage, messageLog, channelNameColor)
HandleChannelJoined(channel, welcomeMessage, messageLog, channelNameColor, false, true)
end)
EventFolder.OnChannelLeft.OnClientEvent:connect(function(channel)
ChatWindow:RemoveChannel(channel)
DoFadeInFromNewInformation()
end)
EventFolder.OnMuted.OnClientEvent:connect(function(channel)
--// Do something eventually maybe?
--// This used to take away the chat bar in channels the player was muted in.
--// We found out this behavior was inconvenient for doing chat commands though.
end)
EventFolder.OnUnmuted.OnClientEvent:connect(function(channel)
--// Same as above.
end)
EventFolder.OnMainChannelSet.OnClientEvent:connect(function(channel)
DoSwitchCurrentChannel(channel)
end)
coroutine.wrap(function()
-- ChannelNameColorUpdated may not exist if the client version is older than the server version.
local ChannelNameColorUpdated = DefaultChatSystemChatEvents:WaitForChild("ChannelNameColorUpdated", 5)
if ChannelNameColorUpdated then
ChannelNameColorUpdated.OnClientEvent:connect(function(channelName, channelNameColor)
ChatBar:SetChannelNameColor(channelName, channelNameColor)
end)
end
end)()
--- Interaction with SetCore Player events.
local PlayerBlockedEvent = nil
local PlayerMutedEvent = nil
local PlayerUnBlockedEvent = nil
local PlayerUnMutedEvent = nil
-- This is pcalled because the SetCore methods may not be released yet.
pcall(function()
PlayerBlockedEvent = StarterGui:GetCore("PlayerBlockedEvent")
PlayerMutedEvent = StarterGui:GetCore("PlayerMutedEvent")
PlayerUnBlockedEvent = StarterGui:GetCore("PlayerUnblockedEvent")
PlayerUnMutedEvent = StarterGui:GetCore("PlayerUnmutedEvent")
end)
function SendSystemMessageToSelf(message)
local currentChannel = ChatWindow:GetCurrentChannel()
if currentChannel then
local messageData =
{
ID = -1,
FromSpeaker = nil,
SpeakerUserId = 0,
OriginalChannel = currentChannel.Name,
IsFiltered = true,
MessageLength = string.len(message),
Message = trimTrailingSpaces(message),
MessageType = ChatConstants.MessageTypeSystem,
Time = os.time(),
ExtraData = nil,
}
currentChannel:AddMessageToChannel(messageData)
end
end
function MutePlayer(player)
local mutePlayerRequest = DefaultChatSystemChatEvents:FindFirstChild("MutePlayerRequest")
if mutePlayerRequest then
return mutePlayerRequest:InvokeServer(player.Name)
end
return false
end
if PlayerBlockedEvent then
PlayerBlockedEvent.Event:connect(function(player)
if MutePlayer(player) then
SendSystemMessageToSelf(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatMain_SpeakerHasBeenBlocked",
string.format("Speaker '%s' has been blocked.", player.Name)
),
"{RBX_NAME}",player.Name
)
)
end
end)
end
if PlayerMutedEvent then
PlayerMutedEvent.Event:connect(function(player)
if MutePlayer(player) then
SendSystemMessageToSelf(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatMain_SpeakerHasBeenMuted",
string.format("Speaker '%s' has been muted.", player.Name)
),
"{RBX_NAME}", player.Name
)
)
end
end)
end
function UnmutePlayer(player)
local unmutePlayerRequest = DefaultChatSystemChatEvents:FindFirstChild("UnMutePlayerRequest")
if unmutePlayerRequest then
return unmutePlayerRequest:InvokeServer(player.Name)
end
return false
end
if PlayerUnBlockedEvent then
PlayerUnBlockedEvent.Event:connect(function(player)
if UnmutePlayer(player) then
SendSystemMessageToSelf(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatMain_SpeakerHasBeenUnBlocked",
string.format("Speaker '%s' has been unblocked.", player.Name)
),
"{RBX_NAME}",player.Name
)
)
end
end)
end
if PlayerUnMutedEvent then
PlayerUnMutedEvent.Event:connect(function(player)
if UnmutePlayer(player) then
SendSystemMessageToSelf(
string.gsub(
ChatLocalization:Get(
"GameChat_ChatMain_SpeakerHasBeenUnMuted",
string.format("Speaker '%s' has been unmuted.", player.Name)
),
"{RBX_NAME}",player.Name
)
)
end
end)
end
-- Get a list of blocked users from the corescripts.
-- Spawned because this method can yeild.
spawn(function()
-- Pcalled because this method is not released on all platforms yet.
if LocalPlayer.UserId > 0 then
pcall(function()
local blockedUserIds = StarterGui:GetCore("GetBlockedUserIds")
if #blockedUserIds > 0 then
local setInitalBlockedUserIds = DefaultChatSystemChatEvents:FindFirstChild("SetBlockedUserIdsRequest")
if setInitalBlockedUserIds then
setInitalBlockedUserIds:FireServer(blockedUserIds)
end
end
end)
end
end)
spawn(function()
local success, canLocalUserChat = pcall(function()
return Chat:CanUserChatAsync(LocalPlayer.UserId)
end)
if success then
canChat = RunService:IsStudio() or canLocalUserChat
end
end)
local initData = EventFolder.GetInitDataRequest:InvokeServer()
-- Handle joining general channel first.
for i, channelData in pairs(initData.Channels) do
if channelData[1] == ChatSettings.GeneralChannelName then
HandleChannelJoined(channelData[1], channelData[2], channelData[3], channelData[4], true, false)
end
end
for i, channelData in pairs(initData.Channels) do
if channelData[1] ~= ChatSettings.GeneralChannelName then
HandleChannelJoined(channelData[1], channelData[2], channelData[3], channelData[4], true, false)
end
end
return moduleApiTable
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/ChatScript/init.client.lua
================================================
-- // FileName: ChatScript.lua
-- // Written by: Xsitsu
-- // Description: Hooks main chat module up to Topbar in corescripts.
local StarterGui = game:GetService("StarterGui")
local GuiService = game:GetService("GuiService")
local ChatService = game:GetService("Chat")
local MAX_COREGUI_CONNECTION_ATTEMPTS = 10
local ClientChatModules = ChatService:WaitForChild("ClientChatModules")
local ChatSettings = require(ClientChatModules:WaitForChild("ChatSettings"))
local function DoEverything()
local Chat = require(script:WaitForChild("ChatMain"))
local containerTable = {}
containerTable.ChatWindow = {}
containerTable.SetCore = {}
containerTable.GetCore = {}
containerTable.ChatWindow.ChatTypes = {}
containerTable.ChatWindow.ChatTypes.BubbleChatEnabled = ChatSettings.BubbleChatEnabled
containerTable.ChatWindow.ChatTypes.ClassicChatEnabled = ChatSettings.ClassicChatEnabled
--// Connection functions
local function ConnectEvent(name)
local event = Instance.new("BindableEvent")
event.Name = name
containerTable.ChatWindow[name] = event
event.Event:connect(function(...) Chat[name](Chat, ...) end)
end
local function ConnectFunction(name)
local func = Instance.new("BindableFunction")
func.Name = name
containerTable.ChatWindow[name] = func
func.OnInvoke = function(...) return Chat[name](Chat, ...) end
end
local function ReverseConnectEvent(name)
local event = Instance.new("BindableEvent")
event.Name = name
containerTable.ChatWindow[name] = event
Chat[name]:connect(function(...) event:Fire(...) end)
end
local function ConnectSignal(name)
local event = Instance.new("BindableEvent")
event.Name = name
containerTable.ChatWindow[name] = event
event.Event:connect(function(...) Chat[name]:fire(...) end)
end
local function ConnectSetCore(name)
local event = Instance.new("BindableEvent")
event.Name = name
containerTable.SetCore[name] = event
event.Event:connect(function(...) Chat[name.."Event"]:fire(...) end)
end
local function ConnectGetCore(name)
local func = Instance.new("BindableFunction")
func.Name = name
containerTable.GetCore[name] = func
func.OnInvoke = function(...) return Chat["f"..name](...) end
end
--// Do connections
ConnectEvent("ToggleVisibility")
ConnectEvent("SetVisible")
ConnectEvent("FocusChatBar")
ConnectEvent("EnterWhisperState")
ConnectFunction("GetVisibility")
ConnectFunction("GetMessageCount")
ConnectEvent("TopbarEnabledChanged")
ConnectFunction("IsFocused")
ReverseConnectEvent("ChatBarFocusChanged")
ReverseConnectEvent("VisibilityStateChanged")
ReverseConnectEvent("MessagesChanged")
ReverseConnectEvent("MessagePosted")
ConnectSignal("CoreGuiEnabled")
ConnectSetCore("ChatMakeSystemMessage")
ConnectSetCore("ChatWindowPosition")
ConnectSetCore("ChatWindowSize")
ConnectGetCore("ChatWindowPosition")
ConnectGetCore("ChatWindowSize")
ConnectSetCore("ChatBarDisabled")
ConnectGetCore("ChatBarDisabled")
ConnectEvent("SpecialKeyPressed")
SetCoreGuiChatConnections(containerTable)
end
function SetCoreGuiChatConnections(containerTable)
local tries = 0
while tries < MAX_COREGUI_CONNECTION_ATTEMPTS do
tries = tries + 1
local success, ret = pcall(function() StarterGui:SetCore("CoreGuiChatConnections", containerTable) end)
if success then
break
end
if not success and tries == MAX_COREGUI_CONNECTION_ATTEMPTS then
error("Error calling SetCore CoreGuiChatConnections: " .. ret)
end
wait()
end
end
function checkBothChatTypesDisabled()
if ChatSettings.BubbleChatEnabled ~= nil then
if ChatSettings.ClassicChatEnabled ~= nil then
return not (ChatSettings.BubbleChatEnabled or ChatSettings.ClassicChatEnabled)
end
end
return false
end
if (not GuiService:IsTenFootInterface()) and (not game:GetService('UserInputService').VREnabled) then
if not checkBothChatTypesDisabled() then
DoEverything()
else
local containerTable = {}
containerTable.ChatWindow = {}
containerTable.ChatWindow.ChatTypes = {}
containerTable.ChatWindow.ChatTypes.BubbleChatEnabled = false
containerTable.ChatWindow.ChatTypes.ClassicChatEnabled = false
SetCoreGuiChatConnections(containerTable)
end
end
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/PlayerScriptsLoader.client.lua
================================================
-- this exists just to block the Roblox-injected script of the same name
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/animations/axeAnimations.lua
================================================
return {
strike1 = {
animationId = "rbxassetid://5346949146";
looped = false;
priority = Enum.AnimationPriority.Action;
};
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/animations/bowAnimations.lua
================================================
-- animations on character
return {
firing_bow = {
-- animationId_2 = "rbxassetid://2726824751";
animationId = "rbxassetid://2726792530";
looped = false;
-- priority = Enum.AnimationPriority.Action;
priority = Enum.AnimationPriority.Action;
};
stretching_bow = {
animationId = "rbxassetid://2728300592";
-- animationId_2 = "rbxassetid://2766719385";
looped = false;
priority = Enum.AnimationPriority.Action;
-- priority_2 = Enum.AnimationPriority.Core;
};
stretching_bow_stance = {
animationId = "rbxassetid://4719315890",
looped = false,
priority = Enum.AnimationPriority.Action,
},
firing_bow_stance = {
animationId = "rbxassetid://4719339750",
looped = false,
priority = Enum.AnimationPriority.Action,
},
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/animations/bowToolAnimations_noChar.lua
================================================
return {
fire = {
animationId = "http://www.roblox.com/asset/?id=2726670196";
priority = Enum.AnimationPriority.Action;
looped = false;
};
stretch = {
animationId = "http://www.roblox.com/asset/?id=2726680362";
priority = Enum.AnimationPriority.Action;
looped = false;
};
stretchHold = {
animationId = "http://www.roblox.com/asset/?id=2726682464";
priority = Enum.AnimationPriority.Action;
looped = true;
};
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/animations/daggerAnimations.lua
================================================
return {
strike1 = {
animationId = "rbxassetid://2351304598";
looped = false;
priority = Enum.AnimationPriority.Action;
};
strike2 = {
animationId = "rbxassetid://2351309121";
looped = false;
priority = Enum.AnimationPriority.Action;
};
strike3 = {
animationId = "rbxassetid://3391395031";
looped = false;
priority = Enum.AnimationPriority.Action;
};
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/animations/dualAnimations.lua
================================================
return {
strike1 = {
animationId = "rbxassetid://4089519973";
looped = false;
priority = Enum.AnimationPriority.Action;
};
strike2 = {
animationId = "rbxassetid://4089525456";
looped = false;
priority = Enum.AnimationPriority.Action;
};
strike3 = {
animationId = "rbxassetid://4089535231";
looped = false;
priority = Enum.AnimationPriority.Action;
};
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/animations/emoteAnimations.lua
================================================
return {
idling_kicking = {
animationId = "http://www.roblox.com/asset/?id=2510465751";
priority = Enum.AnimationPriority.Action;
looped = false;
};
interaction_greeting = {
animationId = "rbxassetid://2267540472";
priority = Enum.AnimationPriority.Action;
looped = false;
};
consume_consumable = {
animationId = "rbxassetid://2535303304";
priority = Enum.AnimationPriority.Action;
looped = false;
};
consume_loop = {
animationId = "rbxassetid://3583773947";
priority = Enum.AnimationPriority.Action;
looped = true;
};
block = {
animationId = "rbxassetid://2860393735";
priority = Enum.AnimationPriority.Action;
looped = false;
};
dance = {
animationId = "rbxassetid://3518747957";
priority = Enum.AnimationPriority.Action;
looped = true;
};
beg = {
animationId = "rbxassetid://3869526669";
priority = Enum.AnimationPriority.Action;
looped = false;
};
beg_hold = {
animationId = "rbxassetid://3869527720";
priority = Enum.AnimationPriority.Action;
looped = true;
};
snap_dance = {
animationId = "rbxassetid://3453659601";
priority = Enum.AnimationPriority.Action;
looped = true;
};
happy_dance = {
animationId = "rbxassetid://3518766746";
priority = Enum.AnimationPriority.Action;
looped = true;
};
dance2 = {
animationId = "rbxassetid://3518775810";
priority = Enum.AnimationPriority.Action;
looped = true;
};
dance3 = {
animationId = "rbxassetid://3518775005";
priority = Enum.AnimationPriority.Action;
looped = true;
};
["oh yea"] = {
animationId = "rbxassetid://3909142929";
priority = Enum.AnimationPriority.Action;
looped = true;
};
hype = {
animationId = "rbxassetid://3909136684";
priority = Enum.AnimationPriority.Action;
looped = true;
};
flex = {
animationId = "rbxassetid://3518780485";
priority = Enum.AnimationPriority.Action;
looped = false;
};
guitar = {
animationId = "rbxassetid://3518798639";
priority = Enum.AnimationPriority.Action;
looped = false;
};
panic = {
animationId = "rbxassetid://3909113110";
priority = Enum.AnimationPriority.Action;
looped = true;
};
handstand = {
animationId = "rbxassetid://3518781118";
priority = Enum.AnimationPriority.Action;
looped = true;
};
-- jumping jacks
jumps = {
animationId = "rbxassetid://3518781913";
priority = Enum.AnimationPriority.Action;
looped = true;
};
tadaa = {
animationId = "rbxassetid://3518782478";
priority = Enum.AnimationPriority.Action;
looped = false;
};
cheer = {
animationId = "rbxassetid://3909256590";
priority = Enum.AnimationPriority.Action;
looped = false;
};
sit = {
animationId = "rbxassetid://3559077811";
priority = Enum.AnimationPriority.Action;
looped = true;
};
pushups = {
animationId = "rbxassetid://3299614643";
priority = Enum.AnimationPriority.Action;
looped = true;
};
wave = {
animationId = "rbxassetid://2267540472";
priority = Enum.AnimationPriority.Action;
looped = false;
};
point = {
animationId = "rbxassetid://3453662693";
priority = Enum.AnimationPriority.Action;
looped = false;
};
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/animations/fishing-rodAnimations.lua
================================================
return {
["cast-line"] = {
animationId = "rbxassetid://2532986567";
looped = false;
priority = Enum.AnimationPriority.Action;
};
["reel-line"] = {
animationId = "rbxassetid://2532988892";
looped = false;
priority = Enum.AnimationPriority.Action;
};
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/animations/greatswordAnimations.lua
================================================
return {
strike1 = {
animationId = "rbxassetid://4882568220";
looped = false;
priority = Enum.AnimationPriority.Action;
};
strike2 = {
animationId = "rbxassetid://4882574024";
looped = false;
priority = Enum.AnimationPriority.Action;
};
strike3 = {
animationId = "rbxassetid://4882577637";
looped = false;
priority = Enum.AnimationPriority.Action;
}
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/animations/init.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/animations/movementAnimations.lua
================================================
return {
jumping = {
animationId = "http://www.roblox.com/asset/?id=2035198843";
looped = false;
speed = 1.5;
priority = Enum.AnimationPriority.Movement;
};
gettingUp = {
animationId = "http://www.roblox.com/asset/?id=3197470516";
looped = false;
priority = Enum.AnimationPriority.Movement;
};
gettingUp1 = {
animationId = "http://www.roblox.com/asset/?id=3197470516";
looped = false;
priority = Enum.AnimationPriority.Movement;
};
gettingUp2 = {
animationId = "http://www.roblox.com/asset/?id=3197470516";
looped = false;
priority = Enum.AnimationPriority.Movement;
};
double_jumping = {
animationId = "http://www.roblox.com/asset/?id=2410331166";
looped = false;
speed = 1.5;
priority = Enum.AnimationPriority.Movement;
};
walking = {
animationId = "http://www.roblox.com/asset/?id=2036632924";
looped = true;
speed = 1.25;
};
walking_greatsword = {
animationId = "rbxassetid://4875881518";
looped = true;
speed = 1.25;
priority = Enum.AnimationPriority.Action,
};
sprinting_greatsword = {
animationId_2 = "http://www.roblox.com/asset/?id=3875293975";
animationId = "http://www.roblox.com/asset/?id=4087202408";
looped = true;
};
sprinting_swordAndShield = {
animationId = "rbxassetid://4272393904",
looped = true
},
walking_sword_dual = {
animationId = "http://www.roblox.com/asset/?id=3996219245";
looped = true;
};
swimming = {
animationId = "http://www.roblox.com/asset/?id=3277470794";
looped = true;
};
falling = {
animationId = "http://www.roblox.com/asset/?id=2475304222";
looped = true;
};
walking_exhausted = {
animationId = "http://www.roblox.com/asset/?id=2407527752";
looped = true;
speed = 1.25;
};
sprinting = {
animationId = "http://www.roblox.com/asset/?id=2041173462";
looped = true;
};
sprinting_sword_dual = {
animationId = "http://www.roblox.com/asset/?id=3996229378";
looped = true;
};
sprintingWithWeapon = {
animationId = "http://www.roblox.com/asset/?id=2036643580";
looped = true;
};
idling = {
animationId = "http://www.roblox.com/asset/?id=2066794263";
looped = true;
hasVariants = true;
priority = Enum.AnimationPriority.Idle;
};
idling_exhausted = {
animationId = "http://www.roblox.com/asset/?id=2407526978";
animationId_2 = "http://www.roblox.com/asset/?id=2066794263";
looped = true;
priority = Enum.AnimationPriority.Idle;
};
idling_greatsword = {
animationId = "rbxassetid://4882493831";
looped = true;
priority = Enum.AnimationPriority.Idle;
};
idling_dagger = {
animationId = "http://www.roblox.com/asset/?id=2351348658";
looped = true;
priority = Enum.AnimationPriority.Idle;
};
idling_bow = {
animationId = "http://www.roblox.com/asset/?id=2715891793";
animationId_2 = "http://www.roblox.com/asset/?id=2715924622";
looped = true;
priority = Enum.AnimationPriority.Idle;
};
idling_staff = {
animationId = "http://www.roblox.com/asset/?id=3244807491";
looped = true;
priority = Enum.AnimationPriority.Idle;
};
idling_sword_dual = {
animationId = "http://www.roblox.com/asset/?id=3996111126";
looped = true;
priority = Enum.AnimationPriority.Idle;
};
warrior_idling = {
animationId = "http://www.roblox.com/asset/?id=3244862244";
looped = true;
priority = Enum.AnimationPriority.Idle;
};
idling_bow_streched = {
animationId = "http://www.roblox.com/asset/?id=2726808172";
animationId_2 = "http://www.roblox.com/asset/?id=2726809979";
looped = true;
priority = Enum.AnimationPriority.Idle;
};
walking_bow_streched = {
animationId = "http://www.roblox.com/asset/?id=2726815878";
animationId_2 = "http://www.roblox.com/asset/?id=2726809979";
looped = true;
};
dead = {
animationId = "http://www.roblox.com/asset/?id=2071060180";
looped = false;
};
dead_loop = {
animationId = "http://www.roblox.com/asset/?id=2074091647";
looped = true;
};
sitting = {
animationId = "http://www.roblox.com/asset/?id=2274649431";
looped = true;
};
fishing = {
animationId = "http://www.roblox.com/asset/?id=2532987375";
looped = true;
};
consume_consumable = {
animationId = "rbxassetid://2535303304";
priority = Enum.AnimationPriority.Action;
looped = false;
};
consume_loop = {
animationId = "rbxassetid://3583773947";
priority = Enum.AnimationPriority.Action;
looped = true;
};
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/animations/pickaxeAnimations.lua
================================================
return {
strike1 = {
animationId = "rbxassetid://5347066717";
looped = false;
priority = Enum.AnimationPriority.Action;
};
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/animations/staffAnimations.lua
================================================
return {
strike1 = {
animationId = "rbxassetid://2065686536";
looped = false;
priority = Enum.AnimationPriority.Action;
};
strike2 = {
animationId = "rbxassetid://2035777674";
looped = false;
priority = Enum.AnimationPriority.Action;
};
strike3 = {
animationId = "rbxassetid://3391395031";
looped = false;
priority = Enum.AnimationPriority.Action;
};
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/animations/swordAndShieldAnimations.lua
================================================
return {
strike1 = {
animationId = "rbxassetid://2065686536";
looped = false;
priority = Enum.AnimationPriority.Action;
};
strike2 = {
animationId = "rbxassetid://2035777674";
looped = false;
priority = Enum.AnimationPriority.Action;
};
strike3 = {
animationId = "rbxassetid://4272381396";
looped = false;
priority = Enum.AnimationPriority.Action;
};
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/animations/swordAnimations.lua
================================================
return {
strike1 = {
animationId = "rbxassetid://2065686536";
looped = false;
priority = Enum.AnimationPriority.Action;
};
strike2 = {
animationId = "rbxassetid://2035777674";
looped = false;
priority = Enum.AnimationPriority.Action;
};
strike3 = {
animationId = "rbxassetid://3391395031";
looped = false;
priority = Enum.AnimationPriority.Action;
};
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/attackableScript.lua
================================================
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
local tween = modules.load("tween")
local effects = modules.load("effects")
local utilities = modules.load("utilities")
local model = script.Parent.Parent
local chestRootModel = model.Parent
local originalCFrame = model:GetPrimaryPartCFrame()
local attackable = {}
local health = 5
local isOpen = false
local heartbeatConnection = nil
local activeTween = nil
local animControl = script.Parent.Parent:FindFirstChild("AnimationController")
local glow = script.Parent.Parent:FindFirstChild("Glow")
local loop = animControl:LoadAnimation(script.Parent.Parent.chestOpenLoop)
local openAnim = animControl:LoadAnimation(script.Parent.Parent.chestOpen)
openAnim.Looped = false
openAnim.Priority = Enum.AnimationPriority.Action
loop.Looped = true
loop.Priority = Enum.AnimationPriority.Core
local function openChest()
local rewards, status = network:invoke("openTreasureChest_client", chestRootModel)
if rewards then
isOpen = true
elseif status then
network:fire("alert", {text = status}, 1)
end
end
local function doHealthReduction()
health = math.max(health - 1, 0)
if health <= 0 and not isOpen then
openChest()
end
end
local function doShake()
local primaryPart = model.PrimaryPart
local rootCFrame = originalCFrame * CFrame.new(0, -primaryPart.Size.Y/2, 0) * CFrame.Angles(0, math.pi * 2 * math.random(), 0)
local offset = rootCFrame:ToObjectSpace(originalCFrame)
local dummyPart = Instance.new("Part")
dummyPart.CFrame = rootCFrame
local easeInTime = 0.2
local easeOutTime = 1
if heartbeatConnection then
heartbeatConnection:Disconnect()
heartbeatConnection = nil
end
if activeTween then activeTween:Pause() activeTween:Destroy() activeTween = nil end
activeTween = tween(dummyPart, {"CFrame"}, rootCFrame * CFrame.Angles(0, 0, 0.2), easeInTime, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
activeTween.Completed:connect(function()
activeTween = tween(dummyPart, {"CFrame"}, rootCFrame, easeOutTime, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out)
activeTween.Completed:connect(function()
activeTween = nil
dummyPart:Destroy()
if heartbeatConnection then
heartbeatConnection:Disconnect()
heartbeatConnection = nil
end
end)
end)
heartbeatConnection = effects.onHeartbeatFor(easeInTime + easeOutTime, function()
model:SetPrimaryPartCFrame(dummyPart.CFrame:ToWorldSpace(offset))
end)
end
function attackable.onAttackedClient()
doShake()
doHealthReduction()
end
function attackable.onAttackedServer(player)
end
return attackable
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/axe.lua
================================================
local axe = {}
axe.isEquipped = false
local userInputService = game:GetService("UserInputService")
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local abilityAnimations = game:GetService("ReplicatedStorage"):WaitForChild("assets"):WaitForChild("abilityAnimations")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local currentDamageGUID = httpService:GenerateGUID(false)
-- todo: a job for someone else: convert these modules
local animationInterface = require(script.Parent.Parent.Parent:WaitForChild("contents"):WaitForChild("animationInterface"))--network:invoke("getPlayerCoreService", "animationInterface")
-- internal stuff specific to the sword
local animationsForAnimationController
local slashAnimationConnection
local isWithinSlash1Window = false
local isWithinSlash2Window = false
local isWithinDamageSequence = false
local currentWeaponManifest
local myClientCharacterContainer
local playerAbilitiesSlotDataCollection
local player = game.Players.LocalPlayer
local isPlayerSprinting = false
local function onCharacterStateChanged(state, value)
if state == "isSprinting" then
isPlayerSprinting = value
end
end
local isDamageSequenceEnabled = false
local function startDamageSequencePolling()
if isDamageSequenceEnabled then return end
isDamageSequenceEnabled = true
while isDamageSequenceEnabled do
if animationsForAnimationController.axeAnimations.strike1.IsPlaying then
if isWithinDamageSequence then
network:invoke("performClientDamageCycle", "equipment", nil, currentDamageGUID)
end
wait(1 / 20)
else
break
end
end
isDamageSequenceEnabled = false
end
local function onSlashAnimationTrackStopped()
currentDamageGUID = httpService:GenerateGUID(false)
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
-- slash1PeriodStart
-- slash2PeriodStart
-- startDamageSequence
-- stopDamageSequence
local function onSlashAnimationKeyframeReached(keyframeName)
if keyframeName == "slash1PeriodStart" then
isWithinSlash1Window = true
delay(3 / 10, function()
isWithinSlash1Window = false
end)
elseif keyframeName == "startDamageSequence" then
local swingSound = currentWeaponManifest:FindFirstChild("Swing")
if swingSound == nil then
swingSound = Instance.new("Sound")
swingSound.Volume = 1
swingSound.MaxDistance = 50
swingSound.SoundId = "rbxassetid://2069260907"
swingSound.Name = "Swing"
swingSound.Parent = currentWeaponManifest
end
swingSound:Play()
isWithinDamageSequence = true
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = true
end
elseif keyframeName == "stopDamageSequence" then
isWithinDamageSequence = false
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
end
function axe:attack()
-- make sure we can't slash if these conditions are true
if not animationsForAnimationController or not animationsForAnimationController.axeAnimations then
return
elseif isPlayerSprinting then
return
elseif not currentWeaponManifest then
return
elseif not player.Character or not player.Character.PrimaryPart or player.Character.PrimaryPart.state.Value == "dead" then
return
elseif animationsForAnimationController.axeAnimations.strike1.IsPlaying and (not isWithinSlash2Window or not canPlayerDoubleSlash)then --or not canPlayerDoubleSlash
return
end
-- have to do it this way for now, no reference to ability animations in animationsForAnimationController
local animController = myClientCharacterContainer.entity.AnimationController
for i, track in pairs(animController:GetPlayingAnimationTracks()) do
if track.Name == "rock_throw_upper" or track.Name == "rock_throw_upper_loop" then
return
end
end
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
slashAnimationConnection = animationsForAnimationController.axeAnimations.strike1.Stopped:connect(function()
isWithinDamageSequence = false
isWithinSlash1Window = false
isWithinSlash2Window = false
end)
slashAnimationKeyframeConnection = animationsForAnimationController.axeAnimations.strike1.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("axeAnimations", "strike1")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
end
function axe:equip()
isWithinSlash1Window = false
isWithinSlash2Window = false
isWithinDamageSequence = false
isDamageSequenceEnabled = false
--local
myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientCharacterContainer then
currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
animationsForAnimationController = animationInterface:getAnimationsForAnimationController(myClientCharacterContainer.entity.AnimationController)
-- local grip = myClientCharacterContainer.entity:FindFirstChild("Grip", true)
-- if grip then
-- -- force an update
-- onGripPropertyChanged(grip, "Part1")
-- end
end
end
function axe:unequip()
end
local function onPropogationRequestToSelf(propogationNameTag, propogationValue)
if propogationNameTag == "abilities" then
playerAbilitiesSlotDataCollection = propogationValue
end
end
local function main()
onPropogationRequestToSelf("abilities", network:invoke("getCacheValueByNameTag", "abilities"))
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:connect("characterStateChanged", "Event", onCharacterStateChanged)
end
main()
return axe
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/bow.lua
================================================
local hitDebounceTable = {}
local bow = {}
bow.isEquipped = false
local runService = game:GetService("RunService")
local userInputService = game:GetService("UserInputService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local client_utilities = modules.load("client_utilities")
local detection = modules.load("detection")
local placeSetup = modules.load("placeSetup")
local projectile = modules.load("projectile")
local configuration = modules.load("configuration")
local damage = modules.load("damage")
local tween = modules.load("tween")
local animationInterface = require(script.Parent.Parent.Parent:WaitForChild("contents"):WaitForChild("animationInterface"))--network:invoke("getPlayerCoreService", "animationInterface")
local entityRenderCollectionFolder = placeSetup.awaitPlaceFolder("entityRenderCollection")
local entityManifestCollectionFolder = placeSetup.awaitPlaceFolder("entityManifestCollection")
local itemsFolder = placeSetup.awaitPlaceFolder("items")
local entitiesFolder = placeSetup.awaitPlaceFolder("entities")
-- internal stuff specific to the bow
local isBowCharging = false
local currentArrow
local currentWeaponManifest
local bowAnimationsForTool
local animationsForAnimationController
local camera = workspace.CurrentCamera
local player = game.Players.LocalPlayer
local isPlayerSprinting = false
local function getRayClosestPoint(ray, targetPos)
local AP = targetPos - ray.Origin
local AB = (ray.Origin + ray.Direction) - ray.Origin
local dotProduct = AP:Dot(AB) / AB:Dot(AB)
dotProduct = math.clamp(dotProduct, 0, 1)
return ray.Origin + dotProduct * AB
end
local function onCharacterStateChanged(state, value)
if state == "isSprinting" then
isPlayerSprinting = value
end
end
local function onBowToolStretchStopped()
if isBowCharging and bowAnimationsForTool.stretchHold then
bowAnimationsForTool.stretchHold:Play()
end
if animationsForAnimationController.bowAnimations.stretching_bow.IsPlaying then
animationsForAnimationController.bowAnimations.stretching_bow:Stop()
end
end
local bowPullTime
local function playerHasArrowToShoot(numArrows)
local inventory = network:invoke("getCacheValueByNameTag", "inventory") or {}
for i, inventorySlotData in pairs(inventory) do
if inventorySlotData.id == 87 and inventorySlotData.stacks >= 1 then
return true, math.min(inventorySlotData.stacks, numArrows)
end
end
return false
end
local BOW_CHARGE_TIME = 0.8
local ARROWS_PER_SECOND = 1 / BOW_CHARGE_TIME
local arrowsToFire = 0
function bow:attackRangerStance()
local hasArrows, numArrows = playerHasArrowToShoot(1)
if not hasArrows then
network:fire("alert", {text = "You don't have any arrows!", id = "noarrows"}, 3)
return false
end
network:invoke("setIsJumpEnabled", false)
network:invoke("setIsSprintingEnabled", false)
network:invoke("setIsChanneling", true)
isBowCharging = true
animationInterface:replicatePlayerAnimationSequence("bowAnimations", "stretching_bow_stance", nil, {
attackSpeed = 1,
numArrows = numArrows,
firingSeed = math.random(1,2048)
})
-- this is intentionally a wait in order to delay
-- the auto-attack loop that calls this function
-- so that spam-clicking can't cheese shots per second
wait(0.8)
if not isBowCharging then
return
elseif not playerHasArrowToShoot(1) then
network:invoke("setIsJumpEnabled", true)
network:invoke("setIsSprintingEnabled", true)
network:invoke("setIsChanneling", false)
isBowCharging = false
return
end
local abilityExecutionData = network:invoke("getAbilityExecutionData")
abilityExecutionData.bowChargeTime = configuration.getConfigurationValue("maxBowChargeTime")
animationInterface:replicatePlayerAnimationSequence("bowAnimations", "firing_bow_stance", nil, abilityExecutionData)
wait(0.9)
network:invoke("setIsJumpEnabled", true)
network:invoke("setIsSprintingEnabled", true)
network:invoke("setIsChanneling", false)
isBowCharging = false
end
local timeSinceChargeBow
function bow:attack(inputObject)
-- make sure we can't slash if these conditions are true
if not animationsForAnimationController or not animationsForAnimationController.bowAnimations then
print"no bow anim"
return
elseif isPlayerSprinting then
print"is sprint"
return
elseif not bowAnimationsForTool then
print"no bowtool anim"
return
elseif not currentWeaponManifest then
print("no weapon")
return
elseif not player.Character or not player.Character.PrimaryPart or player.Character.PrimaryPart.state.Value == "dead" then
print"is dead"
return
elseif isBowCharging then
print"already charging"
return
-- elseif network:invoke("getCharacterMovementStates").isInAir then
-- return
end
if inputObject.UserInputState ~= Enum.UserInputState.Begin then print"not begin" return end
if utilities.doesEntityHaveStatusEffect(player.Character.PrimaryPart, "ranger stance") then
self:attackRangerStance()
return
end
local stats = network:invoke("getCacheValueByNameTag", "nonSerializeData").statistics_final
local maxNumArrows = utilities.calculateNumArrowsFromDex(stats.dex)
local hasArrows, numArrows = playerHasArrowToShoot(maxNumArrows)
if not hasArrows then
network:fire("alert", {text = "You don't have any arrows!", id = "noarrows"}, 3)
return false
end
network:invoke("setIsJumpEnabled", false)
network:invoke("setIsSprintingEnabled", false)
network:invoke("setIsChanneling", true)
isBowCharging = true
-- the bow isn't quite fast enough on its own, so secretly boost attack speed behind the scenes
local baseAttackSpeedBoost = 0.33
local bonus = (stats.attackSpeed / 2) + baseAttackSpeedBoost
local attackSpeedScalar = 1 + bonus
animationInterface:replicatePlayerAnimationSequence("bowAnimations", "stretching_bow", nil, {
attackSpeed = stats.attackSpeed,
numArrows = numArrows,
firingSeed = math.random(1,2048)
})
-- wait for bow to let go of holding
bowPullTime = tick()
while inputObject.UserInputState ~= Enum.UserInputState.End do
runService.Stepped:wait()
end
self:fireArrow()
end
function bow:release()
-- dummy function to dissuade the system from calling this
-- one click should lead to one arrow at least no matter what
end
function bow:fireArrow()
local stats = network:invoke("getCacheValueByNameTag", "nonSerializeData").statistics_final
local maxNumArrows = utilities.calculateNumArrowsFromDex(stats.dex)
if not isBowCharging then
return
elseif not playerHasArrowToShoot(maxNumArrows) then
network:invoke("setIsJumpEnabled", true)
network:invoke("setIsSprintingEnabled", true)
network:invoke("setIsChanneling", false)
isBowCharging = false
return
end
isBowCharging = false
local attackSpeedScalar = 1 + stats.attackSpeed
local abilityExecutionData = network:invoke("getAbilityExecutionData")
abilityExecutionData.bowChargeTime = tick() - bowPullTime
bowPullTime = nil
network:invoke("setIsJumpEnabled", true)
network:invoke("setIsSprintingEnabled", true)
network:invoke("setIsChanneling", false)
if abilityExecutionData.bowChargeTime <= configuration.getConfigurationValue("minBowChargeTime") then
abilityExecutionData.canceled = true
end
local currentArrow = network:invoke("getPlayerRenderDataByNameTag", player, "primaryArrow")
if not abilityExecutionData.canceled and currentArrow then
local targets = damage.getDamagableTargets(player)
local hitPart, hitPos, hitNormal, hitMaterial, hitRay = client_utilities.raycastFromCurrentScreenPoint({entityRenderCollectionFolder; itemsFolder; entitiesFolder})
local ray = Ray.new(currentArrow.Position, (hitPos - currentArrow.Position).unit * 50)
local closestHitbox, closestDist, closestProjection = nil, configuration.getConfigurationValue("bowSnapTargetMaxDistance"), nil
for i, targetHitbox in pairs(targets) do
local pointOnRay = getRayClosestPoint(ray, targetHitbox.Position)
local boxProjection = detection.projection_Box(targetHitbox.CFrame, targetHitbox.Size, pointOnRay)
local distanceMissed = (pointOnRay - boxProjection).magnitude
if distanceMissed <= closestDist then
closestDist = distanceMissed
closestHitbox = targetHitbox
closestProjection = boxProjection
end
end
if closestHitbox then
-- target monster
abilityExecutionData["target-position"] = closestProjection
-- target monster
abilityExecutionData["target-velocity"] = closestHitbox.Velocity
-- target monster distance away
if player.Character and player.Character.PrimaryPart then
abilityExecutionData["target-distance-away"] = (closestHitbox.Position - player.Character.PrimaryPart.Position).magnitude
end
end
end
animationInterface:replicatePlayerAnimationSequence("bowAnimations", "firing_bow", nil, abilityExecutionData)
end
function bow:equip()
local myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientCharacterContainer then
currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
animationsForAnimationController = animationInterface:getAnimationsForAnimationController(myClientCharacterContainer.entity.AnimationController)
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("AnimationController") then
bowAnimationsForTool = animationInterface:getAnimationsForAnimationController(currentWeaponManifest.AnimationController, "bowToolAnimations_noChar").bowToolAnimations_noChar
end
end
end
function bow:unequip()
network:fire("replicateClientCharacterWeaponStateChanged", "bow", nil)
end
local function main()
network:connect("characterStateChanged", "Event", onCharacterStateChanged)
end
main()
return bow
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/bow.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/dagger.lua
================================================
local dagger = {}
dagger.isEquipped = false
local userInputService = game:GetService("UserInputService")
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local abilityAnimations = game:GetService("ReplicatedStorage"):WaitForChild("assets"):WaitForChild("abilityAnimations")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local detection = modules.load("detection")
local placeSetup = modules.load("placeSetup")
local currentDamageGUID = httpService:GenerateGUID(false)
local animationInterface = require(script.Parent.Parent.Parent:WaitForChild("contents"):WaitForChild("animationInterface"))--network:invoke("getPlayerCoreService", "animationInterface")
-- internal stuff specific to the dagger
local animationControllerLoaded
local attackSequenceLength
local animationsForAnimationController
local slashAnimationConnection
local isWithinSlash1Window = false
local isWithinSlash2Window = false
local isWithinDamageSequence = false
local canPlayerDoubleSlash = false
local canPlayerTripleSlash = false
local currentWeaponManifest
local myClientCharacterContainer
local playerAbilitiesSlotDataCollection
local player = game.Players.LocalPlayer
local isPlayerSprinting = false
local function onCharacterStateChanged(state, value)
if state == "isSprinting" then
isPlayerSprinting = value
end
end
local function doesPlayerHaveAbilityUnlocked(abilityId, variant)
if playerAbilitiesSlotDataCollection then
for _, abilitySlotData in pairs(playerAbilitiesSlotDataCollection) do
if abilitySlotData.id == abilityId and abilitySlotData.rank > 0 then
if variant then
return abilitySlotData.variant == variant
else
return true
end
end
end
end
return false
end
local isDamageSequenceEnabled = false
local function startDamageSequencePolling()
if isDamageSequenceEnabled then return end
isDamageSequenceEnabled = true
while isDamageSequenceEnabled do
if animationsForAnimationController.daggerAnimations.strike1.IsPlaying or animationsForAnimationController.daggerAnimations.strike2.IsPlaying or animationsForAnimationController.daggerAnimations.strike3.IsPlaying then
if isWithinDamageSequence then
network:invoke("performClientDamageCycle", "equipment", nil, currentDamageGUID)
end
wait(1 / 20)
else
break
end
end
isDamageSequenceEnabled = false
end
local function onSlashAnimationTrackStopped()
currentDamageGUID = httpService:GenerateGUID(false)
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
-- slash1PeriodStart
-- slash2PeriodStart
-- startDamageSequence
-- stopDamageSequence
local function onSlashAnimationKeyframeReached(keyframeName)
if keyframeName == "slash1PeriodStart" then
currentWeaponManifest.Trail.Color = ColorSequence.new(Color3.new(1,0,0))
isWithinSlash1Window = true
-- delay(3 / 10, function()
-- isWithinSlash1Window = false
-- end)
elseif keyframeName == "slash2PeriodStart" then
currentWeaponManifest.Trail.Color = ColorSequence.new(Color3.new(0,1,0))
isWithinSlash2Window = true
-- delay(3 / 10, function()
-- isWithinSlash2Window = false
-- end)
elseif keyframeName == "startDamageSequence" then
currentWeaponManifest.Trail.Color = ColorSequence.new(Color3.new(1,1,1))
local swingSound = currentWeaponManifest:FindFirstChild("Swing")
if swingSound == nil then
swingSound = Instance.new("Sound")
swingSound.Volume = 1
swingSound.MaxDistance = 50
swingSound.SoundId = "rbxassetid://2069260907"
swingSound.Name = "Swing"
swingSound.Parent = currentWeaponManifest
end
swingSound:Play()
isWithinDamageSequence = true
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = true
end
elseif keyframeName == "stopDamageSequence" then
isWithinDamageSequence = false
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
end
function dagger:attack()
-- make sure we can't slash if these conditions are true
if not animationsForAnimationController or not animationsForAnimationController.daggerAnimations then
return
elseif isPlayerSprinting then
return
elseif not currentWeaponManifest then
return
elseif not player.Character or not player.Character.PrimaryPart or player.Character.PrimaryPart.state.Value == "dead" then
return
elseif animationsForAnimationController.daggerAnimations.strike1.IsPlaying and ((not isWithinSlash2Window) or (not canPlayerDoubleSlash))then --or not canPlayerDoubleSlash
return
elseif animationsForAnimationController.daggerAnimations.strike2.IsPlaying and not isWithinSlash1Window then
return
elseif animationsForAnimationController.daggerAnimations.strike3.IsPlaying then
return
end
-- have to do it this way for now, no reference to ability animations in animationsForAnimationController
local animController = myClientCharacterContainer.entity.AnimationController
for i, track in pairs(animController:GetPlayingAnimationTracks()) do
if track.Name == "rock_throw_upper" or track.Name == "rock_throw_upper_loop" then
return
end
end
if animationsForAnimationController.daggerAnimations.strike1.IsPlaying and isWithinSlash2Window then
isWithinSlash1Window = false
isWithinSlash2Window = false
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.daggerAnimations.strike1:Stop()
slashAnimationConnection = animationsForAnimationController.daggerAnimations.strike2.Stopped:connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = animationsForAnimationController.daggerAnimations.strike2.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("daggerAnimations", "strike2")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
elseif (not animationsForAnimationController.daggerAnimations.strike1.IsPlaying) and ((not animationsForAnimationController.daggerAnimations.strike2.IsPlaying) or isWithinSlash1Window) then
if canPlayerTripleSlash and isWithinSlash1Window then
isWithinSlash1Window = false
isWithinSlash2Window = false
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.daggerAnimations.strike2:Stop()
slashAnimationConnection = animationsForAnimationController.daggerAnimations.strike3.Stopped:connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = animationsForAnimationController.daggerAnimations.strike3.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("daggerAnimations", "strike3")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
else
isWithinSlash1Window = false
isWithinSlash2Window = false
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.daggerAnimations.strike2:Stop()
slashAnimationConnection = animationsForAnimationController.daggerAnimations.strike1.Stopped:connect(function()
isWithinDamageSequence = false
end)
slashAnimationKeyframeConnection = animationsForAnimationController.daggerAnimations.strike1.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("daggerAnimations", "strike1")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
end
end
end
function dagger:equip()
isWithinSlash1Window = false
isWithinSlash2Window = false
isWithinDamageSequence = false
isDamageSequenceEnabled = false
--local
myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientCharacterContainer then
currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
animationsForAnimationController = animationInterface:getAnimationsForAnimationController(myClientCharacterContainer.entity.AnimationController)
-- local grip = myClientCharacterContainer.entity:FindFirstChild("Grip", true)
-- if grip then
-- -- force an update
-- onGripPropertyChanged(grip, "Part1")
-- end
end
end
function dagger:unequip()
end
local function onPropogationRequestToSelf(propogationNameTag, propogationValue)
if propogationNameTag == "abilities" then
playerAbilitiesSlotDataCollection = propogationValue
canPlayerDoubleSlash = doesPlayerHaveAbilityUnlocked(3)
canPlayerTripleSlash = doesPlayerHaveAbilityUnlocked(3, "tripleSlash")
end
end
local function main()
onPropogationRequestToSelf("abilities", network:invoke("getCacheValueByNameTag", "abilities"))
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:connect("characterStateChanged", "Event", onCharacterStateChanged)
end
main()
return dagger
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/dagger.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/dagger_old.lua
================================================
local dagger = {}
dagger.isEquipped = false
local userInputService = game:GetService("UserInputService")
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local detection = modules.load("detection")
local placeSetup = modules.load("placeSetup")
local currentDamageGUID = httpService:GenerateGUID(false)
local animationInterface = require(script.Parent.Parent.Parent:WaitForChild("contents"):WaitForChild("animationInterface"))--network:invoke("getPlayerCoreService", "animationInterface")
local myClientCharacterContainer
-- internal stuff specific to the dagger
local animationControllerLoaded
local attackSequenceLength
local animationsForAnimationController
local slashAnimationConnection
local isWithinSlash1Window = false
local isWithinSlash2Window = false
local isWithinDamageSequence = false
local canPlayerDoubleSlash = false
local currentWeaponManifest
local playerAbilitiesSlotDataCollection
local player = game.Players.LocalPlayer
local isPlayerSprinting = false
local function onCharacterStateChanged(state, value)
if state == "isSprinting" then
isPlayerSprinting = value
end
end
local function doesPlayerHaveAbilityUnlocked(abilityId)
if playerAbilitiesSlotDataCollection then
for _, abilitySlotData in pairs(playerAbilitiesSlotDataCollection) do
if abilitySlotData.id == abilityId and abilitySlotData.rank > 0 then
return true
end
end
end
return false
end
local isDamageSequenceEnabled = false
local function startDamageSequencePolling()
if isDamageSequenceEnabled then return end
isDamageSequenceEnabled = true
while isDamageSequenceEnabled do
if animationsForAnimationController.daggerAnimations.strike1.IsPlaying or animationsForAnimationController.daggerAnimations.strike2.IsPlaying then
if isWithinDamageSequence then
-- todo: consider just using serverHitbox in `monsterManifestCollectionFolder` ?
network:invoke("performClientDamageCycle", "equipment", nil, currentDamageGUID)
end
wait(1 / 20)
else
break
end
end
isDamageSequenceEnabled = false
end
local function onSlashAnimationTrackStopped()
currentDamageGUID = httpService:GenerateGUID(false)
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
-- slash1PeriodStart
-- slash2PeriodStart
-- startDamageSequence
-- stopDamageSequence
local function onSlashAnimationKeyframeReached(keyframeName)
if keyframeName == "slash1PeriodStart" then
isWithinSlash1Window = true
delay(3 / 10, function()
isWithinSlash1Window = false
end)
elseif keyframeName == "slash2PeriodStart" then
isWithinSlash2Window = true
delay(3 / 10, function()
isWithinSlash2Window = false
end)
elseif keyframeName == "startDamageSequence" then
local swingSound = currentWeaponManifest:FindFirstChild("Swing")
if swingSound == nil then
swingSound = Instance.new("Sound")
swingSound.Volume = 1
swingSound.MaxDistance = 50
swingSound.SoundId = "rbxassetid://2069260907"
swingSound.Name = "Swing"
swingSound.Parent = currentWeaponManifest
end
swingSound:Play()
isWithinDamageSequence = true
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = true
end
elseif keyframeName == "stopDamageSequence" then
isWithinDamageSequence = false
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
end
function dagger:attack()
-- make sure we can't slash if these conditions are true
if not animationsForAnimationController or not animationsForAnimationController.swordAnimations then
return
elseif isPlayerSprinting then
return
elseif not currentWeaponManifest then
return
elseif not player.Character or not player.Character.PrimaryPart or player.Character.PrimaryPart.state.Value == "dead" then
return
elseif animationsForAnimationController.swordAnimations.strike1.IsPlaying and (not isWithinSlash2Window or not canPlayerDoubleSlash)then
return
elseif animationsForAnimationController.swordAnimations.strike2.IsPlaying and not isWithinSlash1Window then
return
end
-- have to do it this way for now, no reference to ability animations in animationsForAnimationController
local animController = myClientCharacterContainer.entity.AnimationController
for i, track in pairs(animController:GetPlayingAnimationTracks()) do
if track.Name == "rock_throw_upper" or track.Name == "rock_throw_upper_loop" then
return
end
end
if animationsForAnimationController.daggerAnimations.strike1.IsPlaying and isWithinSlash2Window and canPlayerDoubleSlash then
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.daggerAnimations.strike1:Stop()
slashAnimationConnection = animationsForAnimationController.daggerAnimations.strike2.Stopped:connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = animationsForAnimationController.daggerAnimations.strike2.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicatePlayerAnimationSequence("daggerAnimations", "strike2")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
elseif not animationsForAnimationController.daggerAnimations.strike1.IsPlaying and (not animationsForAnimationController.daggerAnimations.strike2.IsPlaying or isWithinSlash1Window) then
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.daggerAnimations.strike2:Stop()
slashAnimationConnection = animationsForAnimationController.daggerAnimations.strike1.Stopped:connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = animationsForAnimationController.daggerAnimations.strike1.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicatePlayerAnimationSequence("daggerAnimations", "strike1")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
end
end
function dagger:equip()
isWithinSlash1Window = false
isWithinSlash2Window = false
isWithinDamageSequence = false
isDamageSequenceEnabled = false
myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientCharacterContainer then
currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
animationsForAnimationController = animationInterface:getAnimationsForAnimationController(myClientCharacterContainer.entity.AnimationController)
-- local grip = myClientCharacterContainer.entity:FindFirstChild("Grip", true)
-- if grip then
-- -- force an update
-- onGripPropertyChanged(grip, "Part1")
-- end
end
end
function dagger:unequip()
end
local function onPropogationRequestToSelf(propogationNameTag, propogationValue)
if propogationNameTag == "abilities" then
playerAbilitiesSlotDataCollection = propogationValue
if doesPlayerHaveAbilityUnlocked(3) then
canPlayerDoubleSlash = true
else
canPlayerDoubleSlash = false
end
end
end
local function main()
onPropogationRequestToSelf("abilities", network:invoke("getCacheValueByNameTag", "abilities"))
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:connect("characterStateChanged", "Event", onCharacterStateChanged)
end
main()
return dagger
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/dagger_old.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/dual.lua
================================================
local dual = {}
dual.isEquipped = false
local userInputService = game:GetService("UserInputService")
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local abilityAnimations = game:GetService("ReplicatedStorage"):WaitForChild("assets"):WaitForChild("abilityAnimations")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local detection = modules.load("detection")
local placeSetup = modules.load("placeSetup")
local currentDamageGUID = httpService:GenerateGUID(false)
local animationInterface = require(script.Parent.Parent.Parent:WaitForChild("contents"):WaitForChild("animationInterface"))--network:invoke("getPlayerCoreService", "animationInterface")
-- internal stuff specific to the dual
local animationControllerLoaded
local attackSequenceLength
local animationsForAnimationController
local slashAnimationConnection
local isWithinSlash1Window = false
local isWithinSlash2Window = false
local isWithinDamageSequence = false
local canPlayerDoubleSlash = false
local canPlayerTripleSlash = false
local weaponManifestRight, weaponManifestLeft
local myClientCharacterContainer
local playerAbilitiesSlotDataCollection
local player = game.Players.LocalPlayer
local isPlayerSprinting = false
local function onCharacterStateChanged(state, value)
if state == "isSprinting" then
isPlayerSprinting = value
end
end
local function doesPlayerHaveAbilityUnlocked(abilityId)
if playerAbilitiesSlotDataCollection then
for _, abilitySlotData in pairs(playerAbilitiesSlotDataCollection) do
if abilitySlotData.id == abilityId and abilitySlotData.rank > 0 then
return true
end
end
end
return false
end
local isDamageSequenceEnabled = false
local function startDamageSequencePolling()
if isDamageSequenceEnabled then return end
isDamageSequenceEnabled = true
while isDamageSequenceEnabled do
if animationsForAnimationController.dualAnimations.strike1.IsPlaying or animationsForAnimationController.dualAnimations.strike2.IsPlaying or animationsForAnimationController.dualAnimations.strike3.IsPlaying then
if isWithinDamageSequence then
network:invoke("performClientDamageCycle", "equipment", nil, currentDamageGUID)
end
wait(1 / 20)
else
break
end
end
isDamageSequenceEnabled = false
end
local function onSlashAnimationTrackStopped()
currentDamageGUID = httpService:GenerateGUID(false)
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
end
-- slash1PeriodStart
-- slash2PeriodStart
-- startDamageSequence
-- stopDamageSequence
local function onSlashAnimationKeyframeReached(keyframeName)
local anims = animationsForAnimationController.dualAnimations
if not anims then return end
local weaponManifests
if anims.strike1.IsPlaying then
weaponManifests = {weaponManifestRight}
elseif anims.strike2.IsPlaying then
weaponManifests = {weaponManifestLeft}
elseif anims.strike3.IsPlaying then
weaponManifests = {weaponManifestRight, weaponManifestLeft}
else
-- this should hopefully not happen
weaponManifests = {}
end
if keyframeName == "slash1PeriodStart" then
isWithinSlash1Window = true
delay(0.6, function()
isWithinSlash1Window = false
end)
elseif keyframeName == "slash2PeriodStart" then
isWithinSlash2Window = true
delay(0.6, function()
isWithinSlash2Window = false
end)
elseif keyframeName == "startDamageSequence" then
isWithinDamageSequence = true
for _, currentWeaponManifest in pairs(weaponManifests) do
local swingSound = currentWeaponManifest:FindFirstChild("Swing")
if swingSound == nil then
swingSound = Instance.new("Sound")
swingSound.Volume = 1
swingSound.MaxDistance = 50
swingSound.SoundId = "rbxassetid://2069260907"
swingSound.Name = "Swing"
swingSound.Parent = currentWeaponManifest
end
swingSound:Play()
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = true
end
end
elseif keyframeName == "stopDamageSequence" then
isWithinDamageSequence = false
for _, currentWeaponManifest in pairs(weaponManifests) do
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
end
end
local function resetConnections()
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
end
function dual:attack()
if not animationsForAnimationController then return end
local anims = animationsForAnimationController.dualAnimations
if not anims then
return
elseif isPlayerSprinting then
return
elseif not (weaponManifestRight and weaponManifestRight) then
return
elseif not player.Character or not player.Character.PrimaryPart or player.Character.PrimaryPart.state.Value == "dead" then
return
elseif anims.strike1.IsPlaying then
return
elseif anims.strike2.IsPlaying then
return
elseif anims.strike3.IsPlaying then
return
end
-- should slash1 if we're not playing it or we're following through from
-- slash2 if we're not a triple slash character
local shouldSlash1 = (not anims.strike1.IsPlaying) or (isWithinSlash1Window and (not canPlayerTripleSlash))
-- should slash2 if we're not playing it and we're following through from
-- slash1 and we can double slash
local shouldSlash2 = (not anims.strike2.IsPlaying) and isWithinSlash2Window and canPlayerDoubleSlash
-- should slash3 if we're not playing it and we're following through from
-- slash2 and we can triple slash
local shouldSlash3 = (not anims.strike3.IsPlaying) and isWithinSlash1Window and canPlayerTripleSlash
-- so... let's do the slash we're supposed to be doing
-- prioritize slash3 because sometimes slash1 thinks it's better
if shouldSlash3 then
isWithinSlash1Window = false
resetConnections()
slashAnimationConnection = anims.strike3.Stopped:Connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = anims.strike3.KeyframeReached:Connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("dualAnimations", "strike3")
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
elseif shouldSlash2 then
isWithinSlash2Window = false
resetConnections()
slashAnimationConnection = anims.strike2.Stopped:Connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = anims.strike2.KeyframeReached:Connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("dualAnimations", "strike2")
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
elseif shouldSlash1 then
isWithinSlash1Window = false
resetConnections()
slashAnimationConnection = anims.strike1.Stopped:Connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = anims.strike1.KeyframeReached:Connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("dualAnimations", "strike1")
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
end
end
function dual:equip()
isWithinSlash1Window = false
isWithinSlash2Window = false
isWithinDamageSequence = false
isDamageSequenceEnabled = false
--local
myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientCharacterContainer then
local currentlyEquipped = network:invoke("getCurrentlyEquippedForRenderCharacter", myClientCharacterContainer.entity)
weaponManifestRight = currentlyEquipped["1"].manifest
weaponManifestLeft = currentlyEquipped["11"].manifest
animationsForAnimationController = animationInterface:getAnimationsForAnimationController(myClientCharacterContainer.entity.AnimationController)
-- local grip = myClientCharacterContainer.entity:FindFirstChild("Grip", true)
-- if grip then
-- -- force an update
-- onGripPropertyChanged(grip, "Part1")
-- end
end
end
function dual:unequip()
end
local function onPropogationRequestToSelf(propogationNameTag, propogationValue)
if propogationNameTag == "abilities" then
playerAbilitiesSlotDataCollection = propogationValue
canPlayerDoubleSlash = doesPlayerHaveAbilityUnlocked(3)
canPlayerTripleSlash = doesPlayerHaveAbilityUnlocked(30)
end
end
local function main()
onPropogationRequestToSelf("abilities", network:invoke("getCacheValueByNameTag", "abilities"))
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:connect("characterStateChanged", "Event", onCharacterStateChanged)
end
main()
return dual
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/dual.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/fishing-rod.lua
================================================
local fishingPole = {}
fishingPole.isEquipped = false
local userInputService = game:GetService("UserInputService")
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local detection = modules.load("detection")
local placeSetup = modules.load("placeSetup")
local projectile = modules.load("projectile")
local client_utilities = modules.load("client_utilities")
local currentDamageGUID = httpService:GenerateGUID(false)
local animationInterface = require(script.Parent.Parent.Parent:WaitForChild("contents"):WaitForChild("animationInterface"))--network:invoke("getPlayerCoreService", "animationInterface")
-- internal stuff specific to the fishingPole
local animationControllerLoaded
local attackSequenceLength
local slashAnimationConnection
local isWithinSlash1Window = false
local isWithinSlash2Window = false
local isWithinDamageSequence = false
local canPlayerDoubleSlash = false
local currentWeaponManifest
local playerAbilitiesSlotDataCollection
local player = game.Players.LocalPlayer
local isFishing = false
local isProcessing = false
local fishingPoleManifest
local currentBob
local timesFishing = 0
function fishingPole:attack()
if not isProcessing then
isProcessing = true
if not isFishing then
timesFishing = timesFishing + 1
local myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if not myClientCharacterContainer or not myClientCharacterContainer:FindFirstChild("entity") then return false end
local currentlyEquipped = network:invoke("getCurrentlyEquippedForRenderCharacter", myClientCharacterContainer.entity)
local currentWeaponManifest = currentlyEquipped["1"] and currentlyEquipped["1"].manifest
if not currentWeaponManifest then return end
local _, targetPosition = client_utilities.raycastFromCurrentScreenPoint({myClientCharacterContainer})
animationInterface:replicatePlayerAnimationSequence("fishing-rodAnimations", "cast-line", nil, {targetPosition = targetPosition})
network:invoke("setCharacterArrested", true)
network:invoke("setCharacterMovementState", "isFishing", true)
--network:invokeServer("playerRequest_startFishing")
else
animationInterface:replicatePlayerAnimationSequence("fishing-rodAnimations", "reel-line")
isFishing = false
spawn(function()
wait(1)
isProcessing = false
end)
end
end
end
local function onFishingBobBobbed()
local clientFishingBob = network:invoke("getPlayerRenderDataByNameTag", player, "fishingBob")
if clientFishingBob and clientFishingBob.Parent then
local currentFishing = timesFishing
clientFishingBob.CFrame = clientFishingBob.CFrame - Vector3.new(0, 1, 0)
clientFishingBob.splash:Emit(40)
utilities.playSound("fishing_FishBite", clientFishingBob)
wait(0.15)
if currentFishing == timesFishing then
utilities.playSound("fishing_FishSplashOutOfWater", clientFishingBob)
end
wait(0.1)
if currentFishing == timesFishing then
clientFishingBob.CFrame = clientFishingBob.CFrame - Vector3.new(0, 0.25, 0)
clientFishingBob.splash:Emit(40)
end
end
end
function fishingPole:equip()
currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
end
function fishingPole:unequip()
if isFishing then
network:invoke("setCharacterMovementState", "isFishing", false)
network:invoke("setCharacterArrested", false)
local currentBob = network:invoke("getPlayerRenderDataByNameTag", game.Players.LocalPlayer, "fishingBob")
if currentBob then
game:GetService("Debris"):AddItem(currentBob, 1 / 30)
end
if fishingPoleManifest and fishingPoleManifest:FindFirstChild("line") then
fishingPoleManifest.line.Attachment1 = nil
fishingPoleManifest = nil
end
isFishing = false
end
end
local function onPropogationRequestToSelf(propogationNameTag, propogationValue)
if propogationNameTag == "abilities" then
playerAbilitiesSlotDataCollection = propogationValue
end
end
local function onFishingBobHit(hitWater)
if not hitWater then
isFishing = false
network:invoke("setCharacterMovementState", "isFishing", false)
network:invoke("setCharacterArrested", false)
spawn(function()
wait(1)
isProcessing = false
end)
else
isFishing = true
isProcessing = false
end
end
local function main()
onPropogationRequestToSelf("abilities", network:invoke("getCacheValueByNameTag", "abilities"))
network:create("fishingBobHit", "BindableEvent", "Event", onFishingBobHit)
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:connect("signal_fishingBobBobbed", "OnClientEvent", onFishingBobBobbed)
end
main()
return fishingPole
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/fishing-rod.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/greatsword.lua
================================================
local sword = {}
sword.isEquipped = false
local userInputService = game:GetService("UserInputService")
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local abilityAnimations = game:GetService("ReplicatedStorage"):WaitForChild("assets"):WaitForChild("abilityAnimations")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local detection = modules.load("detection")
local placeSetup = modules.load("placeSetup")
local currentDamageGUID = httpService:GenerateGUID(false)
local animationInterface = require(script.Parent.Parent.Parent:WaitForChild("contents"):WaitForChild("animationInterface"))--network:invoke("getPlayerCoreService", "animationInterface")
-- internal stuff specific to the sword
local animationControllerLoaded
local attackSequenceLength
local animationsForAnimationController
local slashAnimationConnection
local isWithinSlash1Window = false
local isWithinSlash2Window = false
local isWithinDamageSequence = false
local canPlayerDoubleSlash = false
local canPlayerTripleSlash = false
local isContinueNextStrike = false
local currentWeaponManifest
local myClientCharacterContainer
local playerAbilitiesSlotDataCollection
local player = game.Players.LocalPlayer
local isPlayerSprinting = false
local function onCharacterStateChanged(state, value)
if state == "isSprinting" then
isPlayerSprinting = value
end
end
local function doesPlayerHaveAbilityUnlocked(abilityId, variant)
if playerAbilitiesSlotDataCollection then
for _, abilitySlotData in pairs(playerAbilitiesSlotDataCollection) do
if abilitySlotData.id == abilityId and abilitySlotData.rank > 0 then
if variant then
return abilitySlotData.variant == variant
else
return true
end
end
end
end
return false
end
local isDamageSequenceEnabled = false
local function startDamageSequencePolling()
if isDamageSequenceEnabled then return end
isDamageSequenceEnabled = true
while isDamageSequenceEnabled do
if animationsForAnimationController.greatswordAnimations.strike1.IsPlaying or animationsForAnimationController.greatswordAnimations.strike2.IsPlaying or animationsForAnimationController.greatswordAnimations.strike3.IsPlaying then
if isWithinDamageSequence then
network:invoke("performClientDamageCycle", "equipment", nil, currentDamageGUID)
end
wait(1 / 20)
else
break
end
end
isDamageSequenceEnabled = false
end
-- slash1PeriodStart
-- slash2PeriodStart
-- startDamageSequence
-- stopDamageSequence
local function onSlashAnimationKeyframeReached(keyframeName)
if keyframeName == "startDamageSequence" then
local swingSound = currentWeaponManifest:FindFirstChild("Swing")
if swingSound == nil then
swingSound = Instance.new("Sound")
swingSound.PlaybackSpeed = 0.6
swingSound.Volume = 1
swingSound.MaxDistance = 50
swingSound.SoundId = "rbxassetid://2069260907"
swingSound.Name = "Swing"
swingSound.Parent = currentWeaponManifest
end
swingSound:Play()
isWithinDamageSequence = true
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = true
end
elseif keyframeName == "stopDamageSequence" then
isWithinDamageSequence = false
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
end
local function onSlashAnimationTrackStopped(current)
currentDamageGUID = httpService:GenerateGUID(false)
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
if isContinueNextStrike then
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
local nextStrike = (current == "strike1" and "strike2") or (current == "strike2" and "strike3")
if nextStrike then
slashAnimationKeyframeConnection = animationsForAnimationController.greatswordAnimations[nextStrike].KeyframeReached:connect(onSlashAnimationKeyframeReached)
slashAnimationConnection = animationsForAnimationController.greatswordAnimations[nextStrike].Stopped:connect(function()
onSlashAnimationTrackStopped(nextStrike)
end)
--checking if player has the ability to do nextStrike
--strike0 represents when the player should not strike again
if nextStrike == "strike2" then
if canPlayerDoubleSlash then else
nextStrike = "strike0"
end
elseif nextStrike == "strike3" then
if canPlayerTripleSlash then else
nextStrike = "strike0"
end
end
if nextStrike == "strike0" then
else
animationInterface:replicateClientAnimationSequence("greatswordAnimations", nextStrike)
spawn(startDamageSequencePolling)
end
end
else
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
end
isContinueNextStrike = false
end
function sword:attack()
if not animationsForAnimationController or not animationsForAnimationController.greatswordAnimations then
return
elseif isPlayerSprinting then
return
elseif not currentWeaponManifest then
return
elseif not player.Character or not player.Character.PrimaryPart or player.Character.PrimaryPart.state.Value == "dead" then
return
elseif animationsForAnimationController.greatswordAnimations.strike1.IsPlaying and isContinueNextStrike then --or not canPlayerDoubleSlash
return
elseif animationsForAnimationController.greatswordAnimations.strike2.IsPlaying and isContinueNextStrike then
return
elseif animationsForAnimationController.greatswordAnimations.strike3.IsPlaying then
return
end
if not animationsForAnimationController.greatswordAnimations.strike1.IsPlaying and not animationsForAnimationController.greatswordAnimations.strike2.IsPlaying and not animationsForAnimationController.greatswordAnimations.strike3.IsPlaying then
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
slashAnimationConnection = animationsForAnimationController.greatswordAnimations.strike1.Stopped:connect(function() onSlashAnimationTrackStopped("strike1") end)
slashAnimationKeyframeConnection = animationsForAnimationController.greatswordAnimations.strike1.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("greatswordAnimations", "strike1")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
else
isContinueNextStrike = true
end
end
function sword:equip()
isWithinSlash1Window = false
isWithinSlash2Window = false
isWithinDamageSequence = false
isDamageSequenceEnabled = false
myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientCharacterContainer then
currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
animationsForAnimationController = animationInterface:getAnimationsForAnimationController(myClientCharacterContainer.entity.AnimationController)
end
end
function sword:unequip()
end
local function onPropogationRequestToSelf(propogationNameTag, propogationValue)
if propogationNameTag == "abilities" then
playerAbilitiesSlotDataCollection = propogationValue
canPlayerDoubleSlash = doesPlayerHaveAbilityUnlocked(3)
canPlayerTripleSlash = doesPlayerHaveAbilityUnlocked(3, "tripleSlash")
end
end
local function main()
onPropogationRequestToSelf("abilities", network:invoke("getCacheValueByNameTag", "abilities"))
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:connect("characterStateChanged", "Event", onCharacterStateChanged)
end
main()
return sword
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/greatsword.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/init.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/pickaxe.lua
================================================
local axe = {}
axe.isEquipped = false
local userInputService = game:GetService("UserInputService")
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local abilityAnimations = game:GetService("ReplicatedStorage"):WaitForChild("assets"):WaitForChild("abilityAnimations")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local currentDamageGUID = httpService:GenerateGUID(false)
local animationInterface = require(script.Parent.Parent.Parent:WaitForChild("contents"):WaitForChild("animationInterface"))--network:invoke("getPlayerCoreService", "animationInterface")
-- internal stuff specific to the sword
local animationsForAnimationController
local slashAnimationConnection
local isWithinSlash1Window = false
local isWithinSlash2Window = false
local isWithinDamageSequence = false
local currentWeaponManifest
local myClientCharacterContainer
local playerAbilitiesSlotDataCollection
local player = game.Players.LocalPlayer
local isPlayerSprinting = false
local function onCharacterStateChanged(state, value)
if state == "isSprinting" then
isPlayerSprinting = value
end
end
local isDamageSequenceEnabled = false
local function startDamageSequencePolling()
if isDamageSequenceEnabled then return end
isDamageSequenceEnabled = true
while isDamageSequenceEnabled do
if animationsForAnimationController.pickaxeAnimations.strike1.IsPlaying then
if isWithinDamageSequence then
network:invoke("performClientDamageCycle", "equipment", nil, currentDamageGUID)
end
wait(1 / 20)
else
break
end
end
isDamageSequenceEnabled = false
end
local function onSlashAnimationTrackStopped()
currentDamageGUID = httpService:GenerateGUID(false)
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
-- slash1PeriodStart
-- slash2PeriodStart
-- startDamageSequence
-- stopDamageSequence
local function onSlashAnimationKeyframeReached(keyframeName)
if keyframeName == "slash1PeriodStart" then
isWithinSlash1Window = true
delay(3 / 10, function()
isWithinSlash1Window = false
end)
elseif keyframeName == "startDamageSequence" then
local swingSound = currentWeaponManifest:FindFirstChild("Swing")
if swingSound == nil then
swingSound = Instance.new("Sound")
swingSound.Volume = 1
swingSound.MaxDistance = 50
swingSound.SoundId = "rbxassetid://2069260907"
swingSound.Name = "Swing"
swingSound.Parent = currentWeaponManifest
end
swingSound:Play()
isWithinDamageSequence = true
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = true
end
elseif keyframeName == "stopDamageSequence" then
isWithinDamageSequence = false
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
end
function axe:attack()
-- make sure we can't slash if these conditions are true
if not animationsForAnimationController or not animationsForAnimationController.pickaxeAnimations then
return
elseif isPlayerSprinting then
return
elseif not currentWeaponManifest then
return
elseif not player.Character or not player.Character.PrimaryPart or player.Character.PrimaryPart.state.Value == "dead" then
return
elseif animationsForAnimationController.pickaxeAnimations.strike1.IsPlaying and (not isWithinSlash2Window or not canPlayerDoubleSlash)then --or not canPlayerDoubleSlash
return
end
-- have to do it this way for now, no reference to ability animations in animationsForAnimationController
local animController = myClientCharacterContainer.entity.AnimationController
for i, track in pairs(animController:GetPlayingAnimationTracks()) do
if track.Name == "rock_throw_upper" or track.Name == "rock_throw_upper_loop" then
return
end
end
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
slashAnimationConnection = animationsForAnimationController.pickaxeAnimations.strike1.Stopped:connect(function()
isWithinDamageSequence = false
isWithinSlash1Window = false
isWithinSlash2Window = false
end)
slashAnimationKeyframeConnection = animationsForAnimationController.pickaxeAnimations.strike1.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("pickaxeAnimations", "strike1")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
end
function axe:equip()
isWithinSlash1Window = false
isWithinSlash2Window = false
isWithinDamageSequence = false
isDamageSequenceEnabled = false
--local
myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientCharacterContainer then
currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
animationsForAnimationController = animationInterface:getAnimationsForAnimationController(myClientCharacterContainer.entity.AnimationController)
-- local grip = myClientCharacterContainer.entity:FindFirstChild("Grip", true)
-- if grip then
-- -- force an update
-- onGripPropertyChanged(grip, "Part1")
-- end
end
end
function axe:unequip()
end
local function onPropogationRequestToSelf(propogationNameTag, propogationValue)
if propogationNameTag == "abilities" then
playerAbilitiesSlotDataCollection = propogationValue
end
end
local function main()
onPropogationRequestToSelf("abilities", network:invoke("getCacheValueByNameTag", "abilities"))
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:connect("characterStateChanged", "Event", onCharacterStateChanged)
end
main()
return axe
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/staff.lua
================================================
local staff = {}
staff.isEquipped = false
local userInputService = game:GetService("UserInputService")
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local abilityAnimations = game:GetService("ReplicatedStorage"):WaitForChild("assets"):WaitForChild("abilityAnimations")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local detection = modules.load("detection")
local placeSetup = modules.load("placeSetup")
local currentDamageGUID = httpService:GenerateGUID(false)
local animationInterface = require(script.Parent.Parent.Parent:WaitForChild("contents"):WaitForChild("animationInterface"))--network:invoke("getPlayerCoreService", "animationInterface")
-- internal stuff specific to the staff
local animationControllerLoaded
local attackSequenceLength
local animationsForAnimationController
local slashAnimationConnection
local isWithinSlash1Window = false
local isWithinSlash2Window = false
local isWithinDamageSequence = false
local canPlayerDoubleSlash = false
local canPlayerTripleSlash = false
local currentWeaponManifest
local myClientCharacterContainer
local playerAbilitiesSlotDataCollection
local player = game.Players.LocalPlayer
local isPlayerSprinting = false
local function onCharacterStateChanged(state, value)
if state == "isSprinting" then
isPlayerSprinting = value
end
end
local function doesPlayerHaveAbilityUnlocked(abilityId, variant)
if playerAbilitiesSlotDataCollection then
for _, abilitySlotData in pairs(playerAbilitiesSlotDataCollection) do
if abilitySlotData.id == abilityId and abilitySlotData.rank > 0 then
if variant then
return abilitySlotData.variant == variant
else
return true
end
end
end
end
return false
end
local isDamageSequenceEnabled = false
local function startDamageSequencePolling()
if isDamageSequenceEnabled then return end
isDamageSequenceEnabled = true
while isDamageSequenceEnabled do
if animationsForAnimationController.swordAnimations.strike1.IsPlaying or animationsForAnimationController.swordAnimations.strike2.IsPlaying or animationsForAnimationController.swordAnimations.strike3.IsPlaying then
if isWithinDamageSequence then
network:invoke("performClientDamageCycle", "equipment", nil, currentDamageGUID)
end
wait(1 / 20)
else
break
end
end
isDamageSequenceEnabled = false
end
local function onSlashAnimationTrackStopped()
currentDamageGUID = httpService:GenerateGUID(false)
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
-- slash1PeriodStart
-- slash2PeriodStart
-- startDamageSequence
-- stopDamageSequence
local function onSlashAnimationKeyframeReached(keyframeName)
if keyframeName == "slash1PeriodStart" then
isWithinSlash1Window = true
delay(3 / 10, function()
isWithinSlash1Window = false
end)
elseif keyframeName == "slash2PeriodStart" then
isWithinSlash2Window = true
delay(3 / 10, function()
isWithinSlash2Window = false
end)
elseif keyframeName == "startDamageSequence" then
local swingSound = currentWeaponManifest:FindFirstChild("Swing")
if swingSound == nil then
swingSound = Instance.new("Sound")
swingSound.Volume = 1
swingSound.MaxDistance = 50
swingSound.SoundId = "rbxassetid://2069260907"
swingSound.Name = "Swing"
swingSound.Parent = currentWeaponManifest
end
swingSound:Play()
isWithinDamageSequence = true
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = true
end
elseif keyframeName == "stopDamageSequence" then
isWithinDamageSequence = false
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
end
function staff:attack()
-- make sure we can't slash if these conditions are true
if not animationsForAnimationController or not animationsForAnimationController.swordAnimations then
return
elseif isPlayerSprinting then
return
elseif not currentWeaponManifest then
return
elseif not player.Character or not player.Character.PrimaryPart or player.Character.PrimaryPart.state.Value == "dead" then
return
elseif animationsForAnimationController.swordAnimations.strike1.IsPlaying and (not isWithinSlash2Window or not canPlayerDoubleSlash)then --or not canPlayerDoubleSlash
return
elseif animationsForAnimationController.swordAnimations.strike2.IsPlaying and not isWithinSlash1Window then
return
elseif animationsForAnimationController.swordAnimations.strike3.IsPlaying then
return
end
-- have to do it this way for now, no reference to ability animations in animationsForAnimationController
local animController = myClientCharacterContainer.entity.AnimationController
for i, track in pairs(animController:GetPlayingAnimationTracks()) do
if track.Name == "rock_throw_upper" or track.Name == "rock_throw_upper_loop" then
return
end
end
if animationsForAnimationController.swordAnimations.strike1.IsPlaying and isWithinSlash2Window then
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.swordAnimations.strike1:Stop()
slashAnimationConnection = animationsForAnimationController.swordAnimations.strike2.Stopped:connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = animationsForAnimationController.swordAnimations.strike2.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("swordAnimations", "strike2")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
elseif not animationsForAnimationController.swordAnimations.strike1.IsPlaying and (not animationsForAnimationController.swordAnimations.strike2.IsPlaying or isWithinSlash1Window) then
if canPlayerTripleSlash and animationsForAnimationController.swordAnimations.strike2.IsPlaying and animationsForAnimationController.swordAnimations.strike2.TimePosition >= animationsForAnimationController.swordAnimations.strike2.Length * 0.3 and animationsForAnimationController.swordAnimations.strike2.TimePosition <= animationsForAnimationController.swordAnimations.strike2.Length * 0.7 then
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.swordAnimations.strike2:Stop()
slashAnimationConnection = animationsForAnimationController.swordAnimations.strike3.Stopped:connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = animationsForAnimationController.swordAnimations.strike3.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("swordAnimations", "strike3")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
else
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.swordAnimations.strike2:Stop()
slashAnimationConnection = animationsForAnimationController.swordAnimations.strike1.Stopped:connect(function()
isWithinDamageSequence = false
isWithinSlash1Window = false
isWithinSlash2Window = false
end)
slashAnimationKeyframeConnection = animationsForAnimationController.swordAnimations.strike1.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("swordAnimations", "strike1")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
end
end
end
function staff:equip()
isWithinSlash1Window = false
isWithinSlash2Window = false
isWithinDamageSequence = false
isDamageSequenceEnabled = false
--local
myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientCharacterContainer then
currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
animationsForAnimationController = animationInterface:getAnimationsForAnimationController(myClientCharacterContainer.entity.AnimationController)
end
end
function staff:unequip()
end
local function onPropogationRequestToSelf(propogationNameTag, propogationValue)
if propogationNameTag == "abilities" then
playerAbilitiesSlotDataCollection = propogationValue
canPlayerDoubleSlash = doesPlayerHaveAbilityUnlocked(3)
canPlayerTripleSlash = doesPlayerHaveAbilityUnlocked(3, "tripleSlash")
end
end
local function main()
onPropogationRequestToSelf("abilities", network:invoke("getCacheValueByNameTag", "abilities"))
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:connect("characterStateChanged", "Event", onCharacterStateChanged)
end
main()
return staff
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/staff.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/staff_melee_old.lua
================================================
local staff = {}
staff.isEquipped = false
local userInputService = game:GetService("UserInputService")
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local abilityAnimations = game:GetService("ReplicatedStorage"):WaitForChild("abilityAnimations")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local detection = modules.load("detection")
local placeSetup = modules.load("placeSetup")
local currentDamageGUID = httpService:GenerateGUID(false)
local animationInterface = require(script.Parent.Parent.Parent:WaitForChild("contents"):WaitForChild("animationInterface"))--network:invoke("getPlayerCoreService", "animationInterface")
-- internal stuff specific to the staff
local animationControllerLoaded
local attackSequenceLength
local animationsForAnimationController
local slashAnimationConnection
local isWithinSlash1Window = false
local isWithinSlash2Window = false
local isWithinDamageSequence = false
local canPlayerDoubleSlash = false
local currentWeaponManifest
local myClientCharacterContainer
local playerAbilitiesSlotDataCollection
local player = game.Players.LocalPlayer
local isPlayerSprinting = false
local function onCharacterStateChanged(state, value)
if state == "isSprinting" then
isPlayerSprinting = value
end
end
local function doesPlayerHaveAbilityUnlocked(abilityId)
if playerAbilitiesSlotDataCollection then
for _, abilitySlotData in pairs(playerAbilitiesSlotDataCollection) do
if abilitySlotData.id == abilityId and abilitySlotData.rank > 0 then
return true
end
end
end
return false
end
local isDamageSequenceEnabled = false
local function startDamageSequencePolling()
if isDamageSequenceEnabled then return end
isDamageSequenceEnabled = true
while isDamageSequenceEnabled do
if animationsForAnimationController.swordAnimations.strike1.IsPlaying or animationsForAnimationController.swordAnimations.strike2.IsPlaying then
if isWithinDamageSequence then
network:invoke("performClientDamageCycle", "equipment", nil, currentDamageGUID)
end
wait(1 / 20)
else
break
end
end
isDamageSequenceEnabled = false
end
local function onSlashAnimationTrackStopped()
currentDamageGUID = httpService:GenerateGUID(false)
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
-- slash1PeriodStart
-- slash2PeriodStart
-- startDamageSequence
-- stopDamageSequence
local function onSlashAnimationKeyframeReached(keyframeName)
if keyframeName == "slash1PeriodStart" then
isWithinSlash1Window = true
delay(3 / 10, function()
isWithinSlash1Window = false
end)
elseif keyframeName == "slash2PeriodStart" then
isWithinSlash2Window = true
delay(3 / 10, function()
isWithinSlash2Window = false
end)
elseif keyframeName == "startDamageSequence" then
local swingSound = currentWeaponManifest:FindFirstChild("Swing")
if swingSound == nil then
swingSound = Instance.new("Sound")
swingSound.Volume = 1
swingSound.MaxDistance = 50
swingSound.SoundId = "rbxassetid://2069260907"
swingSound.Name = "Swing"
swingSound.Parent = currentWeaponManifest
end
swingSound:Play()
isWithinDamageSequence = true
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = true
end
elseif keyframeName == "stopDamageSequence" then
isWithinDamageSequence = false
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
end
function staff:attack()
-- make sure we can't slash if these conditions are true
if not animationsForAnimationController or not animationsForAnimationController.swordAnimations then
return
elseif isPlayerSprinting then
return
elseif not currentWeaponManifest then
return
elseif not player.Character or not player.Character.PrimaryPart or player.Character.PrimaryPart.state.Value == "dead" then
return
elseif animationsForAnimationController.swordAnimations.strike1.IsPlaying and (not isWithinSlash2Window or not canPlayerDoubleSlash)then --or not canPlayerDoubleSlash
return
elseif animationsForAnimationController.swordAnimations.strike2.IsPlaying and not isWithinSlash1Window then
return
end
if animationsForAnimationController.swordAnimations.strike1.IsPlaying and isWithinSlash2Window then
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.swordAnimations.strike1:Stop()
slashAnimationConnection = animationsForAnimationController.swordAnimations.strike2.Stopped:connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = animationsForAnimationController.swordAnimations.strike2.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("swordAnimations", "strike2")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
elseif not animationsForAnimationController.swordAnimations.strike1.IsPlaying and (not animationsForAnimationController.swordAnimations.strike2.IsPlaying or isWithinSlash1Window) then
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.swordAnimations.strike2:Stop()
slashAnimationConnection = animationsForAnimationController.swordAnimations.strike1.Stopped:connect(function()
isWithinDamageSequence = false
isWithinSlash1Window = false
isWithinSlash2Window = false
end)
slashAnimationKeyframeConnection = animationsForAnimationController.swordAnimations.strike1.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("swordAnimations", "strike1")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
end
end
function staff:equip()
isWithinSlash1Window = false
isWithinSlash2Window = false
isWithinDamageSequence = false
isDamageSequenceEnabled = false
--local
myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientCharacterContainer then
currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
animationsForAnimationController = animationInterface:getAnimationsForAnimationController(myClientCharacterContainer.entity.AnimationController)
-- local grip = myClientCharacterContainer.entity:FindFirstChild("Grip", true)
-- if grip then
-- -- force an update
-- onGripPropertyChanged(grip, "Part1")
-- end
end
end
function staff:unequip()
end
local function onPropogationRequestToSelf(propogationNameTag, propogationValue)
if propogationNameTag == "abilities" then
playerAbilitiesSlotDataCollection = propogationValue
canPlayerDoubleSlash = doesPlayerHaveAbilityUnlocked(3)
end
end
local function main()
onPropogationRequestToSelf("abilities", network:invoke("getCacheValueByNameTag", "abilities"))
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:connect("characterStateChanged", "Event", onCharacterStateChanged)
end
main()
return staff
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/staff_melee_old.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/staff_ranged_old.lua
================================================
local hitDebounceTable = {}
local staff = {}
staff.isEquipped = false
local userInputService = game:GetService("UserInputService")
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local detection = modules.load("detection")
local placeSetup = modules.load("placeSetup")
local configuration = modules.load("configuration")
local currentDamageGUID = httpService:GenerateGUID(false)
local animationInterface = require(script.Parent.Parent.Parent:WaitForChild("contents"):WaitForChild("animationInterface"))--network:invoke("getPlayerCoreService", "animationInterface")
-- internal stuff specific to the staff
local animationControllerLoaded
local attackSequenceLength
local animationsForAnimationController
local slashAnimationConnection
local myClientCharacterContainer
local isWithinSlash1Window = false
local isWithinSlash2Window = false
local isWithinDamageSequence = false
local canPlayerDoubleSlash = false
local currentWeaponManifest
local playerAbilitiesSlotDataCollection
local player = game.Players.LocalPlayer
local isPlayerSprinting = false
local function onCharacterStateChanged(state, value)
if state == "isSprinting" then
isPlayerSprinting = value
end
end
local function doesPlayerHaveAbilityUnlocked(abilityId)
if playerAbilitiesSlotDataCollection then
for _, abilitySlotData in pairs(playerAbilitiesSlotDataCollection) do
if abilitySlotData.id == abilityId and abilitySlotData.rank > 0 then
return true
end
end
end
return false
end
local isDamageSequenceEnabled = false
local function startDamageSequencePolling()
if isDamageSequenceEnabled then return end
isDamageSequenceEnabled = true
-- staff uses magic attack, no polling
--[[
while isDamageSequenceEnabled do
if animationsForAnimationController.staffAnimations.strike1.IsPlaying or animationsForAnimationController.staffAnimations.strike2.IsPlaying then
if isWithinDamageSequence then
-- todo: consider just using serverHitbox in `monsterManifestCollectionFolder` ?
network:invoke("performClientDamageCycle", "equipment", nil, currentDamageGUID)
end
wait(1 / 20)
else
break
end
end
]]
isDamageSequenceEnabled = false
end
local function onSlashAnimationTrackStopped()
currentDamageGUID = httpService:GenerateGUID(false)
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
-- currentWeaponManifest.Trail.Enabled = false
end
end
-- slash1PeriodStart
-- slash2PeriodStart
-- startDamageSequence
-- stopDamageSequence
local function onSlashAnimationKeyframeReached(keyframeName)
if keyframeName == "slash1PeriodStart" then
isWithinSlash1Window = true
delay(3 / 10, function()
isWithinSlash1Window = false
end)
elseif keyframeName == "slash2PeriodStart" then
isWithinSlash2Window = true
delay(3 / 10, function()
isWithinSlash2Window = false
end)
elseif keyframeName == "startDamageSequence" then
local swingSound = currentWeaponManifest:FindFirstChild("Swing")
if swingSound == nil then
swingSound = Instance.new("Sound")
swingSound.Volume = 1
swingSound.MaxDistance = 50
swingSound.SoundId = "rbxassetid://2069260907"
swingSound.Name = "Swing"
swingSound.Parent = currentWeaponManifest
end
-- swingSound:Play()
isWithinDamageSequence = true
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
-- currentWeaponManifest.Trail.Enabled = true
end
elseif keyframeName == "stopDamageSequence" then
isWithinDamageSequence = false
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
-- currentWeaponManifest.Trail.Enabled = false
end
end
end
function staff:attack()
-- make sure we can't slash if these conditions are true
if not animationsForAnimationController or not animationsForAnimationController.staffAnimations then
return
elseif isPlayerSprinting then
return
elseif not currentWeaponManifest then
return
elseif not player.Character or not player.Character.PrimaryPart or player.Character.PrimaryPart.state.Value == "dead" then
return
elseif animationsForAnimationController.staffAnimations.strike1.IsPlaying and (not isWithinSlash2Window or not canPlayerDoubleSlash)then
return
elseif animationsForAnimationController.staffAnimations.strike2.IsPlaying and not isWithinSlash1Window then
return
end
-- have to do it this way for now, no reference to ability animations in animationsForAnimationController
local animController = myClientCharacterContainer.entity.AnimationController
for i, track in pairs(animController:GetPlayingAnimationTracks()) do
if track.Name == "rock_throw_upper" or track.Name == "rock_throw_upper_loop" then
return
end
end
local abilityExecutionData = network:invoke("getAbilityExecutionData")
local manaBasicAttackCost = configuration.getConfigurationValue("mageManaDrainFromBasicAttack")
if player.Character.PrimaryPart.mana.Value < manaBasicAttackCost then
abilityExecutionData.noRangeManaAttack = true
end
if animationsForAnimationController.staffAnimations.strike1.IsPlaying and isWithinSlash2Window then
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.staffAnimations.strike1:Stop()
slashAnimationConnection = animationsForAnimationController.staffAnimations.strike2.Stopped:connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = animationsForAnimationController.staffAnimations.strike2.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicatePlayerAnimationSequence("staffAnimations", "strike2", nil, abilityExecutionData)
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
elseif not animationsForAnimationController.staffAnimations.strike1.IsPlaying and (not animationsForAnimationController.staffAnimations.strike2.IsPlaying or isWithinSlash1Window) then
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.staffAnimations.strike2:Stop()
slashAnimationConnection = animationsForAnimationController.staffAnimations.strike1.Stopped:connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = animationsForAnimationController.staffAnimations.strike1.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicatePlayerAnimationSequence("staffAnimations", "strike1", nil, abilityExecutionData)
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
end
end
function staff:equip()
isWithinSlash1Window = false
isWithinSlash2Window = false
isWithinDamageSequence = false
isDamageSequenceEnabled = false
myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientCharacterContainer then
currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
animationsForAnimationController = animationInterface:getAnimationsForAnimationController(myClientCharacterContainer.entity.AnimationController)
-- local grip = myClientCharacterContainer.entity:FindFirstChild("Grip", true)
-- if grip then
-- -- force an update
-- onGripPropertyChanged(grip, "Part1")
-- end
end
end
function staff:unequip()
end
local function onPropogationRequestToSelf(propogationNameTag, propogationValue)
if propogationNameTag == "abilities" then
playerAbilitiesSlotDataCollection = propogationValue
if doesPlayerHaveAbilityUnlocked(3) then
canPlayerDoubleSlash = true
else
canPlayerDoubleSlash = false
end
end
end
local function main()
onPropogationRequestToSelf("abilities", network:invoke("getCacheValueByNameTag", "abilities"))
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:connect("characterStateChanged", "Event", onCharacterStateChanged)
end
main()
return staff
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/staff_ranged_old.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/sword.lua
================================================
local sword = {}
sword.isEquipped = false
local userInputService = game:GetService("UserInputService")
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local abilityAnimations = game:GetService("ReplicatedStorage"):WaitForChild("assets"):WaitForChild("abilityAnimations")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local detection = modules.load("detection")
local placeSetup = modules.load("placeSetup")
local currentDamageGUID = httpService:GenerateGUID(false)
local animationInterface = require(script.Parent.Parent.Parent:WaitForChild("contents"):WaitForChild("animationInterface"))--network:invoke("getPlayerCoreService", "animationInterface")
-- internal stuff specific to the sword
local animationControllerLoaded
local attackSequenceLength
local animationsForAnimationController
local slashAnimationConnection
local isWithinSlash1Window = false
local isWithinSlash2Window = false
local isWithinDamageSequence = false
local canPlayerDoubleSlash = false
local canPlayerTripleSlash = false
local currentWeaponManifest
local myClientCharacterContainer
local playerAbilitiesSlotDataCollection
local player = game.Players.LocalPlayer
local isPlayerSprinting = false
local function onCharacterStateChanged(state, value)
if state == "isSprinting" then
isPlayerSprinting = value
end
end
local function doesPlayerHaveAbilityUnlocked(abilityId, variant)
if playerAbilitiesSlotDataCollection then
for _, abilitySlotData in pairs(playerAbilitiesSlotDataCollection) do
if abilitySlotData.id == abilityId and abilitySlotData.rank > 0 then
if variant then
return abilitySlotData.variant == variant
else
return true
end
end
end
end
return false
end
local isDamageSequenceEnabled = false
local function startDamageSequencePolling()
if isDamageSequenceEnabled then return end
isDamageSequenceEnabled = true
while isDamageSequenceEnabled do
if animationsForAnimationController.swordAnimations.strike1.IsPlaying or animationsForAnimationController.swordAnimations.strike2.IsPlaying or animationsForAnimationController.swordAnimations.strike3.IsPlaying then
if isWithinDamageSequence then
network:invoke("performClientDamageCycle", "equipment", nil, currentDamageGUID)
end
wait(1 / 20)
else
break
end
end
isDamageSequenceEnabled = false
end
local function onSlashAnimationTrackStopped()
currentDamageGUID = httpService:GenerateGUID(false)
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
-- slash1PeriodStart
-- slash2PeriodStart
-- startDamageSequence
-- stopDamageSequence
local function onSlashAnimationKeyframeReached(keyframeName)
if keyframeName == "slash1PeriodStart" then
isWithinSlash1Window = true
delay(3 / 10, function()
isWithinSlash1Window = false
end)
elseif keyframeName == "slash2PeriodStart" then
isWithinSlash2Window = true
delay(3 / 10, function()
isWithinSlash2Window = false
end)
elseif keyframeName == "startDamageSequence" then
local swingSound = currentWeaponManifest:FindFirstChild("Swing")
if swingSound == nil then
swingSound = Instance.new("Sound")
swingSound.Volume = 1
swingSound.MaxDistance = 50
swingSound.SoundId = "rbxassetid://2069260907"
swingSound.Name = "Swing"
swingSound.Parent = currentWeaponManifest
end
swingSound:Play()
isWithinDamageSequence = true
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = true
end
elseif keyframeName == "stopDamageSequence" then
isWithinDamageSequence = false
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
end
function sword:attack()
-- make sure we can't slash if these conditions are true
if not animationsForAnimationController or not animationsForAnimationController.swordAnimations then
return
elseif isPlayerSprinting then
return
elseif not currentWeaponManifest then
return
elseif not player.Character or not player.Character.PrimaryPart or player.Character.PrimaryPart.state.Value == "dead" then
return
elseif animationsForAnimationController.swordAnimations.strike1.IsPlaying and (not isWithinSlash2Window or not canPlayerDoubleSlash)then --or not canPlayerDoubleSlash
return
elseif animationsForAnimationController.swordAnimations.strike2.IsPlaying and not isWithinSlash1Window then
return
elseif animationsForAnimationController.swordAnimations.strike3.IsPlaying then
return
end
-- have to do it this way for now, no reference to ability animations in animationsForAnimationController
local animController = myClientCharacterContainer.entity.AnimationController
for i, track in pairs(animController:GetPlayingAnimationTracks()) do
if track.Name == "rock_throw_upper" or track.Name == "rock_throw_upper_loop" then
return
end
end
if animationsForAnimationController.swordAnimations.strike1.IsPlaying and isWithinSlash2Window then
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.swordAnimations.strike1:Stop()
slashAnimationConnection = animationsForAnimationController.swordAnimations.strike2.Stopped:connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = animationsForAnimationController.swordAnimations.strike2.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("swordAnimations", "strike2")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
elseif not animationsForAnimationController.swordAnimations.strike1.IsPlaying and (not animationsForAnimationController.swordAnimations.strike2.IsPlaying or isWithinSlash1Window) then
if canPlayerTripleSlash and animationsForAnimationController.swordAnimations.strike2.IsPlaying and animationsForAnimationController.swordAnimations.strike2.TimePosition >= animationsForAnimationController.swordAnimations.strike2.Length * 0.3 and animationsForAnimationController.swordAnimations.strike2.TimePosition <= animationsForAnimationController.swordAnimations.strike2.Length * 0.7 then
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.swordAnimations.strike2:Stop()
slashAnimationConnection = animationsForAnimationController.swordAnimations.strike3.Stopped:connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = animationsForAnimationController.swordAnimations.strike3.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("swordAnimations", "strike3")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
else
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.swordAnimations.strike2:Stop()
slashAnimationConnection = animationsForAnimationController.swordAnimations.strike1.Stopped:connect(function()
isWithinDamageSequence = false
isWithinSlash1Window = false
isWithinSlash2Window = false
end)
slashAnimationKeyframeConnection = animationsForAnimationController.swordAnimations.strike1.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("swordAnimations", "strike1")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
end
end
end
function sword:equip()
isWithinSlash1Window = false
isWithinSlash2Window = false
isWithinDamageSequence = false
isDamageSequenceEnabled = false
--local
myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientCharacterContainer then
currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
animationsForAnimationController = animationInterface:getAnimationsForAnimationController(myClientCharacterContainer.entity.AnimationController)
-- local grip = myClientCharacterContainer.entity:FindFirstChild("Grip", true)
-- if grip then
-- -- force an update
-- onGripPropertyChanged(grip, "Part1")
-- end
end
end
function sword:unequip()
end
local function onPropogationRequestToSelf(propogationNameTag, propogationValue)
if propogationNameTag == "abilities" then
playerAbilitiesSlotDataCollection = propogationValue
canPlayerDoubleSlash = doesPlayerHaveAbilityUnlocked(3)
canPlayerTripleSlash = doesPlayerHaveAbilityUnlocked(3, "tripleSlash")
end
end
local function main()
onPropogationRequestToSelf("abilities", network:invoke("getCacheValueByNameTag", "abilities"))
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:connect("characterStateChanged", "Event", onCharacterStateChanged)
end
main()
return sword
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/sword.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/damageInterfaces/swordAndShield.lua
================================================
local swordAndShield = {}
swordAndShield.isEquipped = false
local userInputService = game:GetService("UserInputService")
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local abilityAnimations = game:GetService("ReplicatedStorage"):WaitForChild("abilityAnimations")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local detection = modules.load("detection")
local placeSetup = modules.load("placeSetup")
local currentDamageGUID = httpService:GenerateGUID(false)
local animationInterface = require(script.Parent.Parent.Parent:WaitForChild("contents"):WaitForChild("animationInterface"))--network:invoke("getPlayerCoreService", "animationInterface")
-- internal stuff specific to the swordAndShield
local animationControllerLoaded
local attackSequenceLength
local animationsForAnimationController
local slashAnimationConnection
local isWithinSlash1Window = false
local isWithinSlash2Window = false
local isWithinDamageSequence = false
local canPlayerDoubleSlash = false
local canPlayerTripleSlash = false
local currentWeaponManifest
local myClientCharacterContainer
local playerAbilitiesSlotDataCollection
local player = game.Players.LocalPlayer
local isPlayerSprinting = false
local function onCharacterStateChanged(state, value)
if state == "isSprinting" then
isPlayerSprinting = value
end
end
local function doesPlayerHaveAbilityUnlocked(abilityId)
if playerAbilitiesSlotDataCollection then
for _, abilitySlotData in pairs(playerAbilitiesSlotDataCollection) do
if abilitySlotData.id == abilityId and abilitySlotData.rank > 0 then
return true
end
end
end
return false
end
local isDamageSequenceEnabled = false
local function startDamageSequencePolling()
if isDamageSequenceEnabled then return end
isDamageSequenceEnabled = true
while isDamageSequenceEnabled do
if animationsForAnimationController.swordAndShieldAnimations.strike1.IsPlaying or animationsForAnimationController.swordAndShieldAnimations.strike2.IsPlaying or animationsForAnimationController.swordAndShieldAnimations.strike3.IsPlaying then
if isWithinDamageSequence then
network:invoke("performClientDamageCycle", "equipment", nil, currentDamageGUID)
end
wait(1 / 20)
else
break
end
end
isDamageSequenceEnabled = false
end
local function onSlashAnimationTrackStopped()
currentDamageGUID = httpService:GenerateGUID(false)
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
-- slash1PeriodStart
-- slash2PeriodStart
-- startDamageSequence
-- stopDamageSequence
local function onSlashAnimationKeyframeReached(keyframeName)
if keyframeName == "slash1PeriodStart" then
isWithinSlash1Window = true
delay(3 / 10, function()
isWithinSlash1Window = false
end)
elseif keyframeName == "slash2PeriodStart" then
isWithinSlash2Window = true
delay(3 / 10, function()
isWithinSlash2Window = false
end)
elseif keyframeName == "startDamageSequence" then
local swingSound = currentWeaponManifest:FindFirstChild("Swing")
if swingSound == nil then
swingSound = Instance.new("Sound")
swingSound.Volume = 1
swingSound.MaxDistance = 50
swingSound.SoundId = "rbxassetid://2069260907"
swingSound.Name = "Swing"
swingSound.Parent = currentWeaponManifest
end
swingSound:Play()
isWithinDamageSequence = true
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = true
end
elseif keyframeName == "stopDamageSequence" then
isWithinDamageSequence = false
if currentWeaponManifest and currentWeaponManifest:FindFirstChild("Trail") then
currentWeaponManifest.Trail.Enabled = false
end
end
end
function swordAndShield:attack()
-- make sure we can't slash if these conditions are true
if not animationsForAnimationController or not animationsForAnimationController.swordAndShieldAnimations then
return
elseif isPlayerSprinting then
return
elseif not currentWeaponManifest then
return
elseif not player.Character or not player.Character.PrimaryPart or player.Character.PrimaryPart.state.Value == "dead" then
return
elseif animationsForAnimationController.swordAndShieldAnimations.strike1.IsPlaying and (not isWithinSlash2Window or not canPlayerDoubleSlash)then --or not canPlayerDoubleSlash
return
elseif animationsForAnimationController.swordAndShieldAnimations.strike2.IsPlaying and not isWithinSlash1Window then
return
elseif animationsForAnimationController.swordAndShieldAnimations.strike3.IsPlaying then
return
end
-- have to do it this way for now, no reference to ability animations in animationsForAnimationController
local animController = myClientCharacterContainer.entity.AnimationController
for i, track in pairs(animController:GetPlayingAnimationTracks()) do
if track.Name == "rock_throw_upper" or track.Name == "rock_throw_upper_loop" then
return
end
end
if animationsForAnimationController.swordAndShieldAnimations.strike1.IsPlaying and isWithinSlash2Window then
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.swordAndShieldAnimations.strike1:Stop()
slashAnimationConnection = animationsForAnimationController.swordAndShieldAnimations.strike2.Stopped:connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = animationsForAnimationController.swordAndShieldAnimations.strike2.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("swordAndShieldAnimations", "strike2")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
elseif not animationsForAnimationController.swordAndShieldAnimations.strike1.IsPlaying and (not animationsForAnimationController.swordAndShieldAnimations.strike2.IsPlaying or isWithinSlash1Window) then
if canPlayerTripleSlash and animationsForAnimationController.swordAndShieldAnimations.strike2.IsPlaying and animationsForAnimationController.swordAndShieldAnimations.strike2.TimePosition >= animationsForAnimationController.swordAndShieldAnimations.strike2.Length * 0.3 and animationsForAnimationController.swordAndShieldAnimations.strike2.TimePosition <= animationsForAnimationController.swordAndShieldAnimations.strike2.Length * 0.7 then
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.swordAndShieldAnimations.strike2:Stop()
slashAnimationConnection = animationsForAnimationController.swordAndShieldAnimations.strike3.Stopped:connect(onSlashAnimationTrackStopped)
slashAnimationKeyframeConnection = animationsForAnimationController.swordAndShieldAnimations.strike3.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("swordAndShieldAnimations", "strike3")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
else
if slashAnimationConnection then
slashAnimationConnection:disconnect()
slashAnimationConnection = nil
end
if slashAnimationKeyframeConnection then
slashAnimationKeyframeConnection:disconnect()
slashAnimationKeyframeConnection = nil
end
animationsForAnimationController.swordAndShieldAnimations.strike2:Stop()
slashAnimationConnection = animationsForAnimationController.swordAndShieldAnimations.strike1.Stopped:connect(function()
isWithinDamageSequence = false
isWithinSlash1Window = false
isWithinSlash2Window = false
end)
slashAnimationKeyframeConnection = animationsForAnimationController.swordAndShieldAnimations.strike1.KeyframeReached:connect(onSlashAnimationKeyframeReached)
animationInterface:replicateClientAnimationSequence("swordAndShieldAnimations", "strike1")
-- start damage sequence
currentDamageGUID = httpService:GenerateGUID(false)
spawn(startDamageSequencePolling)
end
end
end
function swordAndShield:equip()
isWithinSlash1Window = false
isWithinSlash2Window = false
isWithinDamageSequence = false
isDamageSequenceEnabled = false
--local
myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientCharacterContainer then
currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
animationsForAnimationController = animationInterface:getAnimationsForAnimationController(myClientCharacterContainer.entity.AnimationController)
-- local grip = myClientCharacterContainer.entity:FindFirstChild("Grip", true)
-- if grip then
-- -- force an update
-- onGripPropertyChanged(grip, "Part1")
-- end
end
end
function swordAndShield:unequip()
end
local function onPropogationRequestToSelf(propogationNameTag, propogationValue)
if propogationNameTag == "abilities" then
playerAbilitiesSlotDataCollection = propogationValue
canPlayerDoubleSlash = doesPlayerHaveAbilityUnlocked(3)
canPlayerTripleSlash = doesPlayerHaveAbilityUnlocked(30)
end
end
local function main()
onPropogationRequestToSelf("abilities", network:invoke("getCacheValueByNameTag", "abilities"))
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:connect("characterStateChanged", "Event", onCharacterStateChanged)
end
main()
return swordAndShield
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/defaultChestProps.lua
================================================
return {
chestModel = "defaultChest"
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/assets/init.meta.json
================================================
{
"ignoreUnknownInstances": true
}
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/client.client.lua
================================================
-- Master local script
local modules = {}
local player = game.Players.LocalPlayer
local PlayerScripts = script.Parent
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local directories = {ReplicatedStorage.modules, PlayerScripts.contents}
local beginInit = false
local function setup(directory)
for _, moduleScript in pairs(directory:GetDescendants()) do
if moduleScript:IsA("ModuleScript") then
print("$ client", "require module", moduleScript.Name)
modules[moduleScript.Name] = require(moduleScript)
end
end
end
local function initialize()
for moduleName, module in pairs(modules) do
if typeof(module) == "table" and (module.init and not module.__initialized) then
print("$ client", "initialize module", moduleName)
module.init(modules)
module.__initialized = true
end
end
end
for _, directory in pairs(directories) do
-- Get all static modules
setup(directory)
-- Ongoing support
directory.DescendantAdded:connect(function(moduleScript)
if moduleScript:IsA("ModuleScript") then
print("$ client", "require module", moduleScript.Name)
local module = require(moduleScript)
modules[moduleScript.Name] = module
if typeof(module) == "table" and module.init and beginInit then
print("$ client", "initialize module", moduleScript.Name)
module.init(modules)
end
end
end)
end
table.sort(modules, function(module1, module2)
return (module1.priority or 10) < (module2.priority or 10)
end)
beginInit = true
initialize()
-- Gui has to be done seperately unless we disable reloading (which is a bad idea)
-- Can't just use the DescendantAdded method either because init has to run after all requires
local function onCharacterAdded()
setup(player.PlayerGui)
initialize()
end
player.CharacterAdded:connect(onCharacterAdded)
local character = player.Character
if character then
onCharacterAdded()
end
print("$ client", "all modules in queue initialized")
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/ambience.lua
================================================
local module = {}
-- Manages sounds and some aesthetics.
-- One of the first local scripts made for Vesteria. Not clean, needs improvements
-- Author: berezaa
if game.ReplicatedStorage:FindFirstChild("overrideAmbience") then
return
end
local network
local tween
local userSettings
local tracks = {}
local dead
local step = 1/5
local lastUpdate
local easing = Enum.EasingStyle.Linear
local camera = workspace.CurrentCamera
local function addTrack(track)
track.Parent = camera
table.insert(tracks,track)
track.Volume = 0
track.Looped = true
end
local function mergeColors(dayColor, nightColor, Brightness)
local dr, dg, db = Color3.toHSV(dayColor)
local nr, ng, nb = Color3.toHSV(nightColor)
return Color3.fromHSV(nr + (dr - nr) * Brightness, ng + (dg - ng) * Brightness, nb + (db - nb) * Brightness)
end
local function lightingUpdate()
local light = game.ReplicatedStorage:FindFirstChild("lightingSettings")
local dayAmbient = light and light:FindFirstChild("dayAmbient") and light.dayAmbient.Value or (Color3.fromRGB(100, 100, 100))
local nightAmbient = light and light:FindFirstChild("nightAmbient") and light.nightAmbient.Value or Color3.fromRGB(50, 50, 100)
local ClockTime = game.Lighting.ClockTime
local Brightness = 0
-- Night
if ClockTime < 5.0 or ClockTime > 18.5 then
Brightness = 0
-- Sunrise
elseif ClockTime >= 5.0 and ClockTime <= 6.5 then
local Progress = (ClockTime - 5.0) / 1.5
Brightness = Progress
-- Sunset
elseif ClockTime >= 17.5 and ClockTime <= 18.5 then
local Progress = (ClockTime - 17.5)
Brightness = 1 - Progress
-- Day
else
Brightness = 1
end
if lastUpdate then
step = tick() - lastUpdate
end
local newTime = game.ReplicatedStorage.timeOfDay.Value
if newTime < ClockTime then
game.Lighting.ClockTime = newTime
else
tween(game.Lighting, {"ClockTime"}, newTime, step, easing)
end
if Brightness ~= PreviousBrightness then
local dayFogColor = light and light:FindFirstChild("dayFogColor") and light.dayFogColor.Value or Color3.fromRGB(151, 213, 214)
local nightFogColor = light and light:FindFirstChild("nightFogColor") and light.nightFogColor.Value or Color3.fromRGB(0, 66, 120)
local ambientColor = mergeColors(dayAmbient, nightAmbient, Brightness)
local fogColor = mergeColors(dayFogColor, nightFogColor, Brightness)
tween(game.Lighting, {"Ambient", "FogColor", "ExposureCompensation"}, {ambientColor, fogColor, Brightness}, step, easing)
tween(game.Lighting.Atmosphere, {"Density", "Color", "Haze", "Glare"}, {0.438 - 0.164 * Brightness, fogColor, 2.15 - 2.15 * Brightness, 10 * Brightness}, step, easing)
end
Brightness = PreviousBrightness
lastUpdate = tick()
end
--game.Lighting:GetPropertyChangedSignal("ClockTime"):connect(lightingUpdate)
game.SoundService:GetPropertyChangedSignal("AmbientReverb"):connect(function(Value)
if game.SoundService.AmbientReverb == Enum.ReverbType.UnderWater then
if game.SoundService:FindFirstChild("Underwater") then
for i, track in pairs(tracks) do
track.SoundGroup = game.SoundService.Underwater
end
end
else
for i, track in pairs(tracks) do
track.SoundGroup = nil
end
end
end)
local currentTrack = ""
local musicVolume = 0.5
local function setMusicVolume(volume)
musicVolume = 1 * volume
for i,track in pairs(tracks) do
if track.Name == currentTrack then
track.Volume = musicVolume * 0.27
end
end
end
local function playTrack(trackName)
if currentTrack ~= trackName then
currentTrack = trackName
for _, track in pairs(tracks) do
if track.Name == trackName then
track:Play()
track.Volume = musicVolume * 0.27
elseif track.Volume > 0 then
track:Stop()
track.Volume = 0
end
end
end
end
local overriden = false
local function overrideTrack(track)
if not overriden then
overriden = true
addTrack(track)
playTrack(track.Name)
end
end
if game.ReplicatedStorage:FindFirstChild("backgroundMusic") then
overrideTrack(game.ReplicatedStorage.backgroundMusic)
else
playTrack("Village")
end
game.ReplicatedStorage.ChildAdded:connect(function(child)
if child.Name == "backgroundMusic" then
overrideTrack(child)
end
end)
local noise = Instance.new("Sound")
noise.Parent = script
noise.Volume = 0.1
noise.Looped = true
noise.Name = "noise"
local function setNoise(soundId)
if noise.SoundId ~= soundId then
noise:Stop()
noise.SoundId = soundId
noise:Play()
end
end
local function backgroundNoise()
if game.PlaceId == 3232913902 or game.PlaceId == 2544075708 then return end -- crabby den and shiprock bottom. no crickets at these places
if game.Lighting.ClockTime <= 6.5 or game.Lighting.ClockTime >= 18 then
setNoise("rbxassetid://"..2049803364)
noise.Volume = 0.27
else
if workspace:FindFirstChild("forest") then
setNoise("rbxassetid://"..2050179392)
noise.Volume = 0.4
else
setNoise("rbxassetid://"..2050176819)
noise.Volume = 0.75
end
end
end
local function onDataUpdate(key, value)
if key == "userSettings" then
userSettings = value
setMusicVolume(value.musicVolume or 0.5)
end
end
local function isNight()
return game.Lighting.ClockTime < 5.9 or game.Lighting.ClockTime > 18.6
end
-- Sunrise: 5.6 - 6.6
-- Sunset: 17.6 - 18.6
local function timeOfDayPitch()
if game.Lighting.ClockTime < 5.9 or game.Lighting.ClockTime > 18.6 then
return 1--0.8
elseif game.Lighting.ClockTime >= 5.6 and game.Lighting.ClockTime <= 6.6 then
return 1--0.8 + 0.2 * (game.Lighting.ClockTime - 5.6)
elseif game.Lighting.ClockTime >= 17.6 and game.Lighting.ClockTime <= 18.6 then
return 1-- - 0.2 * (game.Lighting.ClockTime - 17.6)
else
return 1
end
end
local function setIsDead(isDead)
dead = isDead
for i,track in pairs(tracks) do
track.PlaybackSpeed = (dead and 0.4 or timeOfDayPitch())
end
end
local function main()
workspace:WaitForChild("Camera")
local assetFolder = script.Parent.Parent:WaitForChild("assets")
camera = workspace.CurrentCamera
for _, child in pairs(assetFolder.tracks:GetChildren()) do
addTrack(child)
end
if userSettings.musicVolume then
setMusicVolume(userSettings.musicVolume or 0.5)
end
game.ReplicatedStorage.timeOfDay.Changed:connect(lightingUpdate)
assetFolder.tracks.ChildAdded:Connect(addTrack)
lightingUpdate()
backgroundNoise()
game.Lighting:GetPropertyChangedSignal("ClockTime"):connect(backgroundNoise)
while wait(1) do
for i,track in pairs(tracks) do
track.PlaybackSpeed = (dead and 0.4 or timeOfDayPitch())
end
end
end
function module.init(Modules)
network = Modules.network
tween = Modules.tween
userSettings = network:invoke("getCacheValueByNameTag", "userSettings")
network:create("musicVolumeChanged", "BindableEvent", "Event", setMusicVolume)
network:create("ambienceSetIsDead", "BindableFunction", "OnInvoke", setIsDead)
network:connect("propogationRequestToSelf", "Event", onDataUpdate)
spawn(main)
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/animationInterface.lua
================================================
-- Author: Polymorphic
local module = {}
module.clientAnimations = {}
local assetFolder = script.Parent.Parent:WaitForChild("assets")
-- module requires module!!!!!!! todo: maybe change this into a service?
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local characterAnimations = replicatedStorage:WaitForChild("characterAnimations")
local animationsContainer = {}
local availableAnimationSequences = {}
local rawAnimationData = {} do
for i, animationDataCollectionModule in pairs(assetFolder.animations:GetChildren()) do
local animationDataCollection = require(animationDataCollectionModule)
for animationName, animationData in pairs(animationDataCollection) do
if animationData.animationId and not animationData.animationId_2 then
local animation = Instance.new("Animation")
animation.AnimationId = animationData.animationId
animation.Name = animationName
if not characterAnimations:FindFirstChild(animationName) then
animation.Parent = characterAnimations
end
animationData.animation = animation
elseif animationData.animationId and animationData.animationId_2 then
local animation = Instance.new("Animation")
animation.AnimationId = animationData.animationId
animation.Name = animationName
local animation_2 = Instance.new("Animation")
animation_2.AnimationId = animationData.animationId_2
animation_2.Name = animationName .. "_2"
if not characterAnimations:FindFirstChild(animation.Name) then
animation.Parent = characterAnimations
end
if not characterAnimations:FindFirstChild(animation_2.Name) then
animation_2.Parent = characterAnimations
end
animationData.animation = {animation; animation_2}
end
end
rawAnimationData[animationDataCollectionModule.Name] = animationDataCollection
end
end
local function getSingleAnimation(animationController, animationGroup, animationName)
if rawAnimationData[animationGroup] and rawAnimationData[animationGroup][animationName] then
local animationData = rawAnimationData[animationGroup][animationName]
if typeof(animationData.animation) == "Instance" then
local animationTrack = animationController:LoadAnimation(animationData.animation)
animationTrack.Priority = animationData.priority or Enum.AnimationPriority.Movement
animationTrack.Looped = animationData.looped or false
animationTrack.Name = animationName
animationTrack:AdjustSpeed(animationData.speed or 1)
return animationTrack
elseif typeof(animationData.animation) == "table" then
local animationTrack = animationController:LoadAnimation(animationData.animation[1])
animationTrack.Priority = animationData.priority or Enum.AnimationPriority.Movement
animationTrack.Looped = animationData.looped or false
animationTrack.Name = animationName
local animationTrack_2 = animationController:LoadAnimation(animationData.animation[2])
animationTrack_2.Priority = --[[animationData.priority_2 or]] animationData.priority or Enum.AnimationPriority.Movement
animationTrack_2.Looped = animationData.looped or false
animationTrack_2.Name = animationName
animationTrack:AdjustSpeed(animationData.speed or 1)
return {animationTrack; animationTrack_2}
end
end
return nil
end
local function getSingleAnimationCluster(animationsName, animationController)
local animationTable = {}
for animationName, animationData in pairs(rawAnimationData[animationsName]) do
if typeof(animationData.animation) == "Instance" then
local animationTrack = animationController:LoadAnimation(animationData.animation)
animationTrack.Priority = animationData.priority or Enum.AnimationPriority.Movement
animationTrack.Looped = animationData.looped or false
animationTrack.Name = animationName
animationTrack:AdjustSpeed(animationData.speed or 1)
animationTable[animationName] = animationTrack
elseif typeof(animationData.animation) == "table" then
local animationTrack = animationController:LoadAnimation(animationData.animation[1])
animationTrack.Priority = animationData.priority or Enum.AnimationPriority.Movement
animationTrack.Looped = animationData.looped or false
animationTrack.Name = animationName
local animationTrack_2 = animationController:LoadAnimation(animationData.animation[2])
animationTrack_2.Priority = --[[animationData.priority_2 or]] animationData.priority or Enum.AnimationPriority.Movement
animationTrack_2.Looped = animationData.looped or false
animationTrack_2.Name = animationName
animationTrack:AdjustSpeed(animationData.speed or 1)
animationTrack_2:AdjustSpeed(animationData.speed or 1)
animationTable[animationName] = {animationTrack; animationTrack_2}
end
end
return animationTable
end
function module:getAnimationsForAnimationController(animationController, ...)
local animationTable = {}
local animationsWanted = { ... }
for i, animationDataCollectionName in pairs(animationsWanted) do
if rawAnimationData[animationDataCollectionName] then
animationTable[animationDataCollectionName] = getSingleAnimationCluster(animationDataCollectionName, animationController)
end
end
return animationTable
end
function module:registerAnimationsForAnimationController(animationController, ...)
local animationTable = {}
local animationsWanted = { ... }
for i, animationDataCollectionName in pairs(animationsWanted) do
if rawAnimationData[animationDataCollectionName] then
animationTable[animationDataCollectionName] = getSingleAnimationCluster(animationDataCollectionName, animationController)
end
end
animationsContainer[animationController] = animationTable
return animationTable
end
function module:getAnimationsForAnimationController(animationController)
return animationsContainer[animationController]
end
function module:deregisterAnimationsForAnimationController(animationController)
if animationController then
animationsContainer[animationsContainer] = nil
end
end
function module:stopPlayingAnimationsByAnimationCollectionName(animationTable, animationCollectionName)
for i, animationTrack in pairs(animationTable[animationCollectionName]) do
if typeof(animationTrack) == "Instance" then
animationTrack:Stop()
elseif typeof(animationTrack) == "table" then
for ii, obj in pairs(animationTrack) do
obj:Stop()
end
end
end
end
function module:stopPlayingAnimationsByAnimationCollectionNameWithException(animationTable, animationCollectionName, exception)
if animationTable[animationCollectionName] then
for i, animationTrack in pairs(animationTable[animationCollectionName]) do
if typeof(animationTrack) == "Instance" then
if not exception or animationTrack.Name ~= exception then
animationTrack:Stop()
end
elseif typeof(animationTrack) == "table" then
if not exception or animationTrack[1].Name ~= exception then
for ii, obj in pairs(animationTrack) do
obj:Stop()
end
end
end
end
end
end
function module:replicateNonPlayerAnimationSequence(animationCollectionName, animationName, callback, extraData)
if module.clientAnimations[animationCollectionName] and module.clientAnimations[animationCollectionName][animationName] then
self:stopPlayingAnimationsByAnimationCollectionName(module.clientAnimations, "emoteAnimations")
network:fire("playNonPlayerAnimationSequence", animationCollectionName, animationName)
if callback and typeof(module.clientAnimations[animationCollectionName][animationName]) == "Instance" then
local connection
connection = module.clientAnimations[animationCollectionName][animationName].Stopped:connect(function()
connection:disconnect()
callback()
end)
end
end
if rawAnimationData[animationCollectionName] and rawAnimationData[animationCollectionName][animationName] then
network:fireServer("replicateNonPlayerAnimationSequence", animationCollectionName, animationName, extraData)
end
end
function module:replicatePlayerAnimationSequence(animationCollectionName, animationName, callback, extraData)
-- todo: remove all calls so no more redirect?
module:replicateClientAnimationSequence(animationCollectionName, animationName, callback, extraData)
end
local ATTACK_SPEED_ANIMATION_COLLECTION_NAMES = {
"swordAnimations",
"staffAnimations",
"daggerAnimations",
"bowAnimations",
"greatswordAnimations",
"dualAnimations",
"swordAndShieldAnimations",
"axeAnimations",
"pickaxeAnimations"
}
local function isAttackSpeedAnimationCollection(name)
for _, attackSpeedAnimationCollectionName in pairs(ATTACK_SPEED_ANIMATION_COLLECTION_NAMES) do
if attackSpeedAnimationCollectionName == name then
return true
end
end
return false
end
function module:replicateClientAnimationSequence(animationCollectionName, animationName, callback, extraData)
if rawAnimationData[animationCollectionName] and rawAnimationData[animationCollectionName][animationName] then
if isAttackSpeedAnimationCollection(animationCollectionName) then
extraData = extraData or {}
extraData.attackSpeed = network:invoke("getCacheValueByNameTag", "nonSerializeData").statistics_final.attackSpeed or 0
end
network:fire("playPlayerAnimationSequenceOnClientCharacter", animationCollectionName, animationName, extraData)
network:fireServer("replicatePlayerAnimationSequence", animationCollectionName, animationName, extraData)
end
end
function module:getPlayingAnimationTracks()
local myClientPlayerCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientPlayerCharacterContainer then
return myClientPlayerCharacterContainer.entity.AnimationController:GetPlayingAnimationTracks()
end
return {}
end
local function regenerateClientAnimations(myClientPlayerCharacterContainer)
local animationController = (myClientPlayerCharacterContainer or network:invoke("getMyClientCharacterContainer")).entity.AnimationController
module.clientAnimations = {}
for animationDataCollectionName, animationCollection in pairs(rawAnimationData) do
-- do not automatically register this animation
if not string.match(animationDataCollectionName, "_noChar") then
module.clientAnimations[animationDataCollectionName] = getSingleAnimationCluster(animationDataCollectionName, animationController)
end
end
end
local function main()
module.getSingleAnimation = getSingleAnimation
module.getSingleAnimationCluster = getSingleAnimationCluster
module.rawAnimationData = rawAnimationData
-- generate animationData for player
local myClientPlayerCharacterContainer = network:invoke("getMyClientCharacterContainer")
if myClientPlayerCharacterContainer then
regenerateClientAnimations(myClientPlayerCharacterContainer)
end
spawn(function()
network:connect("myClientCharacterContainerChanged", "Event", regenerateClientAnimations)
end)
end
spawn(main)
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/bolt.lua
================================================
-- Funny bolt ripped from magic missile
-- I want to use this for EXP orbs when you kill a monster
local bolt = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local runService = game:GetService("RunService")
local modules = require(replicatedStorage:WaitForChild("modules"))
local placeSetup = modules.load("placeSetup")
local network = modules.load("network")
local levels = modules.load("levels")
local tween = modules.load("tween")
local spawnRegionCollectionsFolder = placeSetup.getPlaceFolder("spawnRegionCollections")
local entityManifestCollectionFolder = placeSetup.getPlaceFolder("entityManifestCollection")
local entityRenderCollectionFolder = placeSetup.getPlaceFolder("entityRenderCollection")
local itemsFolder = placeSetup.getPlaceFolder("items")
local entitiesFolder = placeSetup.getPlaceFolder("entities")
local foilage = placeSetup.getPlaceFolder("foilage")
local RAYCAST_IGNORE_LIST = {
spawnRegionCollectionsFolder,
entityManifestCollectionFolder,
entityRenderCollectionFolder,
itemsFolder,
entitiesFolder,
foilage
}
local rand = Random.new()
local assets = replicatedStorage:WaitForChild("assets")
local entities = assets:WaitForChild("entities")
function bolt.fire(launchCFrame, target, size)
-- constants for the function
local contactDistanceSq = 1
local missileTemplate = entities:FindFirstChild("missile")
assert(missileTemplate, "missile for exp bolts not found")
-- create a missile and keep track of it
local missile = missileTemplate:Clone()
missile.Size = Vector3.new(1, 1, 1) * size
missile.top.Position = Vector3.new(0, -size/2, 0)
missile.bottom.Position = Vector3.new(0, size/2, 0)
missile.CFrame = launchCFrame * missile.alignAttachment.CFrame:Inverse()
local mover = missile.mover
local orientationAttachment = missile.orientationAttachment
local align = missile.alignOrientation
local trail = missile.trail
orientationAttachment.CFrame = launchCFrame
orientationAttachment.Parent = workspace.Terrain
missile.Parent = entitiesFolder
-- this data is used to update
-- what the missile does in flight
local boltData = {
speed = 15,
missile = missile,
target = target,
startTime = tick(),
-- which way the missile will drift while searching for a target
driftCFrame = CFrame.Angles(math.pi * 2 * rand:NextNumber(), 0, math.pi * 2 * rand:NextNumber()),
}
local offset = Vector3.new(rand:NextNumber() - 0.5, rand:NextNumber() - 0.5, rand:NextNumber() - 0.5) * 2
local connection
local finished
local function finish(wasSuccess)
if not finished then
finished = true
missile.Anchored = true
trail.Enabled = false
tween(missile, {"Transparency", "Size"}, {1, missile.Size * 5}, 0.7)
delay(0.7, function()
connection:Disconnect()
orientationAttachment:Destroy()
missile:Destroy()
end)
end
end
local function checkForCollision()
local origin = missile.Position
local direction = missile.CFrame.LookVector/2
local ray = Ray.new(origin, direction)
local part = workspace:FindPartOnRayWithIgnoreList(ray, RAYCAST_IGNORE_LIST, false, true)
return part ~= nil and part.CanCollide
end
-- run every frame while the missile is airborne
local function update(dt)
-- move in the direction we're facing
if finished then
missile.CFrame = CFrame.new(boltData.target.Position + offset)
else
mover.Velocity = (missile.CFrame * missile.alignAttachment.CFrame).LookVector * boltData.speed
-- accelerate!
boltData.speed = boltData.speed + (dt or 0) * 15
align.MaxAngularVelocity = align.MaxAngularVelocity + (dt or 0) * 7
-- if we have a target, rotate towards it and run logic if we hit
local directionCFrame = CFrame.new(missile.Position, boltData.target.Position + offset)
orientationAttachment.CFrame = directionCFrame
local delta = (boltData.target.Position + offset) - missile.Position
local distanceSq =
delta.X * delta.X +
delta.Y * delta.Y +
delta.Z * delta.Z
local size = boltData.target.Size / 4
local sizeSq =
size.X * size.X +
size.Y * size.Y +
size.Z * size.Z
if distanceSq <= math.max(contactDistanceSq, sizeSq) then
finish(true)
end
-- if we hit something we're done
if checkForCollision() then
finish(false)
end
end
end
connection = runService.Heartbeat:Connect(update)
update()
-- failsafe
delay(5, function()
finish(false)
end)
return boltData
end
network:connect("signal_exp", "OnClientEvent", function(EXPTable, sourcePart)
local myCharacter = game.Players.LocalPlayer.Character
myCharacter = myCharacter and myCharacter.PrimaryPart
if myCharacter then
for playerName, EXP in pairs(EXPTable) do
local player = game.Players:FindFirstChild(playerName)
local character = player.Character and player.Character.PrimaryPart
if character and ((character == myCharacter) or (character.Position - myCharacter.Position).magnitude <= 150) then
local level = player.level.Value
local needed = levels.getEXPToNextLevel(level)
local bolts = math.ceil((EXP / needed) * (6 + level * 3))
for _ = 1, bolts do
delay(rand:NextNumber() * 0.5, function()
local origin = sourcePart.Position
local offset = Vector3.new(3 * (rand:NextNumber() - 0.5), 5, 3 * (rand:NextNumber() - 0.5))
local lookat = sourcePart.Position + offset
bolt.fire(CFrame.new(origin, lookat), character, 0.3 * EXP ^ (1/3))
end)
end
end
end
end
end)
return bolt
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/cameraScript.lua
================================================
local module = {}
-- Vesteria custom camera to support custom characters
-- Authors: Polymorphic, berezaa
local RunService = game:GetService("RunService")
local camera = workspace.CurrentCamera
local UserInputService = game:GetService("UserInputService")
local player = game.Players.LocalPlayer
local character
local zoom = 10
game.Players.LocalPlayer.CameraMinZoomDistance = 5
local network
local tween
local camera_shaker
local ddy = 0
local ddx = 0
local mobileCameraRotation
local xRotation = -math.rad(20)
local yRotation = -math.rad(20)
local maxYRotation = math.rad(90)
local maxXRotation = math.rad(80)
local fullRotation = math.rad(720)
local zoomIncrement = 3
local overridden = false
local IS_PLAYER_CAMERA_LOCKED = false
local function raycastDownIgnoreCancollideFalse(ray, ignoreList)
local hitPart, hitPosition, hitDown, hitMaterial = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList, true)
local items = workspace.placeFolders:FindFirstChild("items")
-- edit: ignore water and dropped items
while hitPart and not (hitPart.CanCollide and hitMaterial ~= Enum.Material.Water and (items == nil or not hitPart:IsDescendantOf(items))) do
ignoreList[#ignoreList + 1] = hitPart
hitPart, hitPosition, hitDown, hitMaterial = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList, true)
end
return hitPart, hitPosition, hitDown, hitMaterial
end
-------
local zoomCFrame = CFrame.new(0, 0, zoom)
local Par = workspace.CurrentCamera
if game.PlaceId == 2376885433 or game.PlaceId == 2015602902 or game.PlaceId == 4623219432 then
Par = game.ReplicatedStorage
end
local cameraCFrameVal = Instance.new("CFrameValue")
cameraCFrameVal.Name = "CFrameValue"
cameraCFrameVal.Parent = Par
local lockTarget
local rotationLocked
local function lockCameraTarget(target)
lockTarget = target
rotationLocked = false
if target then
if workspace.CurrentCamera:FindFirstChild("overridden") == nil then
local tag = Instance.new("BoolValue")
tag.Name = "overridden"
tag.Parent = workspace.CurrentCamera
end
else
if workspace.CurrentCamera:FindFirstChild("overridden") then
workspace.CurrentCamera.overridden:Destroy()
end
end
end
local function lockCameraTargetWithOrientation(target, xRotate, yRotate, zoomCf)
lockTarget = target
rotationLocked = false
if target then
if workspace.CurrentCamera:FindFirstChild("overridden") == nil then
local tag = Instance.new("BoolValue")
tag.Name = "overridden"
tag.Parent = workspace.CurrentCamera
end
rotationLocked = true
xRotation = xRotate or xRotation
yRotation = yRotate or yRotation
zoomCFrame = zoomCf or zoomCFrame
else
if workspace.CurrentCamera:FindFirstChild("overridden") then
workspace.CurrentCamera.overridden:Destroy()
end
end
end
local function updateCamera(step)
if not character or not character.PrimaryPart then return end
local characterCFrame do
if IS_PLAYER_CAMERA_LOCKED then
characterCFrame = CFrame.new(character.PrimaryPart.Position) + Vector3.new(0, 0.25 + 0.05 * zoom, 0) + (CFrame.new(Vector3.new(), camera.CFrame.RightVector) * CFrame.Angles(0, math.rad(10), 0)).lookVector * 1.75
else
characterCFrame = CFrame.new(character.PrimaryPart.Position) + Vector3.new(0, 0.25 + 0.05 * zoom, 0)
end
end
if lockTarget then
characterCFrame = CFrame.new(lockTarget.Position) + Vector3.new(0,1,0)
end
if not overridden then
local intendedCFrame = (characterCFrame * CFrame.Angles(0, yRotation, 0) * CFrame.Angles(xRotation, 0, 0)) * zoomCFrame
local ignoreList = {workspace.CurrentCamera}
if workspace:FindFirstChild("placeFolders") then
table.insert(ignoreList, workspace.placeFolders)
end
local direction = intendedCFrame.Position - characterCFrame.Position
local ray = Ray.new(characterCFrame.Position, direction)
--local hitPart, hitPosition = game.Workspace:FindPartOnRayWithIgnoreList(ray,ignoreList,false,true)
local hitPart, hitPosition = raycastDownIgnoreCancollideFalse(ray, ignoreList)
--tween(camera,{"CFrame"},intendedCFrame,0.1)
cameraCFrameVal.Value = CFrame.new(hitPosition - direction.unit, characterCFrame.p)
end
camera.Focus = camera.CFrame
end
local function lockCamera(cf, duration, easeStyle)
if cf then
overridden = true
if duration then
tween(cameraCFrameVal,{"Value"},cf,duration,easeStyle)
else
cameraCFrameVal.Value = cf
end
if workspace.CurrentCamera:FindFirstChild("overridden") == nil then
local tag = Instance.new("BoolValue")
tag.Name = "overridden"
tag.Parent = workspace.CurrentCamera
end
else
if workspace.CurrentCamera:FindFirstChild("overridden") then
workspace.CurrentCamera.overridden:Destroy()
end
overridden = false
end
end
local primarycamera_shaker
local function cameraShake(preset)
local camShake = primarycamera_shaker
if preset == nil then
camShake:Shake(camera_shaker.Presets.Explosion)
elseif preset == "bump" then
camShake:Shake(camera_shaker.Presets.Bump)
end
end
-- at the moment can only be played during a cutscene
local function lockCameraWithCameraShake(cf, duration, timeUntilExplosion, easeStyle, preset, explodeDuration)
if cf then
overridden = true
if duration then
tween(camera,{"CFrame"},cf,duration,easeStyle)
spawn(function()
wait(timeUntilExplosion)
--[[
local camShake = camera_shaker.new(Enum.RenderPriority.Camera.Value, function(shakeCf)
camera.CFrame = cf * shakeCf
end)
camShake:Start()
]]
local camShake = primarycamera_shaker
if preset == nil then
camShake:Shake(camera_shaker.Presets.Explosion)
elseif preset == "bump" then
camShake:Shake(camera_shaker.Presets.Bump)
end
--[[
wait(explodeDuration or 4)
camShake:Stop()
]]
end)
else
cameraCFrameVal.Value = cf
end
if workspace.CurrentCamera:FindFirstChild("overridden") == nil then
local tag = Instance.new("BoolValue")
tag.Name = "overridden"
tag.Parent = workspace.CurrentCamera
end
else
if workspace.CurrentCamera:FindFirstChild("overridden") then
workspace.CurrentCamera.overridden:Destroy()
end
overridden = false
end
end
local lockOrigin
local function onInputBegan(inputObject, Absorbed)
if Absorbed then
return false
end
if inputObject.KeyCode == Enum.KeyCode.ButtonR3 then
zoom = zoom - 7
if zoom < player.CameraMinZoomDistance then
zoom = player.CameraMaxZoomDistance
end
if not rotationLocked then
zoomCFrame = CFrame.new(0, 0, zoom)
end
end
if inputObject.UserInputType == Enum.UserInputType.MouseButton2 then
lockOrigin = inputObject.Position
elseif inputObject.KeyCode == Enum.KeyCode.Left then
while inputObject.UserInputState ~= Enum.UserInputState.Cancel and inputObject.UserInputState ~= Enum.UserInputState.End do
game:GetService("RunService").RenderStepped:wait()
yRotation = (yRotation + 0.04) % fullRotation
end
elseif inputObject.KeyCode == Enum.KeyCode.Right then
while inputObject.UserInputState ~= Enum.UserInputState.Cancel and inputObject.UserInputState ~= Enum.UserInputState.End do
game:GetService("RunService").RenderStepped:wait()
yRotation = (yRotation - 0.04) % fullRotation
end
elseif inputObject.KeyCode == Enum.KeyCode.I then
zoom = math.clamp(zoom - 7, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
-- update the zoomCFrame
if not rotationLocked then
zoomCFrame = CFrame.new(0, 0, zoom)
end
elseif inputObject.KeyCode == Enum.KeyCode.O then
zoom = math.clamp(zoom + 7, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
-- update the zoomCFrame
if not rotationLocked then
zoomCFrame = CFrame.new(0, 0, zoom)
end
end
end
local function onInputChanged(inputObject, absorbed)
if absorbed then
return false
end
if inputObject.UserInputType == Enum.UserInputType.MouseMovement and (IS_PLAYER_CAMERA_LOCKED or lockOrigin) then
local yConversion = inputObject.Delta.X / 5
local xConversion = inputObject.Delta.Y / 5
if not rotationLocked then
xRotation = math.clamp(xRotation - math.rad(xConversion), -maxXRotation, maxXRotation)
yRotation = (yRotation - math.rad(yConversion)) % fullRotation
end
elseif inputObject.UserInputType == Enum.UserInputType.MouseWheel then
zoom = math.clamp(zoom - ( inputObject.Position.Z) , player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
-- update the zoomCFrame
if not rotationLocked then
zoomCFrame = CFrame.new(0, 0, zoom)
end
end
end
local function onInputEnded(inputObject)
if inputObject.UserInputType == Enum.UserInputType.MouseButton2 then
lockOrigin = nil
elseif inputObject.KeyCode == Enum.KeyCode.Left then
end
end
local function mobileCameraRotationChanged(rotation)
mobileCameraRotation = rotation
end
local function step()
if mobileCameraRotation and mobileCameraRotation.magnitude > 0.1 then
local dy = mobileCameraRotation.Y
local dx = mobileCameraRotation.X
if math.abs(dy) < 0.1 then
dy = 0
ddy = 0
elseif math.abs(dy) > 0.5 then
ddy = math.clamp(ddy + math.abs(dy/10),0,4)
end
if math.abs(dx) < 0.1 then
dx = 0
ddx = 0
elseif math.abs(dx) > 0.5 then
ddx = math.clamp(ddx + math.abs(dx/10),0,4)
end
local yConversion = dx * (1 + ddx)
local xConversion = dy * (1 + ddy)
if not rotationLocked then
xRotation = math.clamp(xRotation - math.rad(xConversion), -maxXRotation, maxXRotation)
yRotation = (yRotation - math.rad(yConversion)) % fullRotation
end
end
local inputs = UserInputService:GetGamepadState(Enum.UserInputType.Gamepad1)
if inputs then
for index, inputObject in pairs(inputs) do
if inputObject.KeyCode == Enum.KeyCode.Thumbstick2 then
local dy = inputObject.Position.Y
local dx = inputObject.Position.X
if math.abs(dy) < 0.1 then
dy = 0
ddy = 0
elseif math.abs(dy) > 0.5 then
ddy = math.clamp(ddy + math.abs(dy/10),0,4)
end
if math.abs(dx) < 0.1 then
dx = 0
ddx = 0
elseif math.abs(dx) > 0.5 then
ddx = math.clamp(ddx + math.abs(dx/10),0,4)
end
local yConversion = dx * (1 + ddx)
local xConversion = -dy * (1 + ddy)
if not rotationLocked then
xRotation = math.clamp(xRotation - math.rad(xConversion), -maxXRotation, maxXRotation)
yRotation = (yRotation - math.rad(yConversion)) % fullRotation
end
end
end
end
end
local function onCharacterAdded(newCharacter)
character = newCharacter
if IS_PLAYER_CAMERA_LOCKED then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
else
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
end
local function isCameraLocked()
return IS_PLAYER_CAMERA_LOCKED
end
local function toggleCameraLock(setValue)
if setValue ~= nil then
IS_PLAYER_CAMERA_LOCKED = not not setValue
else
IS_PLAYER_CAMERA_LOCKED = not IS_PLAYER_CAMERA_LOCKED
end
if IS_PLAYER_CAMERA_LOCKED then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
else
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
end
if character and character.PrimaryPart then
character.PrimaryPart.hitboxGyro.D = 5000
character.PrimaryPart.hitboxGyro.MaxTorque = Vector3.new(100000000, 100000000, 100000000)
character.PrimaryPart.hitboxGyro.P = 3000000
end
network:fire("toggleCameraLockChanged", IS_PLAYER_CAMERA_LOCKED)
end
function module.init(Modules)
network = Modules.network
camera_shaker = Modules.camera_shaker
tween = Modules.tween
if player.Character then
onCharacterAdded(player.Character)
end
player.CharacterAdded:connect(onCharacterAdded)
UserInputService.InputBegan:connect(onInputBegan)
UserInputService.InputChanged:connect(onInputChanged)
UserInputService.InputEnded:connect(onInputEnded)
primarycamera_shaker = camera_shaker.new(--[[Enum.RenderPriority.Camera.Value]] 2, function(shakeCF)
camera.CFrame = cameraCFrameVal.Value * shakeCF
end)
primarycamera_shaker:Start()
-- TODO: this can really be eliminated with a direct module reference
network:create("lockCameraTarget", "BindableFunction", "OnInvoke", lockCameraTarget)
network:create("lockCameraTargetWithOrientation", "BindableFunction", "OnInvoke", lockCameraTargetWithOrientation)
network:create("cameraShake", "BindableFunction", "OnInvoke", cameraShake)
network:create("lockCameraPosition","BindableFunction","OnInvoke",lockCamera)
network:create("lockCameraPositionWithCameraShake","BindableFunction","OnInvoke",lockCameraWithCameraShake)
network:create("mobileCameraRotationChanged", "BindableEvent", "Event", mobileCameraRotationChanged)
network:create("toggleCameraLockChanged", "BindableEvent")
network:create("getIsPlayerCameraLocked", "BindableFunction", "OnInvoke", isCameraLocked)
network:create("toggleCameraLock", "BindableFunction", "OnInvoke", toggleCameraLock)
RunService.RenderStepped:connect(step)
RunService:BindToRenderStep("cameraRenderUpdate", --[[Enum.RenderPriority.Camera.Value - 1]] 1, updateCamera)
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/chatRunner.lua
================================================
local module = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
network:connect("signal_alertChatMessage", "OnClientEvent", function(messageTable)
game.StarterGui:SetCore("ChatMakeSystemMessage", messageTable)
end)
network:connect("signal_playerKilledByPlayer", "OnClientEvent", function(deadPlayer, killer, damageInfo, verb)
if killer and verb then
-- you did this!
if killer == game.Players.LocalPlayer then
utilities.playSound("kill", deadPlayer.Character and deadPlayer.Character.PrimaryPart)
network:fire("alert", {
text = "You " .. verb .. " " ..deadPlayer.Name.."!";
textColor3 = Color3.fromRGB(255, 93, 61);
})
end
end
end)
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/clientServerDamageSourceInterface.client.lua
================================================
-- this will act as the medium between all damaging abilities/weapons
-- and the server, everything is routed here then sent out by the server
-- Author: Polymorphic
local assetFolder = script.Parent.Parent:WaitForChild("assets")
local module = {}
local player = game.Players.LocalPlayer
local userInputService = game:GetService("UserInputService")
local collectionService = game:GetService("CollectionService")
local httpService = game:GetService("HttpService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local placeSetup = modules.load("placeSetup")
local mapping = modules.load("mapping")
local detection = modules.load("detection")
local damage = modules.load("damage")
local projectile = modules.load("projectile")
local client_utilities = modules.load("client_utilities")
local events = modules.load("events")
local tween = modules.load("tween")
local effects = modules.load("effects")
local ability_utilities = modules.load("ability_utilities")
local itemData = require(replicatedStorage.itemData)
local abilityLookup = require(replicatedStorage.abilityLookup)
local ResourceController = require(script.Parent.resources)
local entityRenderCollectionFolder = placeSetup.awaitPlaceFolder("entityRenderCollection")
local entityManifestCollectionFolder = placeSetup.awaitPlaceFolder("entityManifestCollection")
local itemsFolder = placeSetup.awaitPlaceFolder("items")
local entitiesFolder = placeSetup.awaitPlaceFolder("entities")
local assetsFolder = replicatedStorage:WaitForChild("assets")
local currentlyEquipped
local currentWeaponManifest
local clientCharacterContainer
local canPlayerBasicAttack = true
local isPlayerHoldingDownBasicAttack = false
local isPlayerCastingAbility = false
local function onSetCanPlayerBasicAttack(value)
canPlayerBasicAttack = value
if not canPlayerBasicAttack and isPlayerHoldingDownBasicAttack then
if currentlyEquipped and currentlyEquipped.release then
isPlayerHoldingDownBasicAttack = false
currentlyEquipped:release()
end
end
end
local function getPlayerEquipmentSlotDataForWeapon()
local equipment = network:invoke("getCacheValueByNameTag", "equipment")
if equipment then
for i, equipmentSlotData in pairs(equipment) do
if equipmentSlotData.position == 1 then
return equipmentSlotData
end
end
end
return nil
end
local function equipmentMeetsAbilityRequirement(equipmentData, abilityRequirement)
local equipmentType = equipmentData.equipmentType
if equipmentType == abilityRequirement then
return true
end
if abilityRequirement == "sword" and equipmentType == "greatsword" then
return true
end
local isBowDaggerAbility = (abilityRequirement == "dagger") or (abilityRequirement == "bow")
local isBowDaggerEquipped = (equipmentType == "dagger") or (equipmentType == "bow")
if isBowDaggerAbility and isBowDaggerEquipped then
local renderCharacterContainer = network:invoke("getMyClientCharacterContainer")
if not renderCharacterContainer then return false end
local equipment = network:invoke("getCurrentlyEquippedForRenderCharacter", renderCharacterContainer.entity)
if not equipment then return false end
local offhand = equipment["11"]
if (not offhand) or (not offhand.baseData) then return false end
if offhand.baseData.equipmentType == abilityRequirement then
network:invokeServer("playerRequest_swapWeapons_yielding")
return true
end
end
return false
end
local function getServerHitboxFromClientHitbox(clientHitbox)
if clientHitbox.Parent:FindFirstChild("clientHitboxToServerHitboxReference") then
return clientHitbox.Parent.clientHitboxToServerHitboxReference.Value
end
end
local function getClientHitboxFromServerHitbox(serverHitbox)
for i, clientMonsterContainer in pairs(entityRenderCollectionFolder:GetChildren()) do
if clientMonsterContainer:FindFirstChild("clientHitboxToServerHitboxReference") and clientMonsterContainer.clientHitboxToServerHitboxReference.Value == serverHitbox then
return clientMonsterContainer.PrimaryPart
end
end
end
-- sourceType = "ability", "item"
local function handleRequestEntityDamageRequest(serverHitbox, damagePosition, sourceType, sourceId, sourceTag, GUID)
-- todo: do client-side sanity checks
-- check if is descendant
if damage.canPlayerDamageTarget(player, serverHitbox) then
network:fire("monsterDamagedAtPosition", damagePosition)
network:fireServer("playerRequest_damageEntity", serverHitbox, damagePosition, sourceType, sourceId, sourceTag, GUID)
end
end
local curWeaponType
local function int__equipWeapon(weaponData)
local itemBaseData = itemData[weaponData.id]
if itemBaseData and itemBaseData.isEquippable and itemBaseData.equipmentType and itemBaseData.equipmentSlot == mapping.equipmentPosition.weapon then
if not currentlyEquipped and assetFolder.damageInterfaces:FindFirstChild(itemBaseData.equipmentType) then
curWeaponType = itemBaseData.equipmentType
-- check for dual swords and sword and shield
if clientCharacterContainer and clientCharacterContainer:FindFirstChild("entity") then
local currentEquipment = network:invoke("getCurrentlyEquippedForRenderCharacter", clientCharacterContainer.entity)
if currentEquipment["1"] and currentEquipment["11"] then
if currentEquipment["1"].baseData.equipmentType == "sword" and currentEquipment["11"].baseData.equipmentType == "sword" then
curWeaponType = "dual"
elseif currentEquipment["1"].baseData.equipmentType == "sword" and currentEquipment["11"].baseData.equipmentType == "shield" then
curWeaponType = "swordAndShield"
end
end
end
end
if curWeaponType then
currentlyEquipped = require(assetFolder.damageInterfaces[curWeaponType])
currentlyEquipped:equip()
end
end
end
local function int__unequipWeapon()
if currentlyEquipped then
currentlyEquipped:unequip()
currentlyEquipped = nil
curWeaponType = nil
end
end
local function int_checkIfEquipWeaponFromEquipment(equipment)
if equipment then
local weaponEquipmentData
for i, equipmentData in pairs(equipment) do
if equipmentData.position == mapping.equipmentPosition.weapon then
weaponEquipmentData = equipmentData
end
end
if weaponEquipmentData then
if currentlyEquipped then
-- unequip weapon first, then equip the other weapon
int__unequipWeapon()
end
int__equipWeapon(weaponEquipmentData)
else
if currentlyEquipped then
int__unequipWeapon()
end
end
else
end
end
local function onPropogationRequestToSelf(propogationNameTag, propogationValue)
if propogationNameTag == "equipment" then
int_checkIfEquipWeaponFromEquipment(propogationValue)
end
end
local function onMyClientCharacterWeaponChanged(weaponManifest)
-- set the weapon stuff
currentWeaponManifest = weaponManifest
if weaponManifest:IsA("BasePart") then
weaponManifest.Touched:Connect(function()
-- do nothing just have a touch interest I guess
end)
end
-- if currentWeaponManifest then
-- local equipmentSlotDataCollection = network:invoke("getCacheValueByNameTag", "equipment")
--
-- if equipmentSlotDataCollection then
-- int_checkIfEquipWeaponFromEquipment(equipmentSlotDataCollection)
-- end
-- end
end
local cooldownLookup = {}
local displayLookup = {}
local castingAnimation
local function onMyClientCharacterContainerChanged(newMyClientCharacterContainer)
clientCharacterContainer = newMyClientCharacterContainer
-- wait for humanoid
local animationController = newMyClientCharacterContainer.entity:WaitForChild("AnimationController")
while not animationController:IsDescendantOf(workspace) do wait() end
if currentlyEquipped then
currentlyEquipped:equip()
end
castingAnimation = animationController:LoadAnimation(assetsFolder.abilityAnimations.rock_throw_upper_loop)
end
local function onCharacterAdded(character)
local equipment = network:invoke("getCacheValueByNameTag", "equipment")
int_checkIfEquipWeaponFromEquipment(equipment)
end
local weaponDelays = {dual = .15; dagger = .15; sword = .23; staff = .23; --[[bow handles its own attack delay]] bow = 0}
local attackOnCoolDown
local function onInputBegan(input, absorbed)
if canPlayerBasicAttack and currentlyEquipped and not absorbed and not network:invoke("getIsCurrentlyConsuming") then
if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 or input.KeyCode == Enum.KeyCode.LeftControl or input.KeyCode == Enum.KeyCode.ButtonR2 then
if network:invoke("isCharacterStunned") then return end
if attackOnCoolDown then return end
network:invoke("setCharacterMovementState", "isSprinting", false)
-- isPlayerHoldingDownBasicAttack = true
events:fireEventAll("playerWillUseBasicAttack", player)
currentlyEquipped:attack(input)
network:fire("stopChannels", "attack")
network:fire("signalBasicAttacking", true)
-- attackOnCoolDown = true
local stats = network:invoke("getCacheValueByNameTag", "nonSerializeData").statistics_final
local attackDelay = .25
if weaponDelays[curWeaponType] then
attackDelay = weaponDelays[curWeaponType] / (1 + stats.attackSpeed)
end
end
end
end
local function onInputEnded(input, absorbed)
if isPlayerHoldingDownBasicAttack and currentlyEquipped and not absorbed and currentlyEquipped.release then -- bows
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.KeyCode == Enum.KeyCode.LeftControl or input.KeyCode == Enum.KeyCode.ButtonR2 then
currentlyEquipped:release()
isPlayerHoldingDownBasicAttack = false
end
elseif isPlayerHoldingDownBasicAttack and currentlyEquipped then -- swords
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.KeyCode == Enum.KeyCode.LeftControl or input.KeyCode == Enum.KeyCode.ButtonR2 then
isPlayerHoldingDownBasicAttack = false
network:fire("signalBasicAttacking", false)
end
end
end
local function onAttackInteractionSoundPlayed(position, soundName)
if not position then
position = player.Character.PrimaryPart.Position
end
utilities.playSound(soundName, position)
end
network:connect("attackInteractionSoundPlayed", "OnClientEvent", onAttackInteractionSoundPlayed)
local function onAttackInteractionAttackableAttacked(attackingPlayer, part, hitPosition)
local module = part:FindFirstChild("attackableScript")
if not module then return end
-- hit effects
network:fire("monsterDamagedAtPosition", hitPosition, attackingPlayer ~= player)
module = require(module)
module.onAttackedClient(attackingPlayer)
end
network:connect("attackInteractionAttackableAttacked", "OnClientEvent", onAttackInteractionAttackableAttacked)
local function shake(model)
if model and model:IsA("Model") then
if model.PrimaryPart then
local originalCFrameValue = model:FindFirstChild("originalCFrame")
if originalCFrameValue == nil then
originalCFrameValue = Instance.new("CFrameValue")
originalCFrameValue.Name = "originalCFrame"
originalCFrameValue.Value = model.PrimaryPart.CFrame
originalCFrameValue.Parent = model
end
local originalCFrame = originalCFrameValue.Value
local primaryPart = model.PrimaryPart
local rootCFrame = originalCFrame * CFrame.new(0, -primaryPart.Size.Y/2, 0) * CFrame.Angles(0, math.pi * 2 * math.random(), 0)
local offset = rootCFrame:ToObjectSpace(originalCFrame)
local dummyPart = Instance.new("Part")
dummyPart.CFrame = rootCFrame
local shakeTime = 0.2
local easingStyle = Enum.EasingStyle.Quad
tween(dummyPart, {"CFrame"}, rootCFrame * CFrame.Angles(0.075, 0, 0), shakeTime, easingStyle, Enum.EasingDirection.Out)
delay(shakeTime, function()
tween(dummyPart, {"CFrame"}, rootCFrame, shakeTime, easingStyle, Enum.EasingDirection.In)
end)
effects.onHeartbeatFor(shakeTime * 2, function()
model:SetPrimaryPartCFrame(dummyPart.CFrame:ToWorldSpace(offset))
end)
end
end
end
local hitInteractions = {}
local function attackInteraction(interaction, part)
local name = part.Name:lower()
-- leafy boy
if (name == "grass") or (name == "leaf") or (name == "bush") or (name == "green") then
if not interaction["strikeFoliage"] then
local soundName = "bush" .. math.random(1,3)
onAttackInteractionSoundPlayed(part.Position, soundName)
network:fireServer("attackInteractionSoundPlayed", part, soundName)
shake(part.Parent)
end
end
-- stumpy dude
if (name == "stump") or (name == "log") or (name == "wood") then
shake(part.Parent)
end
--attackable interactable ableables!
if collectionService:HasTag(part, "attackable") then
local model = part:FindFirstAncestorWhichIsA("Model")
local folder = model:FindFirstAncestorWhichIsA("Folder")
if collectionService:HasTag(folder, "resourceNodeTypeFolder") or collectionService:HasTag(folder, "resourceNodeGroupFolder") then
local nodeModel = model
local dropPoint = network:invokeServer("harvestResource", nodeModel)
shake(nodeModel)
else
local hitPosition = detection.projection_Box(part.CFrame, part.Size, currentWeaponManifest.Position)
onAttackInteractionAttackableAttacked(player, part, hitPosition)
network:fireServer("attackInteractionAttackableAttacked", part, hitPosition)
end
end
end
local function doAttackInteractions(guid)
local interaction = hitInteractions[guid]
if not interaction then
interaction = {}
-- save this interaction for a bit, then
-- get rid of it to avoid memory leaks
hitInteractions[guid] = interaction
delay(5, function()
hitInteractions[guid] = nil
end)
end
for _, part in pairs(currentWeaponManifest:GetTouchingParts()) do
if not interaction[part] then
interaction[part] = true
attackInteraction(interaction, part)
end
end
end
local hitDebounceTable = {}
local function performClientDamageCycle(sourceType, sourceId, guid)
if (sourceType == "equipment" and currentWeaponManifest) then
if not hitDebounceTable[guid] then
hitDebounceTable[guid] = {}
-- invalidate this GUID in 5 seconds
-- if an exploiter messes with this, oh well. have fun with a memory leak.
delay(5, function()
hitDebounceTable[guid] = nil
end)
end
doAttackInteractions(guid)
local sizeIncrease = 1 + network:invoke("getCacheValueByNameTag", "nonSerializeData").statistics_final.attackRangeIncrease
for i, entityManifest in pairs(utilities.getEntities()) do
if entityManifest ~= player.Character.PrimaryPart then
if not hitDebounceTable[guid][entityManifest] then
local boxcastOriginCF = currentWeaponManifest.CFrame
local boxProjection_serverHitbox = detection.projection_Box(entityManifest.CFrame, entityManifest.Size, boxcastOriginCF.p)
if detection.boxcast_singleTarget(boxcastOriginCF, currentWeaponManifest.Size * Vector3.new(3 + sizeIncrease, 2 + sizeIncrease, 3 + sizeIncrease), boxProjection_serverHitbox) then
hitDebounceTable[guid][entityManifest] = true
network:fire("requestEntityDamageDealt", entityManifest, boxProjection_serverHitbox, sourceType, sourceId, guid)
end
end
end
end
else
-- do nothing
end
end
local function int_getCurrentlyEquippedEquipmentType()
local weaponData = getPlayerEquipmentSlotDataForWeapon()
return itemData[weaponData.id].equipmentType
end
-- invoke the client call on an ability
-- and return the stuff that we might need
-- who knows? it might come in handy
-- returns to NOTHING if we use an event
local function main()
network:create("getCurrentlyEquippedEquipmentType", "BindableFunction", "OnInvoke", int_getCurrentlyEquippedEquipmentType)
network:create("setCanPlayerBasicAttack", "BindableFunction", "OnInvoke", onSetCanPlayerBasicAttack)
network:create("requestEntityDamageDealt", "BindableEvent", "Event", handleRequestEntityDamageRequest)
network:create("performClientDamageCycle", "BindableFunction", "OnInvoke", performClientDamageCycle)
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
network:connect("myClientCharacterWeaponChanged", "Event", onMyClientCharacterWeaponChanged)
local equipment = network:invoke("getCacheValueByNameTag", "equipment")
-- render character listener --
local newMyClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if newMyClientCharacterContainer then
onMyClientCharacterContainerChanged(newMyClientCharacterContainer)
end
network:connect("myClientCharacterContainerChanged", "Event", onMyClientCharacterContainerChanged)
-- get player character --
if player.Character then
onCharacterAdded(player.Character)
end
player.CharacterAdded:connect(onCharacterAdded)
userInputService.InputBegan:connect(onInputBegan)
userInputService.InputEnded:connect(onInputEnded)
end
main()
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/client_events.lua
================================================
local module = {}
function module.init(Modules)
local network = Modules.network
local events = Modules.events
network:connect("fireEvent", "OnClientEvent", function(...)
events:fireEventLocal(...)
end)
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/control.lua
================================================
-- Authors: Polymorphic, berezaa
local module = {}
local player = game.Players.LocalPlayer
local starterPlayer = game:GetService("StarterPlayer")
local DISTANCE_AWAY_THRESHOLD = 50
local MANIFEST_ORIENTATION_ASSIST_TIME_BASE = 0.5
local MANIFEST_ORIENTATION_ASSIST_TIME = MANIFEST_ORIENTATION_ASSIST_TIME_BASE
local CAMERA_RAYCAST_LENGTH = math.floor(starterPlayer.CameraMaxZoomDistance * 1.25)
local assetFolder = script.Parent.Parent:WaitForChild("assets")
local closestManifest
local currentEquipType
local playerMovementSpeed = 16
local total_statistics = {}
local UserInputService = game:GetService("UserInputService")
local placeSetup
local network
local client_utilities
local tween
local utilities
local terrainUtil
local damage
local isMenuInFocus
local function signal_menuFocusChanged(value)
isMenuInFocus = value
end
local entityRenderCollectionFolder
local entityManifestCollectionFolder
local IGNORE_LIST
local userInputService = game:GetService("UserInputService")
local camera = workspace.Camera
local myClientCharacterContainer
local isPlayerJumpEnabled = true
local isPlayerSprintingEnabled = true
local IS_PLAYER_CAMERA_LOCKED = false
local playerWalkspeedMultiplier = 1
local isPlayerChanneling = false
local basicAttacking = false
local function onPlayerStatisticsChanged(base, tot)
total_statistics = tot
playerMovementSpeed = total_statistics.walkspeed or 14
end
-- todo: fix
local animationInterface
local itemLookup = require(game.ReplicatedStorage.itemData)
local function getServerHitboxFromClientHitbox(clientHitbox)
if clientHitbox.Parent:FindFirstChild("clientHitboxToServerHitboxReference") then
return clientHitbox.Parent.clientHitboxToServerHitboxReference.Value
end
end
-- my only purpose is to make you walk slower while UserInputServiceng a bow :D
local function onSetIsChanneling(isChanneling)
isPlayerChanneling = isChanneling
spawn(function()
if isChanneling then
for i = 1, 0.5, -1 / 30 do
-- break if channeling status changes
if not isPlayerChanneling then break end
playerWalkspeedMultiplier = i
wait()
end
playerWalkspeedMultiplier = 0.5
else
for i = 0.5, 1, 1 / 30 do
-- break if channeling status changes
if isPlayerChanneling then break end
playerWalkspeedMultiplier = i
wait()
end
playerWalkspeedMultiplier = 1
end
end)
end
local tweenService = game:GetService("TweenService")
local TWEEN_INFO = TweenInfo.new(1 / 3, Enum.EasingStyle.Quad, Enum.EasingDirection.Out, 0, false, 0)
local sprintFOV = Instance.new("NumberValue")
sprintFOV.Name = "sprintFOV"
sprintFOV.Parent = script
local LAST_UPDATE_TIME = 0
local UPDATE_TIME = 0.3
local manifestTargetLocked
local isPlayerSprinting = false
local isForward = false
local isBackward = false
local isLeftward = false
local isRightward = false
local isCastingSpell = false
local characterArrested = false
local states = {}
states.isSprinting = false
states.isInAir = false
states.isMoving = false
states.isJumping = false
states.isSitting = false
states.isExhausted = false
states.isDoubleJumping = false
states.isFalling = false
states.isRotating = false
states.isFishing = false
states.isGettingUp = false
states.isSwimming = false
-- ask damien if you need to know how status effects work now, this aint it chief
local function doesCharacterHaveStatusEffect(statusEffectType, sourceId, sourceVariant)
local char = player.Character
if not char then return false end
local manifest = char.PrimaryPart
if not manifest then return false end
local statusEffects = manifest:FindFirstChild("statusEffectsV2")
if not statusEffects then return false end
local decodeSuccessful, statuses = utilities.safeJSONDecode(statusEffects.Value)
if not decodeSuccessful then return false end
for _, status in pairs(statuses) do
if status.statusEffectType == statusEffectType then
if (sourceId == nil or status.sourceId == sourceId) and (sourceVariant == nil or status.variant == sourceVariant) then
return true
end
end
end
return false
end
local function isCharacterStunned()
return doesCharacterHaveStatusEffect("stunned")
end
local function begin_sprint()
network:invoke("setCharacterMovementState", "isSprinting", true)
end
local function end_sprint()
network:invoke("setCharacterMovementState", "isSprinting", false)
end
-- performs state checks and changes the sprinting state, will correct invalid state
local function set_sprinting(val)
if not characterArrested then
if isPlayerSprintingEnabled and val then
begin_sprint()
else
end_sprint()
end
else
end_sprint()
end
end
local tau = 2 * math.pi
local function convertXBoxMovementToAngle(x, y)
return (math.atan2(y, x) - math.pi / 2) % tau
end
local movementUnitVector
local mobileMovementDirection
local function mobileMovementDirectionChanged(direction)
mobileMovementDirection = direction
end
-- youre gonna need to live with this
local function getMovementAngle()
local movementAngle
if isForward and (((isRightward and isLeftward) and not isBackward) or not (isBackward or isRightward or isLeftward)) then
movementAngle = 0
elseif isForward and isRightward and not (isBackward or isLeftward) then
movementAngle = math.pi / 4
elseif isRightward and (((isForward and isBackward) and not isLeftward) or not (isForward or isBackward or isLeftward)) then
movementAngle = math.pi / 2
elseif isBackward and isRightward and not (isForward or isLeftward) then
movementAngle = 3 * math.pi / 4
elseif isBackward and (((isRightward and isLeftward) and not isForward) or not (isForward or isLeftward or isRightward)) then
movementAngle = math.pi
elseif isBackward and isLeftward and not (isRightward or isForward) then
movementAngle = 5 * math.pi / 4
elseif isLeftward and (((isForward and isBackward) and not isRightward) or not (isRightward or isForward or isBackward)) then
movementAngle = 3 * math.pi / 2
elseif isLeftward and isForward and not (isRightward or isBackward) then
movementAngle = 7 * math.pi / 4
end
if mobileMovementDirection then
if mobileMovementDirection.magnitude > 0.1 then
movementUnitVector = mobileMovementDirection
else
movementUnitVector = nil
return nil
end
return convertXBoxMovementToAngle(mobileMovementDirection.X, -mobileMovementDirection.Y)
end
if movementAngle then
movementUnitVector = nil
return movementAngle
end
local state = UserInputService:GetGamepadState(Enum.UserInputType.Gamepad1)
if state then
for index, input in pairs(state) do
if input.KeyCode == Enum.KeyCode.Thumbstick1 then
if input.Position.magnitude > 0.2 and not game.GuiService.SelectedObject then
movementUnitVector = input.Position
else
if states.isSprinting then
set_sprinting(false)
end
movementUnitVector = nil
return nil
end
return convertXBoxMovementToAngle(input.Position.X, input.Position.Y)
end
end
end
movementUnitVector = nil
return movementAngle
end
local function isOnScreen(position)
return
position.X >= 0 and position.X <= workspace.CurrentCamera.ViewportSize.X
and position.Y >= 0 and position.Y <= workspace.CurrentCamera.ViewportSize.Y
and position.Z > 0
end
local targetsList = {}
local targetIndex = 1
local manifestCurrentlyTargetted
local manifestCurrentlyTargetted_distanceAway
local function updateTargetsList()
local characterHitbox = player.Character and player.Character.PrimaryPart
if not characterHitbox then return end
if tick() - LAST_UPDATE_TIME >= UPDATE_TIME then
LAST_UPDATE_TIME = tick()
local new__targetsList = {}
local hitPart, hitPosition = client_utilities.raycastFromCurrentScreenPoint({entityRenderCollectionFolder})
if hitPart then
local hitPlayer, hitMonster do
local canDoDamage, trueHitPart = damage.canPlayerDamageTarget(player, hitPart)
if canDoDamage and trueHitPart then
if trueHitPart.entityId.Value == "character" then
hitPlayer = game.Players:GetPlayerFromCharacter(trueHitPart.Parent)
elseif trueHitPart.entityId.Value == "monster" then
hitMonster = trueHitPart
end
end
end
if
hitMonster and
not hitMonster:FindFirstChild("pet") and
not hitMonster:FindFirstChild("isStealthed")
then
local distanceAway = (hitMonster.Position - characterHitbox.Position).magnitude
if distanceAway <= DISTANCE_AWAY_THRESHOLD then
manifestCurrentlyTargetted = hitMonster
manifestCurrentlyTargetted_distanceAway = distanceAway
-- short circuit
return manifestCurrentlyTargetted, manifestCurrentlyTargetted_distanceAway
end
end
end
local nearestMonsterManifest, distanceAway = nil, DISTANCE_AWAY_THRESHOLD
local damagableTargets = damage.getDamagableTargets(game.Players.LocalPlayer)
for i, manifest in pairs(damagableTargets) do
local isStealthed = manifest:FindFirstChild("isStealthed") ~= nil
if not isStealthed then
local _distanceAway = utilities.magnitude(manifest.Position - characterHitbox.Position)
if distanceAway > _distanceAway then
local position, isInFrontNearClipping = camera:WorldToScreenPoint(manifest.Position)
if position.Z > 0 and isOnScreen(position) and manifest.health.Value > 0 then
nearestMonsterManifest = manifest
distanceAway = _distanceAway
end
end
end
end
manifestCurrentlyTargetted = nearestMonsterManifest
manifestCurrentlyTargetted_distanceAway = distanceAway
end
return manifestCurrentlyTargetted, manifestCurrentlyTargetted_distanceAway
end
local function cycleThroughTargetsList(cycleBackwardsNotForwards)
if #targetsList > 1 then
if cycleBackwardsNotForwards then
if targetIndex == 1 then
targetIndex = #targetsList
else
targetIndex = targetIndex - 1
end
else
targetIndex = ((targetIndex + 1) % #targetsList) + 1
end
else
targetIndex = 1
end
manifestCurrentlyTargetted = targetsList[targetIndex] and targetsList[targetIndex].manifest
manifestCurrentlyTargetted_distanceAway = targetsList[targetIndex] and targetsList[targetIndex].distanceAway
end
local renderStepped_connection
local function onCharacterAdded(character)
-- reset states --
states.isSprinting = false
states.isInAir = false
states.isMoving = false
states.isJumping = false
states.isSitting = false
states.isExhausted = false
states.isDoubleJumping = false
states.isFishing = false
states.isRotating = false
states.isFalling = false
states.isGettingUp = false
states.isSwimming = false
onSetIsChanneling(false)
myClientCharacterContainer = network:invoke("getMyClientCharacterContainer")
if character.PrimaryPart == nil and character:FindFirstChild("hitbox") then
character.PrimaryPart = character.hitbox
end
local startTime = tick()
repeat wait() until character.PrimaryPart or character.Parent == nil or tick() - startTime >= 10
if not character.PrimaryPart then
return false
end
network:fireServer("replicateClientStateChanged", character.PrimaryPart.state.Value)
end
local startSprintTime = 0
local isPlayerSprintingAnimationPlaying = false
local function startSprinting_animations(onlyStopAnimation)
--if states.isExhausted then return end
if isPlayerSprintingAnimationPlaying then return end
network:fire("stopChannels", "sprint")
isPlayerSprintingAnimationPlaying = true
tween(sprintFOV,{"Value"},15,0.5)
--cameraSprinting:Play()
if not states.isInAir and myClientCharacterContainer and myClientCharacterContainer:FindFirstChild("entity") then
myClientCharacterContainer.entity.RightFoot.ParticleEmitter.Enabled = true
myClientCharacterContainer.entity.LeftFoot.ParticleEmitter.Enabled = true
end
startSprintTime = tick()
end
local function stopSprinting_animations()
--if not isPlayerSprintingAnimationPlaying then return end
isPlayerSprintingAnimationPlaying = false
tween(sprintFOV,{"Value"},0,0.5)
--cameraWalking:Play()
if myClientCharacterContainer and myClientCharacterContainer:FindFirstChild("entity") then
myClientCharacterContainer.entity.RightFoot.ParticleEmitter.Enabled = false
myClientCharacterContainer.entity.LeftFoot.ParticleEmitter.Enabled = false
end
end
local renderUpdateConnection
local runService = game:GetService("RunService")
local mouseMovementInputObject = nil
local function raycastFromScreenPosition(screenPositionX, screenPositionY)
local characterHitbox = player.Character and player.Character.PrimaryPart
if characterHitbox then
local cameraRay = workspace.CurrentCamera:ScreenPointToRay(screenPositionX, screenPositionY)
local ray = Ray.new(cameraRay.Origin, cameraRay.Direction.unit * CAMERA_RAYCAST_LENGTH)
local hitPart, hitPosition = workspace:FindPartOnRayWithIgnoreList(ray, IGNORE_LIST)
return hitPart, hitPosition, (hitPart and utilities.magnitude(hitPart.Position - characterHitbox.Position) or nil)
end
end
local curEmote = ""
local isPlayerEmoting = false
local emoteCooldown = false
local defaultEmotes = {
["dance"] = true;
["dance2"] = true;
["dance3"] = true;
["oh yea"] = true;
["hype"] = true;
["sit"] = true;
["wave"] = true;
["point"] = true;
["beg"] = true;
["flex"] = true;
["handstand"] = true;
["tadaa"] = true;
["jumps"] = true;
["guitar"] = true;
["panic"] = true;
["cheer"] = true;
["pushups"] = true;
}
local function performEmote(emote)
if states.isSprinting or states.isInAir or states.isMoving or states.isJumping or states.isSitting or states.isExhausted or states.isDoubleJumping or states.isFalling or states.isFishing or states.isGettingUp or states.isSwimming then
return false
end
local lastEmotesReceived = network:invoke("getCacheValueByNameTag", "globalData").emotes
local found = false
emote = string.lower(emote)
for i, ownedEmote in pairs(lastEmotesReceived) do
if emote == ownedEmote then
found = true
end
end
if defaultEmotes[emote] then found = true end
if found and not emoteCooldown then
if curEmote ~= emote then
curEmote = emote
isPlayerEmoting = true
spawn(function()
emoteCooldown = true
wait(1)
emoteCooldown = false
end)
player.Character.PrimaryPart.state.Value = "idling"
network:fireServer("replicateClientStateChanged", "idling")
animationInterface:replicatePlayerAnimationSequence("emoteAnimations", emote, nil, {dance = true}) -- add security check on the remote, serverside when players can buy emotes
return true
end
--return false, "Cannot perform emote while moving."
else
if emoteCooldown then
return false, "Emote on cooldown."
end
return false, "Cannot perform invalid emote."
end
end
function module.endEmote()
if isPlayerEmoting then
curEmote = ""
isPlayerEmoting = false
end
end
-- this is all crappy bad code but you cant burn it all down because you need it so figure out how to
-- use it in a way that isnt crappy bad code thank you good luck have fun we're praying for your
-- safe return
local function setCharacterMovementState(state, value, ...)
--if state and state ~= "isMoving" then
-- isPlayerEmoting = false
-- curEmote = ""
--end
--isPlayerEmoting = false
--curEmote = ""
if states[state] ~= nil and states[state] ~= value and player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.state.Value ~= "dead" then
isPlayerEmoting = false
curEmote = ""
if state == "isSprinting" and not network:invoke("getIsCurrentlyConsuming") and not states.isExhausted then
if states.isMoving then
if not states[state] and value then
-- turning on
startSprinting_animations()
elseif states[state] and not value then
-- turning off
stopSprinting_animations()
end
else
-- still play exhaust if they stop moving
-- while sprinting then stop sprinting
if states[state] and not value then
stopSprinting_animations()
end
end
elseif state == "isSprinting" and (network:invoke("getIsCurrentlyConsuming") or states.isExhausted) then
return
elseif state == "isMoving" and not states.isExhausted then
if states.isSprinting and not isPlayerSprintingAnimationPlaying then
startSprinting_animations(true)
elseif not states.isSprinting and isPlayerSprintingAnimationPlaying then
stopSprinting_animations(true)
end
elseif state == "isGettingUp" then
if states.isSprinting then
states.isSprinting = false
stopSprinting_animations(true)
end
end
states[state] = value
if player.Character and player.Character.PrimaryPart then
if not states.isGettingUp then
if not states.isSwimming then
if not states.isFishing then
if not states.isSitting then
if not states.isFalling then
if not states.isJumping then
if states.isMoving or states.isRotating then
if states.isSprinting and not characterArrested then
-- pray this doesnt cause race condition issues
if player.Character.PrimaryPart.state.Value ~= "sprinting" then
network:fireServer("replicateClientStateChanged", "sprinting")
end
player.Character.PrimaryPart.state.Value = "sprinting"
else
if states.isExhausted then
player.Character.PrimaryPart.state.Value = "walking_exhausted"
network:fireServer("replicateClientStateChanged", "walking_exhausted")
else
-- pray here too
if player.Character.PrimaryPart.state.Value ~= "walking" then
network:fireServer("replicateClientStateChanged", "walking")
end
player.Character.PrimaryPart.state.Value = "walking"
end
end
else
if states.isExhausted then
player.Character.PrimaryPart.state.Value = "idling_exhausted"
network:fireServer("replicateClientStateChanged", "idling_exhausted")
else
player.Character.PrimaryPart.state.Value = "idling"
network:fireServer("replicateClientStateChanged", "idling", "kicking")
end
end
else
if not states.isDoubleJumping then
player.Character.PrimaryPart.state.Value = "jumping"
network:fireServer("replicateClientStateChanged", "jumping")
else
player.Character.PrimaryPart.state.Value = "double_jumping"
network:fireServer("replicateClientStateChanged", "double_jumping")
end
end
else
player.Character.PrimaryPart.state.Value = "falling"
network:fireServer("replicateClientStateChanged", "falling")
end
else
player.Character.PrimaryPart.state.Value = "sitting"
network:fireServer("replicateClientStateChanged", "sitting", nil, ...)
end
else
player.Character.PrimaryPart.state.Value = "fishing"
network:fireServer("replicateClientStateChanged", "fishing")
end
else
player.Character.PrimaryPart.state.Value = "swimming"
network:fireServer("replicateClientStateChanged", "swimming")
end
else
player.Character.PrimaryPart.state.Value = "gettingUp"
network:fireServer("replicateClientStateChanged", "gettingUp", nil, ...)
end
end
network:fire("characterStateChanged", state, value, ...)
return true
end
end
local MAX_CONE_ANGLE_DIFF = math.rad(20)
local function isMouseInMovementCone(movementDirection, mouseDirection, movementAngle)
local dotProduct = movementDirection:Dot(mouseDirection)
local angleDiff = math.acos(dotProduct)
return angleDiff <= MAX_CONE_ANGLE_DIFF, dotProduct < 0, angleDiff, math.sign(movementDirection:Cross(mouseDirection):Dot(Vector3.new(0, 1, 0)))
end
local wind = assetFolder.wind
local windBaseVolume = game.ReplicatedStorage:FindFirstChild("windVolume") and game.ReplicatedStorage.windVolume.Value or 0.02
wind.Volume = windBaseVolume
wind:Play()
local baseFriction = 500
local friction = baseFriction -- change friction based on individual parts
local externalVelocity = Vector3.new()
local movementVelocity = Vector3.new()
local overrideCharacterCFrame
-- stops all movement, state changes, etc
local function setCharacterArrested(arrested, arrestedCFrame)
if arrested then
characterArrested = true
movementVelocity = Vector3.new()
if states.isSprinting then
stopSprinting_animations(true)
end
setCharacterMovementState("isMoving", false)
setCharacterMovementState("isSprinting", false)
setCharacterMovementState("isSitting", false)
setCharacterMovementState("isRotating", false)
if arrestedCFrame then
overrideCharacterCFrame = arrestedCFrame
externalVelocity = Vector3.new()
player.Character:SetPrimaryPartCFrame(arrestedCFrame)
local characterHitbox = player.Character and player.Character.PrimaryPart
if characterHitbox then
local bodyPosition = characterHitbox.grounder
local bodyVelocity = characterHitbox.hitboxVelocity
local bodyGyro = characterHitbox.hitboxGyro
bodyPosition.MaxForce = Vector3.new(1e5, 1e5, 1e5)
bodyVelocity.MaxForce = Vector3.new(0, 0, 0)
bodyGyro.CFrame = overrideCharacterCFrame
bodyPosition.Position = overrideCharacterCFrame.Position
end
end
else
characterArrested = false
overrideCharacterCFrame = nil
-- if shift is held when we come out of arrest we should start sprinting
local sprintKeyCode = Enum.KeyCode.LeftShift -- later we can change this to work with keybindings
local sprintKeyDown = userInputService:IsKeyDown(sprintKeyCode)
if sprintKeyDown then
set_sprinting(true)
end
end
end
local lastJump = 0
local totalStats
local function playerStatisticsChanged(base, total)
totalStats = total
end
local isPlayerUnderwater
local velocityHandler
local function raycastDownIgnoreCancollideFalse(ray, ignoreList)
local hitPart, hitPosition, hitDown, hitMaterial = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList, true)
local items = workspace.placeFolders:FindFirstChild("items")
-- edit: ignore water and dropped items
while hitPart and not (hitPart.CanCollide and (not hitPart:IsDescendantOf(entityManifestCollectionFolder) or (not hitPart:FindFirstChild("entityType") or hitPart.entityType.Value ~= "pet")) and hitMaterial ~= Enum.Material.Water and (items == nil or not hitPart:IsDescendantOf(items))) do
ignoreList[#ignoreList + 1] = hitPart
hitPart, hitPosition, hitDown, hitMaterial = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList, true)
end
return hitPart, hitPosition, hitDown, hitMaterial
end
local yJumpVelocity
local lastPositionOnGround
local startPositionForFalling
local characterOrientationUpdateConnection
local function setupRenderSteppedConnection()
local function updateCharacterOrientationFaceManifest(_,step)
local characterHitbox = player.Character and player.Character.PrimaryPart
local bodyPosition = characterHitbox and characterHitbox:FindFirstChild("grounder")
local bodyVelocity = characterHitbox and characterHitbox:FindFirstChild("hitboxVelocity")
local bodyGyro = characterHitbox and characterHitbox:FindFirstChild("hitboxGyro")
if not bodyPosition or not bodyVelocity or not bodyGyro then return end
if overrideCharacterCFrame then
bodyPosition.MaxForce = Vector3.new(1e5, 1e5, 1e5)
bodyVelocity.MaxForce = Vector3.new(0, 0, 0)
bodyGyro.CFrame = overrideCharacterCFrame
bodyPosition.Position = overrideCharacterCFrame.Position
return
end
local movementAngle = getMovementAngle()
local hitPosition
if mouseMovementInputObject then
_, hitPosition = raycastFromScreenPosition(mouseMovementInputObject.Position.X, mouseMovementInputObject.Position.Y)
end
-- todo optimize
local ignoreList = {
myClientCharacterContainer,
}
local downRay = Ray.new(characterHitbox.Position, Vector3.new(0, -5, 0))
local downHitPart, downHitPosition, downHitNormal, downHitMaterial = raycastDownIgnoreCancollideFalse(downRay, ignoreList)
local height = 2.5
-- check in front of the player
if utilities.magnitude(movementVelocity) > 0.1 then
local moving = movementVelocity.Unit
local frontRay = Ray.new(characterHitbox.Position, Vector3.new(moving.X, -height, moving.Z))
local frontHitPart, frontHitPosition = raycastDownIgnoreCancollideFalse(frontRay, ignoreList)
if frontHitPart then
local frontHitHeight = frontHitPosition.Y - (characterHitbox.Position.Y - height)
if frontHitHeight > 0 and frontHitHeight < height then
height = height + frontHitHeight * 12
end
end
end
local function land()
bodyPosition.MaxForce = Vector3.new(0, 1e4, 0)
bodyVelocity.MaxForce = Vector3.new(1e4, 0, 1e4)
states.isInAir = false
local yVelocityOnImpact = 0
if states.isJumping then
setCharacterMovementState("isJumping", false)
setCharacterMovementState("isDoubleJumping", false)
if states.isFalling then
yVelocityOnImpact = externalVelocity.Y
end
setCharacterMovementState("isFalling", false)
elseif states.isFalling then
yVelocityOnImpact = externalVelocity.Y
setCharacterMovementState("isFalling", false)
end
bodyPosition.Position = downHitPosition + Vector3.new(0, height, 0)
end
if downHitPart then
-- to get around floating characters, make sure active parts still trigger collision events
if game.CollectionService:HasTag(downHitPart, "ActivePart") then
network:fire("touchedActivePart", downHitPart, characterHitbox)
end
if states.isSprinting and myClientCharacterContainer and myClientCharacterContainer:FindFirstChild("entity") then
myClientCharacterContainer.entity.RightFoot.ParticleEmitter.Enabled = true
myClientCharacterContainer.entity.LeftFoot.ParticleEmitter.Enabled = true
local color = downHitPart.Color
if downHitPart:IsA("Terrain") and (downHitMaterial ~= Enum.Material.Air) then
color = downHitPart:GetMaterialColor(downHitMaterial)
end
myClientCharacterContainer.entity.LeftFoot.ParticleEmitter.Color = ColorSequence.new(color)
myClientCharacterContainer.entity.RightFoot.ParticleEmitter.Color = ColorSequence.new(color)
end
-- make players slide off of enemies
if downHitPart:isDescendantOf(entityManifestCollectionFolder) or downHitPart:isDescendantOf(entityRenderCollectionFolder) then
-- except other players
local downHitRender =
(downHitPart:FindFirstChild("clientHitboxToServerHitboxReference") and downHitPart) or
(downHitPart.Parent:FindFirstChild("clientHitboxToServerHitboxReference") and downHitPart.Parent) or
(downHitPart.Parent.Parent:FindFirstChild("clientHitboxToServerHitboxReference") and downHitPart.Parent.Parent) or
(downHitPart.Parent.Parent.Parent:FindFirstChild("clientHitboxToServerHitboxReference") and downHitPart.Parent.Parent.Parent)
local downHitEntity = downHitPart.Parent
if downHitRender then
downHitEntity = downHitRender.clientHitboxToServerHitboxReference.Value.Parent
end
if game.Players:GetPlayerFromCharacter(downHitEntity) then
friction = baseFriction * downHitPart.Friction
land()
else
friction = 0
local dif = (characterHitbox.Position - downHitPart.Position).unit * 10
externalVelocity = externalVelocity + Vector3.new(dif.X,0,dif.Z)
end
else
friction = baseFriction * downHitPart.Friction
end
end
if not isPlayerUnderwater or not states.isSwimming then
if externalVelocity.Y > 0.1 or downHitPart == nil or (characterHitbox.Position.Y - downHitPosition.Y) > height + 1 then
bodyVelocity.MaxForce = Vector3.new(1e4, 1e4, 1e4)
bodyPosition.MaxForce = Vector3.new()
states.isInAir = true
if externalVelocity.Y < -30 and not states.isFalling then
setCharacterMovementState("isFalling", true)
end
if not startPositionForFalling and characterHitbox.Velocity.Y < -30 and states.isFalling then
startPositionForFalling = characterHitbox.Position
end
if states.isSprinting and myClientCharacterContainer and myClientCharacterContainer:FindFirstChild("entity") then
myClientCharacterContainer.entity.RightFoot.ParticleEmitter.Enabled = false
myClientCharacterContainer.entity.LeftFoot.ParticleEmitter.Enabled = false
end
else
land()
end
else
-- player is underwater, and swimming!
bodyVelocity.MaxForce = Vector3.new(1e4, 1e4, 1e4)
bodyPosition.MaxForce = Vector3.new()
--bodyPosition.Position = downHitPosition + Vector3.new(0, height, 0)
end
local mouseMovementDirection = hitPosition and CFrame.new(
characterHitbox.Position,
Vector3.new(hitPosition.X, characterHitbox.Position.Y, hitPosition.Z)
).lookVector
mouseMovementDirection = mouseMovementDirection and (mouseMovementDirection - Vector3.new(0, mouseMovementDirection.Y, 0)).unit
if isMenuInFocus then
mouseMovementDirection = characterHitbox.CFrame.lookVector * Vector3.new(1,0,1)
end
-- process final move direction
local final_direction
if not characterArrested then
if movementAngle then
local movementDirection = workspace.CurrentCamera.CFrame:toWorldSpace(CFrame.Angles(0, movementAngle, 0)).lookVector
movementDirection = (movementDirection - Vector3.new(0, movementDirection.Y, 0)).unit
local baseSpeed = playerMovementSpeed
if isPlayerSprintingAnimationPlaying then
baseSpeed = baseSpeed * 2 + 1
end
local moveSpeed = baseSpeed * math.clamp(playerWalkspeedMultiplier, 0.5, 1)
movementUnitVector = movementUnitVector or Vector3.new(1,0,0)
movementVelocity = movementDirection * moveSpeed * movementUnitVector.magnitude
-- update player movement
bodyVelocity.Velocity = velocityHandler:stepCurrentVelocity(step)
if currentEquipType ~= "bow" and not IS_PLAYER_CAMERA_LOCKED and not isCastingSpell and mouseMovementDirection then
if not states.isSprinting then
local isInCone, isBehind, angleDiff, sign = isMouseInMovementCone(movementDirection, mouseMovementDirection, movementAngle)
if isInCone then
final_direction = CFrame.new(Vector3.new(), mouseMovementDirection)
else
if not isBehind then
final_direction = CFrame.Angles(0, MAX_CONE_ANGLE_DIFF * sign, 0) * CFrame.new(Vector3.new(), movementDirection)
else
final_direction = CFrame.new(Vector3.new(), movementDirection)
end
end
else
final_direction = CFrame.new(Vector3.new(), movementDirection)
end
elseif IS_PLAYER_CAMERA_LOCKED then
final_direction = CFrame.new(Vector3.new(), camera.CFrame.lookVector * Vector3.new(1, 0, 1))
elseif mouseMovementDirection then
final_direction = CFrame.new(Vector3.new(), mouseMovementDirection)
else
final_direction = CFrame.new(Vector3.new(), movementDirection)
end
assetFolder.bRef.Value = bodyVelocity
-- movement logic
setCharacterMovementState("isMoving", true)
else
if IS_PLAYER_CAMERA_LOCKED then
final_direction = CFrame.new(Vector3.new(), camera.CFrame.lookVector * Vector3.new(1, 0, 1))
elseif mouseMovementDirection then
final_direction = mouseMovementDirection and CFrame.new(Vector3.new(), mouseMovementDirection)
end
movementVelocity = Vector3.new()
bodyVelocity.Velocity = velocityHandler:stepCurrentVelocity(step)
-- movement logic
setCharacterMovementState("isMoving", false)
end
else
-- step even if arrested, incase we manually set it!
bodyVelocity.Velocity = velocityHandler:stepCurrentVelocity(step)
end
-- update the target listing
updateTargetsList()
if manifestCurrentlyTargetted and currentEquipType ~= "bow" and not IS_PLAYER_CAMERA_LOCKED and not states.isSprinting and not isCastingSpell and final_direction then
local frac = ((manifestCurrentlyTargetted_distanceAway - 10) / (DISTANCE_AWAY_THRESHOLD - 10)) ^ 2
local derivativeFrac = 2 * (1 / (DISTANCE_AWAY_THRESHOLD - 10)) * (manifestCurrentlyTargetted_distanceAway - 10)
if derivativeFrac < 0 then
frac = 0
end
frac = math.clamp(1 - frac, 0, 1)
final_direction = final_direction:lerp(CFrame.new(
characterHitbox.Position,
Vector3.new(manifestCurrentlyTargetted.Position.X, characterHitbox.Position.Y, manifestCurrentlyTargetted.Position.Z)
), frac)
end
if not characterArrested and final_direction and player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.state.Value ~= "dead" and not isPlayerEmoting then
if IS_PLAYER_CAMERA_LOCKED then
bodyGyro.CFrame = final_direction
else
bodyGyro.CFrame = bodyGyro.CFrame:lerp(final_direction, 0.1)
end
local diffAngle = Vector3.new(final_direction.lookVector.X, 0, final_direction.lookVector.Z).unit:Dot(Vector3.new(characterHitbox.CFrame.lookVector.X, 0, characterHitbox.CFrame.lookVector.Z).unit)
if math.abs(1 - diffAngle) > 0.05 then
if not states.isRotating then
setCharacterMovementState("isRotating", true)
end
else
if states.isRotating then
setCharacterMovementState("isRotating", false)
end
end
end
end
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.state.Value ~= "dead" then
if characterOrientationUpdateConnection then
characterOrientationUpdateConnection:disconnect()
characterOrientationUpdateConnection = nil
end
characterOrientationUpdateConnection = runService.Stepped:connect(updateCharacterOrientationFaceManifest)
end
end
local function doesPlayerHaveAbilityUnlocked(abilityId)
local playerAbilitiesSlotDataCollection = network:invoke("getCacheValueByNameTag", "abilities")
if playerAbilitiesSlotDataCollection then
for _, abilitySlotData in pairs(playerAbilitiesSlotDataCollection) do
if abilitySlotData.id == abilityId and abilitySlotData.rank > 0 then
return true
end
end
end
return false
end
local isJumping = false
local hasDoubleJumped = false
local function perform_forceJump(inputObject)
if not player.Character or not player.Character.PrimaryPart then return end
if isCharacterStunned() then return end
if states.isJumping then
if player.Character.PrimaryPart.state.Value ~= "dead" and not states.isExhausted and doesPlayerHaveAbilityUnlocked(7) and not states.isDoubleJumping and states.isInAir then
if externalVelocity.Y > -60 then
player.Character.PrimaryPart.stamina.Value = math.max(player.Character.PrimaryPart.stamina.Value - 0.5, 0)
setCharacterMovementState("isDoubleJumping", true)
externalVelocity = Vector3.new(externalVelocity.X, 80, externalVelocity.Z)
network:fire("stopChannels", "jump")
lastJump = tick()
end
--velocityHandler:applyJoltVelocity(Vector3.new(0, 75, 0))
end
else
-- regular jump
if player.Character.PrimaryPart.state.Value ~= "dead" and not states.isExhausted then
player.Character.PrimaryPart.stamina.Value = math.max(player.Character.PrimaryPart.stamina.Value - 0.5, 0)
setCharacterMovementState("isJumping", true)
network:fire("stopChannels", "jump")
local jumpPower = totalStats.jump
velocityHandler:applyJoltVelocity(Vector3.new(0, jumpPower, 0))
lastJump = tick()
end
end
if isPlayerUnderwater and inputObject then
delay(0.25, function()
if isPlayerUnderwater and inputObject.UserInputState == Enum.UserInputState.Begin then
-- still holding it down
setCharacterMovementState("isSwimming", true)
setCharacterMovementState("isSprinting", false)
isPlayerSprintingEnabled = false
while isPlayerUnderwater and inputObject.UserInputState == Enum.UserInputState.Begin do
wait(0.05)
end
setCharacterMovementState("isSwimming", false)
isPlayerSprintingEnabled = true
end
end)
end
end
local function onInputBegan(inputObject, absorbed)
if absorbed then
return false
end
if inputObject.KeyCode == Enum.KeyCode.W or inputObject.KeyCode == Enum.KeyCode.Up then
isForward = true
elseif inputObject.KeyCode == Enum.KeyCode.S or inputObject.KeyCode == Enum.KeyCode.Down then
isBackward = true
elseif inputObject.KeyCode == Enum.KeyCode.D then
isLeftward = true
elseif inputObject.KeyCode == Enum.KeyCode.A then
isRightward = true
elseif inputObject.KeyCode == Enum.KeyCode.LeftShift or inputObject.KeyCode == Enum.KeyCode.ButtonL3 and states.isMoving then
set_sprinting(true)
elseif inputObject.KeyCode == Enum.KeyCode.Space or inputObject.KeyCode == Enum.KeyCode.LeftAlt or inputObject.KeyCode == Enum.KeyCode.ButtonA then
if not characterArrested then
if isPlayerJumpEnabled and not states.isSwimming then
perform_forceJump(inputObject)
end
end
end
end
function module.doJump()
if not characterArrested then
if isPlayerJumpEnabled then
perform_forceJump()
end
end
end
function module.doSprint(val)
set_sprinting(val)
end
local function signalBasicAttacking(val)
basicAttacking = val
end
local function onInputEnded(inputObject)
--if inputObject.UserInputType == Enum.UserInputType.Keyboard then
if inputObject.KeyCode == Enum.KeyCode.W or inputObject.KeyCode == Enum.KeyCode.Up then
isForward = false
elseif inputObject.KeyCode == Enum.KeyCode.S or inputObject.KeyCode == Enum.KeyCode.Down then
isBackward = false
elseif inputObject.KeyCode == Enum.KeyCode.D then
isLeftward = false
elseif inputObject.KeyCode == Enum.KeyCode.A then
isRightward = false
elseif inputObject.KeyCode == Enum.KeyCode.LeftShift --[[or inputObject.KeyCode == Enum.KeyCode.ButtonL3]] then
set_sprinting(false)
elseif inputObject.KeyCode == Enum.KeyCode.P then
--cycleThroughTargetsList()
end
--end
end
local isSetup = false
local function onInputChanged(inputObject)
if inputObject.UserInputType == Enum.UserInputType.MouseMovement then
mouseMovementInputObject = inputObject
end
end
local function onMyClientCharacterDied()
if characterOrientationUpdateConnection then
characterOrientationUpdateConnection:disconnect()
characterOrientationUpdateConnection = nil
end
-- runService:UnbindFromRenderStep("updateCharacterOrientation")
end
local function onSetCharacterMovementStateInvoke(state, value, ...)
if states[state] == nil or states[state] == value then return end
return setCharacterMovementState(state, value, ...)
end
local function getCurrentlyFacingManifest(updateFirst)
if updateFirst then
updateTargetsList()
end
return manifestCurrentlyTargetted
end
local function setIsCastingSpell(value)
isCastingSpell = value
end
local function getMovementVelocity()
return movementVelocity
end
local function getTotalVelocity()
local bodyVelocity = player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart:FindFirstChild("hitboxVelocity")
if bodyVelocity then
return bodyVelocity.Velocity
end
return Vector3.new(0,0,0)
end
local function getCharacterMovementStates()
return states
end
local function onSetMovementVelocity(newMovementVelocity)
movementVelocity = newMovementVelocity
end
local function onSetIsJumpEnabled(isEnabled)
isPlayerJumpEnabled = isEnabled
end
local function onSetIsSprintingEnabled(isEnabled)
isPlayerSprintingEnabled = isEnabled
end
local function onPropogationRequestToSelf(nameTag, value)
if nameTag == "equipment" then
local weaponData do
for i, equipmentSlotData in pairs(value) do
if equipmentSlotData.position == 1 then
weaponData = equipmentSlotData
end
end
end
if weaponData then
currentEquipType = itemLookup[weaponData.id].equipmentType
end
end
end
local function setStamina(value, cureExhaustion)
local char = player.Character
if not char then return end
local manifest = char.PrimaryPart
if not manifest then return end
local stamina = manifest:FindFirstChild("stamina")
if not stamina then return end
local maxStamina = manifest:FindFirstChild("maxStamina")
if not maxStamina then return end
if value == "max" then
value = maxStamina.Value
end
stamina.Value = value
if cureExhaustion then
setCharacterMovementState("isExhausted", false)
end
end
function module.init(Modules)
placeSetup = Modules.placeSetup
network = Modules.network
client_utilities = Modules.client_utilities
tween = Modules.tween
utilities = Modules.utilities
terrainUtil = Modules.terrainUtil
damage = Modules.damage
animationInterface = Modules.animationInterface
IGNORE_LIST = {placeSetup.getPlaceFoldersFolder()}
entityRenderCollectionFolder = placeSetup.awaitPlaceFolder("entityRenderCollection")
entityManifestCollectionFolder = placeSetup.awaitPlaceFolder("entityManifestCollection")
totalStats = network:invoke("getCacheValueByNameTag", "nonSerializeData").statistics_final
if player.Character then
spawn(function()
onCharacterAdded(player.Character)
end)
end
network:create("doesPlayerHaveStatusEffect", "BindableFunction", "OnInvoke", doesCharacterHaveStatusEffect)
network:create("isCharacterStunned", "BindableFunction", "OnInvoke", isCharacterStunned)
network:create("mobileMovementDirectionChanged", "BindableEvent", "Event", mobileMovementDirectionChanged)
network:create("playerRequest_performEmote", "BindableFunction", "OnInvoke", performEmote)
network:create("setCharacterArrested", "BindableFunction", "OnInvoke", setCharacterArrested)
network:create("signal_menuFocusChanged", "BindableEvent", "Event", signal_menuFocusChanged)
network:connect("myClientCharacterContainerChanged", "Event", function() onCharacterAdded(player.Character) end)
network:connect("toggleCameraLockChanged", "Event", function(newValue) IS_PLAYER_CAMERA_LOCKED = newValue end)
network:create("signalBasicAttacking", "BindableEvent", "Event", signalBasicAttacking)
network:connect("playerStatisticsChanged", "OnClientEvent", playerStatisticsChanged)
userInputService.InputBegan:connect(onInputBegan)
userInputService.InputChanged:connect(onInputChanged)
userInputService.InputEnded:connect(onInputEnded)
network:connect("myClientCharacterDied", "Event", onMyClientCharacterDied)
network:create("stopChannels", "BindableEvent")
network:create("setIsChanneling", "BindableFunction", "OnInvoke", onSetIsChanneling)
network:create("setMovementVelocity", "BindableFunction", "OnInvoke", onSetMovementVelocity)
network:create("setIsJumpEnabled", "BindableFunction", "OnInvoke", onSetIsJumpEnabled)
network:create("setIsSprintingEnabled", "BindableFunction", "OnInvoke", onSetIsSprintingEnabled)
network:create("characterStateChanged", "BindableEvent")
network:create("setCharacterMovementState", "BindableFunction", "OnInvoke", onSetCharacterMovementStateInvoke)
network:create("forceCharacterMovementState", "RemoteEvent", "OnClientEvent", onSetCharacterMovementStateInvoke)
network:create("getCurrentlyFacingManifest", "BindableFunction", "OnInvoke", getCurrentlyFacingManifest)
network:create("getMovementVelocity", "BindableFunction", "OnInvoke", getMovementVelocity)
network:create("getTotalVelocity", "BindableFunction", "OnInvoke", getTotalVelocity)
network:create("setIsCastingSpell", "BindableFunction", "OnInvoke", setIsCastingSpell)
network:create("getCharacterMovementStates", "BindableFunction", "OnInvoke", getCharacterMovementStates)
network:connect("playerStatisticsChanged", "OnClientEvent", onPlayerStatisticsChanged)
network:connect("propogationRequestToSelf", "Event", onPropogationRequestToSelf)
network:connect("setStamina", "OnClientEvent", setStamina)
total_statistics = network:invoke("getCacheValueByNameTag", "nonSerializeData").statistics_final
velocityHandler = {} do
local airResistance = 20
function velocityHandler:applyJoltVelocity(vel)
if isPlayerUnderwater then
vel = vel * 0.5
end
externalVelocity = externalVelocity + vel
end
-- this is what i think of your oop damien
local function jolt(vel)
velocityHandler:applyJoltVelocity(vel)
end
network:create("applyJoltVelocityToCharacter","BindableEvent","Event",jolt)
network:connect("deathTrapKnockback", "OnClientEvent", jolt)
local cameraUnderwater
local underwaterBlur = Instance.new("BlurEffect")
underwaterBlur.Size = 4
underwaterBlur.Enabled = false
underwaterBlur.Parent = game.Lighting
local underwaterCorrect = Instance.new("ColorCorrectionEffect")
underwaterCorrect.Saturation = 0.35
underwaterCorrect.Contrast = 0.05
underwaterCorrect.Enabled = false
underwaterCorrect.Parent = game.Lighting
local underwaterBloom = Instance.new("BloomEffect")
underwaterBloom.Enabled = false
underwaterBloom.Parent = game.Lighting
local defaultReverb = game.SoundService.AmbientReverb
function velocityHandler:stepCurrentVelocity(step)
local characterHitbox = player.Character and player.Character.PrimaryPart
local frictionApplied = false
local observedVelocity = characterHitbox.Velocity
local heatExhausted = doesCharacterHaveStatusEffect("heat exhausted")
if states.isSprinting and characterHitbox.Velocity.Magnitude > 0.2 then
characterHitbox.stamina.Value = math.max(characterHitbox.stamina.Value - step, 0)
elseif not states.isExhausted and not states.isJumping and not states.isDoubleJumping then --and not states.isFalling then
local recovery = totalStats.staminaRecovery
if (tick() - lastJump) > 1 / (recovery) then
local scalar = recovery
if heatExhausted then
scalar = scalar - 1
end
characterHitbox.stamina.Value = math.min(characterHitbox.stamina.Value + (characterHitbox.maxStamina.Value * step/3 * scalar), characterHitbox.maxStamina.Value)
end
end
if heatExhausted then
characterHitbox.stamina.Value = math.max(characterHitbox.stamina.Value - step / 16, 0)
end
if characterHitbox.health.Value <= 0 or characterHitbox.state.Value == "dead" then
if states.isExhausted then
setCharacterMovementState("isExhausted", false)
end
elseif characterHitbox.stamina.Value <= 0 and not states.isExhausted then
setCharacterMovementState("isSprinting", false)
setCharacterMovementState("isExhausted", true)
spawn(function()
wait(2)
characterHitbox.stamina.Value = 0
setCharacterMovementState("isExhausted", false)
end)
network:fireServer("playerWasExhausted")
end
local desiredFOV = 70 + sprintFOV.Value + math.clamp(utilities.magnitude(observedVelocity) / 5 - 10, 0, 40)
local currentFOV = workspace.CurrentCamera.FieldOfView
if desiredFOV ~= currentFOV then
local difference = desiredFOV - currentFOV
local change = math.abs(difference) * step * 3 + 0.1
if desiredFOV > currentFOV then
if desiredFOV > currentFOV + change then
currentFOV = currentFOV + change
else
currentFOV = desiredFOV
end
else
if desiredFOV < currentFOV - change then
currentFOV = currentFOV - change
else
currentFOV = desiredFOV
end
end
if not workspace.CurrentCamera:FindFirstChild("overridden") then
workspace.CurrentCamera.FieldOfView = currentFOV
end
end
local char = game.Players.LocalPlayer.Character and game.Players.LocalPlayer.Character.PrimaryPart
if char then
if (not isPlayerUnderwater) and terrainUtil.isPointUnderwater(char.Position) then
externalVelocity = externalVelocity / 5
isPlayerUnderwater = true
-- bubbles should be tied to your head being underwater, not your torso
--[[
if myClientCharacterContainer and myClientCharacterContainer:FindFirstChild("entity") and myClientCharacterContainer.entity:FindFirstChild("Head") and myClientCharacterContainer.entity.Head:FindFirstChild("MouthAttachment") then
myClientCharacterContainer.entity.Head.MouthAttachment.bubbleParticles.Enabled = true
end
]]
local soundMirror = game.ReplicatedStorage.assets.sounds:FindFirstChild("water_in")
if soundMirror then
local sound = Instance.new("Sound")
for property, value in pairs(game.HttpService:JSONDecode(soundMirror.Value)) do
sound[property] = value
end
sound.Parent = game.Players.LocalPlayer.Character.PrimaryPart
sound.PlaybackSpeed = math.random(105,120)/100
sound:Play()
game.Debris:AddItem(sound,5)
end
local splashPart = game.ReplicatedStorage:FindFirstChild("fishingBob")
if splashPart then
splashPart = splashPart:Clone()
splashPart.Transparency = 1
splashPart.CanCollide = false
splashPart.CFrame = CFrame.new() + char.Position
splashPart.splash.Color = ColorSequence.new(workspace.Terrain.WaterColor)
splashPart.splash:Emit(20)
splashPart.Parent = workspace.CurrentCamera
game.Debris:AddItem(splashPart,5)
end
network:fireServer("onPlayerEnteredWater", char.Position)
elseif isPlayerUnderwater and not terrainUtil.isPointUnderwater(char.Position - Vector3.new(0, 0.5, 0)) then
--[[
if myClientCharacterContainer and myClientCharacterContainer:FindFirstChild("entity") and myClientCharacterContainer.entity:FindFirstChild("Head") and myClientCharacterContainer.entity.Head:FindFirstChild("MouthAttachment") then
myClientCharacterContainer.entity.Head.MouthAttachment.bubbleParticles.Enabled = false
end
]]
isPlayerUnderwater = false
if states.isSwimming then
setCharacterMovementState("isSwimming", false)
setCharacterMovementState("isJumping", false)
setCharacterMovementState("isDoubleJumping", false)
perform_forceJump()
end
local soundMirror = game.ReplicatedStorage.assets.sounds:FindFirstChild("water_out")
if soundMirror then
local sound = Instance.new("Sound")
for property, value in pairs(game.HttpService:JSONDecode(soundMirror.Value)) do
sound[property] = value
end
sound.Parent = game.Players.LocalPlayer.Character.PrimaryPart
sound.PlaybackSpeed = math.random(105,120)/100
sound:Play()
game.Debris:AddItem(sound,5)
end
end
end
if terrainUtil.isPointUnderwater(workspace.CurrentCamera.CFrame.Position) then
if not cameraUnderwater then
cameraUnderwater = true
game.SoundService.AmbientReverb = "UnderWater"
underwaterBlur.Enabled = true
underwaterCorrect.Enabled = true
underwaterBloom.Enabled = true
end
elseif cameraUnderwater and not terrainUtil.isPointUnderwater(workspace.CurrentCamera.CFrame.Position + Vector3.new(0, 0.25, 0)) then
cameraUnderwater = false
game.SoundService.AmbientReverb = defaultReverb
underwaterBlur.Enabled = false
underwaterCorrect.Enabled = false
underwaterBloom.Enabled = false
end
if not states.isSwimming and (utilities.magnitude(externalVelocity) > 0 or states.isInAir) then
-- Collision velocity decrement
local expectedVelocity = externalVelocity + movementVelocity
local observedVelocity = characterHitbox.Velocity
local collideDecrementX = math.abs(expectedVelocity.X - observedVelocity.X) * step * 3
local collideDecrementZ = math.abs(expectedVelocity.Z - observedVelocity.Z) * step * 3
local externalXZ = Vector3.new(externalVelocity.X, 0, externalVelocity.Z)
local gravityDecrement = Vector3.new(0, workspace.Gravity * step, 0)
if isPlayerUnderwater then
gravityDecrement = gravityDecrement / 5
end
-- Normal velocity decrement (Friction/Air resist)
if states.isInAir then
local volumeGoal = math.clamp((observedVelocity.magnitude - 100) / 500, windBaseVolume, 2)
local delta = math.abs(volumeGoal - wind.Volume)
if wind.Volume < volumeGoal then
wind.Volume = wind.Volume + math.clamp(delta, 0.01, 0.1)
else
wind.Volume = wind.Volume - math.clamp(delta, 0.01, 0.1)
end
--local airDecrement = airResistance * step
local airDecrementX = airResistance * step * (math.abs(externalVelocity.X) / utilities.magnitude(externalXZ)) + collideDecrementX
local airDecrementZ = airResistance * step * (math.abs(externalVelocity.Z) / utilities.magnitude(externalXZ)) + collideDecrementZ
if math.abs(externalVelocity.X) > airDecrementX then
externalVelocity = Vector3.new(externalVelocity.X - airDecrementX * math.sign(externalVelocity.X), externalVelocity.Y, externalVelocity.Z)
else
externalVelocity = Vector3.new(0, externalVelocity.Y, externalVelocity.Z)
end
if math.abs(externalVelocity.Z) > airDecrementZ then
externalVelocity = Vector3.new(externalVelocity.X, externalVelocity.Y, externalVelocity.Z - airDecrementZ * math.sign(externalVelocity.Z))
else
externalVelocity = Vector3.new(externalVelocity.X, externalVelocity.Y, 0)
end
externalVelocity = externalVelocity - gravityDecrement
else
if wind.Volume > windBaseVolume then
wind.Volume = wind.Volume - 0.02
end
local frictionDecrementX = friction * step * (math.abs(externalVelocity.X) / utilities.magnitude(externalXZ)) + collideDecrementX
local frictionDecrementZ = friction * step * (math.abs(externalVelocity.Z) / utilities.magnitude(externalXZ)) + collideDecrementZ
if frictionDecrementX > 0 then
if math.abs(externalVelocity.X) > frictionDecrementX then
externalVelocity = Vector3.new(externalVelocity.X - frictionDecrementX * math.sign(externalVelocity.X), externalVelocity.Y, externalVelocity.Z)
frictionApplied = true
else
externalVelocity = Vector3.new(0, externalVelocity.Y, externalVelocity.Z)
end
end
if frictionDecrementZ > 0 then
if math.abs(externalVelocity.Z) > frictionDecrementZ then
externalVelocity = Vector3.new(externalVelocity.X, externalVelocity.Y, externalVelocity.Z - frictionDecrementZ * math.sign(externalVelocity.Z))
frictionApplied = true
else
externalVelocity = Vector3.new(externalVelocity.X, externalVelocity.Y, 0)
end
end
if externalVelocity.Y >= gravityDecrement.Y then
externalVelocity = externalVelocity - gravityDecrement
else
externalVelocity = Vector3.new(externalVelocity.X, 0, externalVelocity.Z)
end
end
else
if wind.Volume > windBaseVolume then
wind.Volume = wind.Volume - 0.02
end
end
if myClientCharacterContainer and myClientCharacterContainer:FindFirstChild("entity") then
if frictionApplied then
local speed = math.clamp(utilities.magnitude(externalVelocity) / 3, 5, 60)
myClientCharacterContainer.entity.RightFoot.smoke.Rate = speed
myClientCharacterContainer.entity.LeftFoot.smoke.Rate = speed
end
myClientCharacterContainer.entity.RightFoot.smoke.Enabled = frictionApplied
myClientCharacterContainer.entity.LeftFoot.smoke.Enabled = frictionApplied
end
local velocityMulti = 1
if isPlayerUnderwater then
velocityMulti = 0.7
end
if player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.state.Value == "dead" then
movementVelocity = Vector3.new()
end
if isPlayerUnderwater and states.isSwimming then
externalVelocity = Vector3.new()
return movementVelocity * velocityMulti + Vector3.new(0, 16, 0)
else
return (movementVelocity + externalVelocity) * velocityMulti
end
end
end
-- todo: make it so you can't exploit this, coming soon.
while not player.Character or not player.Character.PrimaryPart do
wait(0.5)
end
-- todo: fix
local timeSinceLastInput = tick()
userInputService.InputBegan:connect(function()
timeSinceLastInput = tick()
end)
spawn(function()
while true do
if player.Character and player.Character.PrimaryPart then
if player.Character.PrimaryPart.state.Value == "idling" and (tick() - timeSinceLastInput) > 5 and not isPlayerEmoting and not basicAttacking then
local options = {"idling_kicking"}
local option = options[math.random(#options)]
animationInterface:replicatePlayerAnimationSequence("emoteAnimations", option)
end
end
wait(math.random(5, 10))
end
end)
setupRenderSteppedConnection()
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/coreRenderServices/appearance_manager.lua
================================================
local appearence_manager = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage:WaitForChild("modules"))
local mapping = modules.load("mapping")
local function isBodyPart(obj)
return obj:IsA("BasePart") and replicatedStorage.playerBaseCharacter:FindFirstChild(obj.Name) and replicatedStorage.playerBaseCharacter[obj.Name]:IsA("BasePart")
end
function appearence_manager.ApplySkinColor(appearanceData, renderCharacter, accessoryLookup, assets)
if appearanceData and appearanceData.accessories.skinColorId then
for _, obj in pairs(renderCharacter:GetChildren()) do
if obj:IsA("BasePart") and isBodyPart(obj) then
obj.Color = assets.accessories.skinColor:FindFirstChild(tostring(appearanceData.accessories.skinColorId or 1)).Value
end
end
else
for _, obj in pairs(renderCharacter:GetChildren()) do
if obj:IsA("BasePart") and isBodyPart(obj) then
obj.Color = BrickColor.new("Light orange").Color
end
end
end
end
function appearence_manager.LoadAppearence(accessoryLookup, appearanceData, hatEquipmentData, itemLookup, renderCharacter)
--damien this took 5 min-=
-- cry me a river
local hairColor = accessoryLookup.hairColor:FindFirstChild(tostring(appearanceData.accessories.hairColorId or 1)).Value
local shirtColor = accessoryLookup.shirtColor:FindFirstChild(tostring(appearanceData.accessories.shirtColorId or 1)).Value
for accessoryType, id in pairs(appearanceData.accessories) do
if accessoryType == "hair" and hatEquipmentData then
local itemBaseData = itemLookup[hatEquipmentData.id]
if itemBaseData then
local equipmentHairType_accessory = itemBaseData.equipmentHairType or 1
if equipmentHairType_accessory == mapping.equipmentHairType.partial then
id = id .. "_clipped"
elseif equipmentHairType_accessory == mapping.equipmentHairType.none then
-- no hair
id = ""
end
end
end
if accessoryLookup:FindFirstChild(accessoryType) then
local accessoryToLookIn = replicatedStorage.hairClipped:FindFirstChild(tostring(id)) or accessoryLookup[accessoryType]:FindFirstChild(tostring(id))
if accessoryToLookIn then
for _, accessoryPartContainer in pairs(accessoryToLookIn:GetChildren()) do
if renderCharacter:FindFirstChild(accessoryPartContainer.Name) then
if accessoryPartContainer:FindFirstChild("shirtTag") then
renderCharacter[accessoryPartContainer.Name].Color = shirtColor
elseif accessoryPartContainer:FindFirstChild("colorOverride") then
renderCharacter[accessoryPartContainer.Name].Color = accessoryPartContainer.Color
end
for _, accessoryPart in pairs(accessoryPartContainer:GetChildren()) do
if accessoryPart:IsA("BasePart") then
local accessory = accessoryPart:Clone()
accessory.Anchored = false
accessory.CanCollide = false
if accessory.Name == "hair_Head" then
accessory.Color = hairColor
end
if accessory.Name == "shirt" or accessory:FindFirstChild("shirtTag") then
accessory.Color = shirtColor
end
local projectionWeld = Instance.new("Motor6D")
projectionWeld.Parent = accessory
projectionWeld.Name = "projectionWeld"
projectionWeld.Part0 = accessory
projectionWeld.Part1 = renderCharacter[accessoryPartContainer.Name]
projectionWeld.C0 = CFrame.new()
projectionWeld.C1 = accessoryPartContainer.CFrame:toObjectSpace(accessoryPart.CFrame)
accessory.Name = "!! ACCESSORY !!"
accessory.Parent = renderCharacter
end
end
end
end
end
end
end
end
return appearence_manager
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/coreRenderServices/bow_manager.lua
================================================
local bow_manager = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local httpService = game:GetService("HttpService")
local modules = require(replicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local projectile = modules.load("projectile")
local assets = game.ReplicatedStorage:WaitForChild("assets")
function bow_manager.int__updateRenderCharacter(renderCharacter,inventoryCountLookup,equipmentSlotData,configuration,itemLookup)
-- arrow is funny hehe
-- todo: customize this per bow
local strap = assets.entities.ArrowUpperTorso2.strap:Clone()
strap.Anchored = false
strap.CanCollide = false
local strapprojectionWeld = Instance.new("Motor6D", strap)
strapprojectionWeld.Name = "projectionWeld"
strapprojectionWeld.Part0 = strap
strapprojectionWeld.Part1 = renderCharacter.UpperTorso
strapprojectionWeld.C0 = CFrame.new()
strapprojectionWeld.C1 = assets.entities.ArrowUpperTorso2.CFrame:toObjectSpace(assets.entities.ArrowUpperTorso2.strap.CFrame)
strap.Name = "!! ARROW !!"
strap.Parent = renderCharacter
local quiver = assets.entities.ArrowUpperTorso2.quiver:Clone()
quiver.Anchored = false
quiver.CanCollide = false
local quiverprojectionWeld = Instance.new("Motor6D", quiver)
quiverprojectionWeld.Name = "projectionWeld"
quiverprojectionWeld.Part0 = quiver
quiverprojectionWeld.Part1 = renderCharacter.UpperTorso
quiverprojectionWeld.C0 = CFrame.new()
quiverprojectionWeld.C1 = assets.entities.ArrowUpperTorso2.CFrame:toObjectSpace(assets.entities.ArrowUpperTorso2.quiver.CFrame)
quiver.Name = "!! ARROW !!"
quiver.Parent = renderCharacter
-- represent the arrows
local arrows = inventoryCountLookup[equipmentSlotData.id] or 0
local arrowParts = math.clamp(math.floor(arrows / configuration.getConfigurationValue("arrowsPerArrowPartVisualization")) + 1, 0, configuration.getConfigurationValue("maxArrowPartsVisualization"))
for ai = 1, arrowParts do
local arrow = itemLookup[equipmentSlotData.id].module.manifest:Clone()
arrow.CanCollide = false
arrow.Anchored = false
arrow.Parent = quiver
local xRan, yRan = math.random() * 2 - 1, math.random() * 2 - 1
local arrowWeld = Instance.new("Motor6D", quiver)
arrowWeld.Name = "projectionWeld"
arrowWeld.Part0 = quiver
arrowWeld.Part1 = arrow
arrowWeld.C0 = quiver.Attachment.CFrame
arrowWeld.C1 = CFrame.Angles(xRan * math.rad(15), 0, yRan * math.rad(15))
end
end
function bow_manager.PlayAnimation(playerStats,animationToBePlayed,entityManifest,animationSequenceName,configuration,animationInterface,associatePlayer,characterEntityAnimationTracks,client,damage,animationName,renderEntityData,utilities,assetFolder,entitiesFolder,extraData)
-- bows, make sure all other bow animations stop!
if animationSequenceName == "bowAnimations" then
local currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
if currentWeaponManifest then
currentWeaponManifest = currentWeaponManifest:IsA("Model") and currentWeaponManifest.PrimaryPart or currentWeaponManifest
end
animationInterface:stopPlayingAnimationsByAnimationCollectionName(characterEntityAnimationTracks, "bowAnimations")
if animationName == "stretching_bow_stance" then
renderEntityData.currentPlayerWeaponAnimations.stretch:Play(nil, nil, 1)
utilities.playSound("bowDraw", currentWeaponManifest)
local arrow = assetFolder.arrow:Clone()
renderEntityData.stanceArrow = arrow
arrow.arrowWeld.Part0 = renderEntityData.currentPlayerWeapon.slackRopeRepresentation
arrow.arrowWeld.C0 = CFrame.Angles(-math.pi / 2, 0, 0) * CFrame.new(0, (-arrow.Size.Y/2) - 0.1, 0)
arrow.Parent = entitiesFolder
elseif animationName == "firing_bow_stance" then
local stanceArrowSpeed = 400
local stanceArrowGravity = 0
renderEntityData.currentPlayerWeaponAnimations.fire:Play()
utilities.playSound("bowFireStance", currentWeaponManifest)
local shotCFrame = CFrame.new()
local visualArrow = renderEntityData.stanceArrow
renderEntityData.stanceArrow = nil
if visualArrow then
shotCFrame = visualArrow.CFrame
visualArrow:Destroy()
end
local function ringEffect(cframe, size)
size = size or 1
local ring = script:FindFirstChild("ring"):Clone()
ring.CFrame = cframe * CFrame.Angles(math.pi/2,0,0)
ring.Size = Vector3.new(2, 0.2, 2) * size
ring.Parent = entitiesFolder
local duration = 0.5
tween(ring, {"Size"}, {ring.Size * 4 * size}, duration, Enum.EasingStyle.Quad)
tween(ring, {"Transparency"}, {1}, duration, Enum.EasingStyle.Linear)
game:GetService("Debris"):AddItem(ring, duration)
end
local arrow = assetFolder.arrow:Clone()
arrow.Anchored = true
arrow.CFrame = shotCFrame
arrow.Trail.Enabled = true
arrow.Trail.Lifetime = 1.5
arrow.Trail.WidthScale = NumberSequence.new(1, 8)
arrow.Parent = entitiesFolder
local unitDirection = (extraData["mouse-target-position"] - shotCFrame.Position).Unit
local targetsHit = {}
ringEffect((CFrame.new(Vector3.new(), unitDirection) + shotCFrame.Position) * CFrame.new(0, 0, -2))
local lifetime = 10
--game:GetService("Debris"):AddItem(arrow, lifetime)
local ringLast = tick()
local ringTime = 0.05
projectile.createProjectile(
shotCFrame.Position,
unitDirection,
stanceArrowSpeed,
arrow,
function(hitPart, hitPosition, hitNormal, hitMaterial)
local canDamageTarget, target = damage.canPlayerDamageTarget(game.Players.LocalPlayer, hitPart)
if canDamageTarget and target then
if not targetsHit[target] then
targetsHit[target] = true
utilities.playSound("bowArrowImpact", arrow)
ringEffect(arrow.CFrame * CFrame.Angles(math.pi / 2, 0, 0))
if associatePlayer == client and canDamageTarget then
network:fire("requestEntityDamageDealt", target, hitPosition, "equipment", nil, "ranger stance")
end
end
return true
else
arrow.Trail.Enabled = false
game:GetService("Debris"):AddItem(arrow, arrow.Trail.Lifetime)
end
end,
function(t)
local since = tick() - ringLast
if since >= ringTime then
ringLast = tick()
ringEffect(arrow.CFrame * CFrame.Angles(math.pi / 2, 0, 0), 0.4)
end
return CFrame.Angles(math.pi / 2, 0, 0)
end,
projectile.makeIgnoreList{
entityManifest,
renderEntityData.entityContainer,
},
true,
stanceArrowGravity,
lifetime
)
print(renderEntityData.entityContainer:GetFullName())
elseif animationName == "stretching_bow" then
if renderEntityData.firingAnimationStoppedConnection then
renderEntityData.firingAnimationStoppedConnection:disconnect()
renderEntityData.firingAnimationStoppedConnection = nil
end
local bowPullBackTime = configuration.getConfigurationValue("bowPullBackTime")
local atkspd = (extraData and extraData.attackSpeed) or 0
renderEntityData.bowStrechAnimationStopped = renderEntityData.currentPlayerWeaponAnimations.stretch.Stopped:connect(onBowStrechingAnimationStopped)
renderEntityData.currentPlayerWeaponAnimations.stretch:Play(
0.1,
1,
(renderEntityData.currentPlayerWeaponAnimations.stretch.Length / bowPullBackTime) * (1 + atkspd)
)
utilities.playSound("bowDraw", currentWeaponManifest)
local drawStartTime = tick()
local numArrows = extraData.numArrows or 1
local firingSeed = extraData.firingSeed or 1
-- set-up the arrow
renderEntityData.currentArrows = {}
renderEntityData.firingSeed = firingSeed
-- how far should each arrow be rotated from eachother
local arrowAnglePadding = 3
local startingAngle = -((numArrows - 1)*arrowAnglePadding)/2
local closestAngleToZero = math.huge
for i = 1, numArrows do
local newArrow = assetFolder.arrow:Clone()
newArrow.Parent = workspace.CurrentCamera
local angleOffset = startingAngle + (i-1)*arrowAnglePadding
table.insert(renderEntityData.currentArrows, {
arrow = newArrow,
angleOffset = angleOffset,
orientation = CFrame.Angles(0, math.pi, 0) * CFrame.Angles(math.pi/2,0,0) * CFrame.Angles(math.rad(angleOffset*3),0,0)
})
if math.abs(angleOffset) < closestAngleToZero then
renderEntityData.primaryArrow = newArrow -- used for auto-targeting in bow damage interface
closestAngleToZero = math.abs(angleOffset)
end
end
renderEntityData.currentDrawStartTime = drawStartTime
if utilities.doesPlayerHaveEquipmentPerk(associatePlayer, "overdraw") then
--renderEntityData.currentArrow.Size = renderEntityData.currentArrow.Size * 2
for _, arrowData in pairs(renderEntityData.currentArrows) do
local arrow = arrowData.arrow
arrow.Size = arrow.Size * 2
end
end
-- I have no clue what this is for so I will not touch it ~nimblz
delay(configuration.getConfigurationValue("maxBowChargeTime"), function()
if renderEntityData.currentDrawStartTime == drawStartTime and renderEntityData.currentArrows then
for _, arrowData in pairs(renderEntityData.currentArrows) do
local arrow = arrowData.arrow
arrow.Material = Enum.Material.Neon
arrow.BrickColor = BrickColor.new("Institutional white")
end
end
end)
-- apply welds
for _, arrowData in pairs(renderEntityData.currentArrows) do
local arrow = arrowData.arrow
arrow.arrowWeld.Part0 = renderEntityData.currentPlayerWeapon.slackRopeRepresentation
arrow.arrowWeld.C0 = arrowData.orientation * CFrame.new(0, (-arrow.Size.Y/2) - 0.1, 0)
end
-- update state
renderEntityData.weaponState = "streched"
onEntityStateChanged(entityManifest.state.Value)
-- do this.. hehe
if animationToBePlayed then
if typeof(characterEntityAnimationTracks[animationSequenceName][animationName]) == "Instance" then
characterEntityAnimationTracks[animationSequenceName][animationName]:Play(
0.1,
1,
(renderEntityData.currentPlayerWeaponAnimations.stretch.Length / bowPullBackTime) * (1 + atkspd)
)
elseif typeof(characterEntityAnimationTracks[animationSequenceName][animationName]) == "table" then
animationToBePlayed = animationToBePlayed[1]
for i, obj in pairs(characterEntityAnimationTracks[animationSequenceName][animationName]) do
obj:Play(
0.1,
1,
(renderEntityData.currentPlayerWeaponAnimations.stretch.Length / bowPullBackTime) * (1 + atkspd)
)
end
end
end
return
elseif animationName == "firing_bow" and renderEntityData.currentArrows then
if renderEntityData.bowStrechAnimationStopped then
renderEntityData.bowStrechAnimationStopped:disconnect()
renderEntityData.bowStrechAnimationStopped = nil
end
if renderEntityData.currentPlayerWeaponAnimations.stretch.IsPlaying then
renderEntityData.currentPlayerWeaponAnimations.stretch:Stop()
end
if renderEntityData.currentPlayerWeaponAnimations.stretchHold.IsPlaying then
renderEntityData.currentPlayerWeaponAnimations.stretchHold:Stop()
end
if extraData.canceled then
for _, arrowData in pairs(renderEntityData.currentArrows) do
arrowData.arrow:Destroy()
end
renderEntityData.currentArrows = nil
animationToBePlayed = nil
-- force reset state
renderEntityData.weaponState = nil
onEntityStateChanged(entityManifest.state.Value)
else
local function onFiringAnimationStopped()
if renderEntityData.firingAnimationStoppedConnection then
renderEntityData.firingAnimationStoppedConnection:disconnect()
renderEntityData.firingAnimationStoppedConnection = nil
end
-- update state
renderEntityData.weaponState = nil
onEntityStateChanged(entityManifest.state.Value)
end
renderEntityData.firingAnimationStoppedConnection = renderEntityData.currentPlayerWeaponAnimations.fire.Stopped:connect(onFiringAnimationStopped)
renderEntityData.currentPlayerWeaponAnimations.fire:Play()
utilities.playSound("bowFire", currentWeaponManifest)
local isMagical = playerStats.int >= 30 -- Is magical, use AOE
local explodeRadius = 1.5
local explodeDurration = 1 / 4
if playerStats.int >= 70 then
explodeRadius = 2.5
end
if playerStats.int >= 150 then
explodeRadius = 4
explodeDurration = 3 / 8
end
local maxPierces = utilities.calculatePierceFromStr(playerStats.str)
local numArrows = #renderEntityData.currentArrows
local arrowSpeed = (renderEntityData.weaponBaseData.projectileSpeed or 200) * math.clamp(extraData.bowChargeTime / configuration.getConfigurationValue("maxBowChargeTime"), 0.1, 1)
local speedScalar = maxPierces - (numArrows/2)
speedScalar = math.max(speedScalar, -1) -- this clamps slowest possible speed to 50% of default
arrowSpeed = arrowSpeed + (arrowSpeed * speedScalar * 0.5) -- 50% speed buff per pierc
if utilities.doesPlayerHaveEquipmentPerk(associatePlayer, "overdraw") then
arrowSpeed = arrowSpeed * 2
end
local unitDirection, adjusted_targetPosition = projectile.getUnitVelocityToImpact_predictiveByAbilityExecutionData(
renderEntityData.currentPlayerWeapon.slackRopeRepresentation.Position,
renderEntityData.weaponBaseData.projectileSpeed or 200, -- act as if you were shooting at full
extraData
)
local shotOrigin = CFrame.new(
renderEntityData.currentPlayerWeapon.slackRopeRepresentation.Position,
renderEntityData.currentPlayerWeapon.slackRopeRepresentation.Position + unitDirection
) * CFrame.new(0,0,-1.5)
-- do launch effect
if maxPierces > 0 then
local durration = 0.25
local newRing = script:FindFirstChild("ring"):Clone()
newRing.CFrame = shotOrigin * CFrame.new(0,0,-1) * CFrame.Angles(math.pi/2,0,0)
newRing.Size = Vector3.new(2, 0.2, 2)
newRing.Parent = workspace.CurrentCamera
tween(newRing, {"Size"}, {Vector3.new(3 + (maxPierces*1),0.2,3 + (maxPierces*1))}, durration, Enum.EasingStyle.Quad)
tween(newRing, {"Transparency"}, {1}, durration, Enum.EasingStyle.Linear)
local explosionBall = Instance.new("Part")
local scaler = Instance.new("SpecialMesh")
explosionBall.Size = Vector3.new(3+maxPierces,3+maxPierces,2)
explosionBall.Color = Color3.fromRGB(255,255,255)
explosionBall.Anchored = true
explosionBall.CanCollide = false
explosionBall.Material = Enum.Material.Neon
explosionBall.CFrame = shotOrigin * CFrame.new(0,0,-1.5)
scaler.MeshType = Enum.MeshType.Sphere
scaler.Parent = explosionBall
explosionBall.Parent = workspace.CurrentCamera
local finalLength = 6+(maxPierces*2)
tween(explosionBall, {"Transparency"}, {1}, durration/2, Enum.EasingStyle.Linear)
tween(explosionBall, {"Size"}, {Vector3.new(0.5,0.5,finalLength)}, durration/2, Enum.EasingStyle.Quad)
tween(explosionBall, {"CFrame"}, {shotOrigin * CFrame.new(0,0,-(1.5 + finalLength/2))}, durration/2, Enum.EasingStyle.Quad)
game:GetService("Debris"):AddItem(newRing, durration)
game:GetService("Debris"):AddItem(explosionBall, durration)
end
local arrowRandomizer = Random.new(renderEntityData.firingSeed)
local guid = httpService:GenerateGUID(false)
-- shoot arrows
for _, arrowData in pairs(renderEntityData.currentArrows) do
local arrow = arrowData.arrow
arrow.arrowWeld:Destroy()
arrow.Anchored = true
local pierceCount = 0
local entityPierceBlacklist = {}
local shotOrientation = CFrame.new(Vector3.new(0,0,0), unitDirection)
local displacedShotOrientation = shotOrientation
if numArrows < 4 then
displacedShotOrientation = shotOrientation *CFrame.Angles(
arrowRandomizer:NextNumber(-0.025, 0.025),
math.rad(arrowData.angleOffset),
0
)
else
displacedShotOrientation = shotOrientation * CFrame.Angles(
math.rad(numArrows * 0.8) * arrowRandomizer:NextNumber(-1, 1),
math.rad(arrowData.angleOffset) + (math.rad(5) * arrowRandomizer:NextNumber(-1, 1)),
0
)
end
local finalUnitDirection = displacedShotOrientation.LookVector
if numArrows == 1 and pierceCount >= 1 then
finalUnitDirection = unitDirection
end
if arrow:FindFirstChild("Trail") then
arrow.Trail.Enabled = true
end
renderEntityData.currentDrawStartTime = nil
projectile.createProjectile(
shotOrigin.Position,
finalUnitDirection,
arrowSpeed, --renderEntityData.weaponBaseData.projectileSpeed or 200,
arrow,
function(hitPart, hitPosition, hitNormal, hitMaterial)
--[[
if hitNormal then
currentArrow.CFrame = CFrame.new(hitPosition, hitPosition + hitNormal) * CFrame.Angles(-math.rad(90), 0, 0)
end
]]
local function explode(needsToHit)
local explosionBall = Instance.new("Part")
local scaler = Instance.new("SpecialMesh")
explosionBall.Size = Vector3.new(explodeRadius*2,explodeRadius*2,explodeRadius*2)
explosionBall.Shape = Enum.PartType.Ball
explosionBall.Color = Color3.fromRGB(255,255,255)
explosionBall.Anchored = true
explosionBall.CanCollide = false
explosionBall.Material = Enum.Material.Neon
explosionBall.CFrame = CFrame.new(hitPosition)
scaler.Scale = Vector3.new(0,0,0)
scaler.MeshType = Enum.MeshType.Sphere
scaler.Parent = explosionBall
explosionBall.Parent = workspace.CurrentCamera
tween(explosionBall, {"Transparency"}, {1}, explodeDurration, Enum.EasingStyle.Linear)
tween(explosionBall, {"Color"}, {Color3.fromRGB(0,255,100)}, explodeDurration, Enum.EasingStyle.Linear)
tween(scaler, {"Scale"}, {Vector3.new(1,1,1) * 1.25}, explodeDurration, Enum.EasingStyle.Quint)
game:GetService("Debris"):AddItem(explosionBall, explodeDurration*1.15)
-- do some AOE dmg
if associatePlayer == client then
for i, v in pairs(damage.getDamagableTargets(client)) do
local vSize = (v.Size.X + v.Size.Y + v.Size.Z)/6
if (v.Position - hitPosition).magnitude <= (explodeRadius) + vSize and v ~= needsToHit then
delay(0.1, function()
network:fire("requestEntityDamageDealt", v, hitPosition, "equipment", nil, nil, guid)
end)
end
end
if needsToHit then
delay(0.1, function()
network:fire("requestEntityDamageDealt", needsToHit, hitPosition, "equipment", nil, nil, guid)
end)
end
end
end
local function ring(initialTransparency, initialRadius, finalRadius, lifetime)
initialTransparency = initialTransparency or 3/4
initialRadius = initialRadius or 1
finalRadius = finalRadius or 2
lifetime = lifetime or 1/3
local newRing = script:FindFirstChild("ring"):Clone()
newRing.CFrame = arrow.CFrame
newRing.Transparency = initialTransparency
newRing.Size = Vector3.new(initialRadius, 0.5, initialRadius)
newRing.Parent = workspace.CurrentCamera
tween(newRing, {"Size"}, {Vector3.new(finalRadius,0.2,finalRadius)}, lifetime*1.15, Enum.EasingStyle.Quint)
tween(newRing, {"Transparency"}, {1}, lifetime, Enum.EasingStyle.Linear)
game:GetService("Debris"):AddItem(newRing, lifetime*1.15)
end
if hitPart then
if (hitPart:IsDescendantOf(entityRenderCollectionFolder) or hitPart:IsDescendantOf(entityManifestCollectionFolder)) then -- entity impact
-- pierce check
local canDamageTarget, trueTarget = damage.canPlayerDamageTarget(game.Players.LocalPlayer, hitPart)
if trueTarget and not entityPierceBlacklist[trueTarget] then
entityPierceBlacklist[trueTarget] = true
if isMagical then
-- play magic sound
utilities.playSound("magicAttack", arrow)
else
utilities.playSound("bowArrowImpact", arrow)
end
if isMagical then -- do aoe dmg
explode(trueTarget)
else -- do direct damage
if associatePlayer == client and canDamageTarget then -- we shot this arrow, dmg the entity
network:fire("requestEntityDamageDealt", trueTarget, hitPosition, "equipment", nil, nil, guid)
end
end
pierceCount = pierceCount + 1
if pierceCount <= maxPierces then -- did pierce an entity
local intensity = maxPierces - (pierceCount-1)
intensity = math.clamp(intensity,1,8)
local intensityCalls = {
function() ring(2/3, 1, 2, 1/3) end, -- 1
function() ring(1/2, 1.25, 3, 1/3) end, -- 2
function() ring(1/3, 1.5, 4, 1/2) end, -- 3
function() ring(1/4, 2, 5, 1/2) end, -- 4
function() ring(1/5, 1.5, 6, 1/2) end, -- 5
function() ring(1/8, 2, 7, 2/3) end, -- 6
function() ring(1/8, 2.5, 7.5, 2/3) end, -- 7
function() ring(0, 3, 8, 2/3) end, -- 8
}
(intensityCalls[intensity] or intensityCalls[3])()
return true
else
arrow.Anchored = false
weld(arrow, hitPart)
game:GetService("Debris"):AddItem(arrow, 3)
return false
end
elseif trueTarget and entityPierceBlacklist[trueTarget] then
return true
end
else -- world impact
if arrow:FindFirstChild("impact") then
local hitColor = hitPart.Color
if hitPart == workspace.Terrain then
if hitMaterial ~= Enum.Material.Water then
hitColor = hitPart:GetMaterialColor(hitMaterial)
else
hitColor = BrickColor.new("Cyan").Color
end
end
local emitPart = Instance.new("Part")
emitPart.Size = Vector3.new(0.1,0.1,0.1)
emitPart.Transparency = 1
emitPart.Anchored = true
emitPart.CanCollide = false
emitPart.CFrame = (arrow.CFrame - arrow.CFrame.p) + hitPosition
local impact = arrow.impact:Clone()
impact.Parent = emitPart
emitPart.Parent = workspace.CurrentCamera
impact.Color = ColorSequence.new(hitColor)
impact:Emit(10)
game:GetService("Debris"):AddItem(emitPart,3)
game:GetService("Debris"):AddItem(arrow, 3)
tween(arrow, {"Transparency"}, {1}, 3, Enum.EasingStyle.Linear)
end
if isMagical then
explode()
end
return false
end
end
end,
function(t)
return CFrame.Angles(math.rad(90), 0, 0)
end,
-- ignore list
{entityManifest; renderEntityData.entityContainer},
-- points to next position
true
)
renderEntityData.currentArrows = nil
end
end
end
end
end
return bow_manager
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/coreRenderServices/chat_manager.lua
================================================
-- variables
local MAX_CHAT_BUBBLE_COUNT = 3
-- essentials
local replicatedStorage = game:GetService("ReplicatedStorage")
local players = game:GetService("Players")
local playerScripts = players.LocalPlayer:WaitForChild("PlayerScripts")
local modules = require(replicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local runService = game:GetService("RunService")
local assetFolder = playerScripts:WaitForChild("assets")
local function getOldestChatBubble(chats)
local oldest
local lowestLayoutOrder = 99
for _, chat in pairs(chats) do
if chat:IsA("GuiObject") and chat.LayoutOrder < lowestLayoutOrder then
oldest = chat
lowestLayoutOrder = chat.LayoutOrder
end
end
return oldest
end
local function setPrimaryChatBubble(chatBubble)
if not chatBubble.titleFrame.Visible then
if game.Players.LocalPlayer.Character and chatBubble:IsDescendantOf(game.Players.LocalPlayer.Character) then
return false
end
local size = chatBubble.Size
if chatBubble.titleFrame.title.Text ~= "" then
chatBubble.titleFrame.Visible = true
chatBubble.Size = size + UDim2.new(0, 0, 0, 10)
chatBubble.contents.Position = chatBubble.contents.Position + UDim2.new(0, 0, 0, 5)
local dif = (chatBubble.titleFrame.AbsoluteSize.X + 20) - chatBubble.AbsoluteSize.X
if dif > 0 then
chatBubble.Size = size + UDim2.new(0, dif, 0, 0)
end
end
end
end
local function getChatTagPartForEntity(entityContainer)
for _, chatTagPart in pairs(game.CollectionService:GetTagged("chatTag")) do
if chatTagPart.Parent == entityContainer then
return chatTagPart
end
end
end
local function createChatTagPart(entityContainer, offset, rangeMulti)
--[[
local chatTag = entityContainer.PrimaryPart:FindFirstChilfd("ChatTag") or assetFolder.ChatTag:Clone()
chatTag.Parent = entityContainer.PrimaryPart
]]
local chatTag = entityContainer:FindFirstChild("chatGui") or assetFolder.misc.chatGui:Clone()
chatTag.Parent = entityContainer
chatTag.Adornee = entityContainer.PrimaryPart
chatTag.Enabled = true
local rangeMultiTag = Instance.new("NumberValue")
rangeMultiTag.Name = "rangeMulti"
rangeMultiTag.Value = rangeMulti or 1
rangeMultiTag.Parent = chatTag
offset = offset or Vector3.new()
local offsetTag = Instance.new("Vector3Value")
offsetTag.Name = "offset"
offsetTag.Value = offset
offsetTag.Parent = chatTag
chatTag.ExtentsOffsetWorldSpace = chatTag.ExtentsOffsetWorldSpace + offset
game.CollectionService:AddTag(chatTag,"chatTag")
return chatTag
end
local function displayChatMessageFromChatTagPart(chatTagPart, message, speakerName)
--local chatTag = chatTagPart:FindFirstChild("SurfaceGui")
local chatTag = chatTagPart
if chatTag then
local newChatBubble = chatTag.chatTemplate:clone()
newChatBubble.titleFrame.title.Text = speakerName or ""
local titleBounds = game.TextService:GetTextSize(newChatBubble.titleFrame.title.Text, newChatBubble.titleFrame.title.TextSize, newChatBubble.titleFrame.title.Font, Vector2.new()).X + 20
newChatBubble.titleFrame.Size = UDim2.new(0,titleBounds,0,32)
newChatBubble.titleFrame.Visible = false
local existingChatBubbles = {}
for i,chatBubble in pairs(chatTag.chat:GetChildren()) do
if chatBubble:IsA("GuiObject") then
chatBubble.LayoutOrder = chatBubble.LayoutOrder - 1
table.insert(existingChatBubbles, chatBubble)
end
end
if #existingChatBubbles >= MAX_CHAT_BUBBLE_COUNT then
local oldest = getOldestChatBubble(existingChatBubbles)
oldest:Destroy()
end
newChatBubble.LayoutOrder = 10
newChatBubble.Parent = chatTag.chat
local dialogueText, yOffset, xOffset = network:invoke("createTextFragmentLabels",newChatBubble.contents, {{text = message, textColor3 = Color3.fromRGB(200,200,200)}} )
if yOffset < 18 then
newChatBubble.Size = UDim2.new(0, xOffset + 20 , 0, yOffset + 26)
else
newChatBubble.Size = UDim2.new(1, 0, 0, yOffset + 26)
end
local newOldest = getOldestChatBubble(chatTag.chat:GetChildren())
if newOldest then
setPrimaryChatBubble(newOldest)
end
newChatBubble.Visible = true
spawn(function()
wait(15)
if newChatBubble and newChatBubble.Parent then
newChatBubble:Destroy()
local newOldest = getOldestChatBubble(chatTag.chat:GetChildren())
if newOldest then
setPrimaryChatBubble(newOldest)
end
end
end)
end
end
game.ReplicatedStorage:WaitForChild("DefaultChatSystemChatEvents").OnMessageDoneFiltering.OnClientEvent:connect(function(messageInfo, rio)
-- {"ExtraData":{"Tags":[],"ChatColor":null,"NameColor":null},"IsFiltered":true,"MessageType":"Message","IsFilterResult":true,
-- "Time":1539139847,"ID":0,"FromSpeaker":"berezaa","Message":"## #### ##### #### #### ##","OriginalChannel":"All","SpeakerUserId":5000861,
-- "MessageLength":26}
local yeet = false
if yeet == true then
-- confirm
if messageInfo.IsFilterResult or runService:IsStudio() then
local player = game.Players:GetPlayerByUserId(messageInfo.SpeakerUserId)
local message = messageInfo.Message
if player and player.Character and player.Character.PrimaryPart and message then
local renderEntityData = entitiesBeingRendered[player.Character.PrimaryPart]
if not renderEntityData or not renderEntityData.entityContainer.PrimaryPart then return false end
local chatTag = renderEntityData.entityContainer:FindFirstChild("chatGui")
if chatTag then
displayChatMessageFromChatTagPart(chatTag, message, player.Name)
end
end
end
end
end)
network:create("getChatTagPartForEntity", "BindableFunction", "OnInvoke", getChatTagPartForEntity)
network:create("createChatTagPart", "BindableFunction", "OnInvoke", createChatTagPart)
network:create("displayChatMessageFromChatTagPart", "BindableFunction", "OnInvoke", displayChatMessageFromChatTagPart)
return true
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/coreRenderServices/init.lua
================================================
local Container = {} do
for _, service in pairs(script:GetChildren()) do
local success, contents = pcall(function()
local module = require(service)
if type(module) == "table" and module.init then
--module:init()
end;
return module
end)
if success then
Container[service.Name] = contents
else
warn("DEBUG:", service.Name, "has failed to load, [Error Message]: \n"..contents)
end;
end;
end;
function Container:Hook(tab, mod)
return setmetatable(tab, Container[mod])
end;
return function(key)
if not Container[key] then
local iterations = 0
repeat
wait(1)
iterations = iterations + 1
until Container[key] or iterations > 10
return Container[key]
else
return Container[key]
end;
end;
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/coreRenderServices/item_manager.lua
================================================
local item_manager = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage:WaitForChild("modules"))
local detection = modules.load("detection")
local network = modules.load("network")
local mapping = modules.load("mapping")
local itemDataLookup = require(replicatedStorage.itemData)
local itemLookup = replicatedStorage.assets.items
function item_manager.getCurrentlyEquippedForRenderCharacter(renderCharacter)
local currentlyEquipped = {}
for i, obj in pairs(renderCharacter:GetChildren()) do
if obj:IsA("BasePart") or obj:IsA("Model") then
local accessoryType, accessoryId, accessorySlot = string.match(obj.Name, "(%w+)_(%d+)_(%d+)")
if accessoryType and accessoryId then
accessoryId = tonumber(accessoryId)
accessorySlot = tonumber(accessorySlot)
if accessoryType == "EQUIPMENT" then
local equipmentBaseData = itemDataLookup[tonumber(accessoryId)]
currentlyEquipped[tostring(accessorySlot)] = {
baseData = equipmentBaseData;
manifest = obj;
}
end
end
end
end
return currentlyEquipped
end
local function isCurrentlyEquipped(currentlyEquipped, equipmentSlotData)
local equipmentBaseData = itemDataLookup[equipmentSlotData.id]
if currentlyEquipped[equipmentBaseData.equipmentSlot] then
if equipmentSlotData.id == currentlyEquipped[tostring(equipmentBaseData.equipmentSlot)].baseData.id then
return true
end
end
return false
end
function item_manager.GetWeaponStateAppendment(renderEntityData)
local weaponStateAppendment
local currentlyEquipped = item_manager.getCurrentlyEquippedForRenderCharacter(renderEntityData.entityContainer.entity)
if currentlyEquipped["1"] and currentlyEquipped["1"].baseData.equipmentType then
-- weaponState weapons should never overlap with dual wielding (BOW IN PARTICULAR)
if renderEntityData.weaponState then
weaponStateAppendment = "_" .. renderEntityData.weaponState
elseif currentlyEquipped["1"] and currentlyEquipped["11"] then
if currentlyEquipped["11"].baseData.equipmentType == "sword" then
weaponStateAppendment = "_dual"
elseif currentlyEquipped["11"].baseData.equipmentType == "shield" then
weaponStateAppendment = "AndShield"
end
end
end
return weaponStateAppendment
end
function item_manager.GetCurrentlyPlayingAnimation(animationNameToLookFor,weaponStateAppendment,characterEntityAnimationTracks,renderCharacter)
local currentlyEquipped = item_manager.getCurrentlyEquippedForRenderCharacter(renderCharacter)
local currentPlayingStateAnimation
if weaponStateAppendment then
if currentlyEquipped["1"] and currentlyEquipped["1"].baseData and currentlyEquipped["1"].baseData.equipmentType then
local fullAnimationName = animationNameToLookFor.."_"..currentlyEquipped["1"].baseData.equipmentType..weaponStateAppendment
currentPlayingStateAnimation =
characterEntityAnimationTracks.movementAnimations[fullAnimationName] or
characterEntityAnimationTracks.movementAnimations[animationNameToLookFor]
else
currentPlayingStateAnimation = characterEntityAnimationTracks.movementAnimations[animationNameToLookFor]
end
else
currentPlayingStateAnimation = characterEntityAnimationTracks.movementAnimations[animationNameToLookFor]
end
return currentPlayingStateAnimation
end
function item_manager.RefreshStateAnimation(entityManifest,renderEntityData,weaponType)
local needStateChange
if entityManifest.entityType.Value == "character" then
local currentlyEquipped = item_manager.getCurrentlyEquippedForRenderCharacter(renderEntityData.entityContainer.entity)
if currentlyEquipped["1"] then
if weaponType == currentlyEquipped["1"].baseData.equipmentType then
needStateChange = true
end
end
end
return needStateChange
end
function item_manager.EquipNewItem(appearanceData,renderCharacter,_entityManifest,entitiesBeingRendered,animationInterface,associatePlayer,client,assetFolder)
local rightGrip, leftGrip, backMount, hipMount, neckMount =
renderCharacter["RightHand"]:FindFirstChild("Grip"),
renderCharacter["LeftHand"]:FindFirstChild("Grip"),
renderCharacter["UpperTorso"]:FindFirstChild("BackMount"),
renderCharacter["LowerTorso"]:FindFirstChild("HipMount"),
renderCharacter["UpperTorso"]:FindFirstChild("BackMount")
local currentlyEquipped = item_manager.getCurrentlyEquippedForRenderCharacter(renderCharacter)
for i, equipmentData in pairs(appearanceData.equipment) do
if not isCurrentlyEquipped(currentlyEquipped, equipmentData) then
if equipmentData.position == mapping.equipmentPosition.weapon or equipmentData.position == mapping.equipmentPosition["offhand"] then
local weaponBaseData = itemDataLookup[equipmentData.id]
local weaponVisualFolder = itemLookup[weaponBaseData.module.Name]
if weaponBaseData and (weaponVisualFolder:FindFirstChild("manifest") or weaponVisualFolder:FindFirstChild("container")) then
local weaponManifest
local dye = equipmentData.dye
local weaponGripType = weaponBaseData.gripType or 1
local gripContainerOverrideCFrame = nil
-- secondary weapons always left gripped
if equipmentData.position == mapping.equipmentPosition["offhand"] then
weaponGripType = mapping.gripType.left
end
local container = weaponVisualFolder:FindFirstChild("container")
if container then
container = container:FindFirstChild("RightHand") or container:FindFirstChild("LeftHand")
container = container:Clone()
local weaponToCopy = container:FindFirstChild("manifest") or container.PrimaryPart
if weaponToCopy:IsA("BasePart") then
for i,v in pairs(container:GetChildren()) do
if v ~= weaponToCopy then
v.Parent = weaponToCopy
if v:IsA("BasePart") then
if dye then
v.Color = Color3.new(v.Color.r * dye.r/255, v.Color.g * dye.g/255, v.Color.b * dye.b/255)
end
end
end
end
if dye then
-- yes im that lazy
local v = weaponToCopy
weaponToCopy.Color = Color3.new(v.Color.r * dye.r/255, v.Color.g * dye.g/255, v.Color.b * dye.b/255)
end
weaponManifest = weaponToCopy
gripContainerOverrideCFrame = weaponToCopy.CFrame:toObjectSpace(weaponManifest.Parent.CFrame)
elseif weaponToCopy:IsA("Model") then
-- render bow
for i,v in pairs(weaponToCopy:GetDescendants()) do
if v:IsA("BasePart") then
if dye then
v.Color = Color3.new(v.Color.r * dye.r/255, v.Color.g * dye.g/255, v.Color.b * dye.b/255)
end
end
end
weaponManifest = weaponToCopy
gripContainerOverrideCFrame = weaponToCopy.PrimaryPart.CFrame:toObjectSpace(container.CFrame)
end
elseif weaponVisualFolder:FindFirstChild("manifest") then
weaponManifest = weaponVisualFolder.manifest:Clone()
if dye then
-- yes im that lazy
local v = weaponManifest
weaponManifest.Color = Color3.new(v.Color.r * dye.r/255, v.Color.g * dye.g/255, v.Color.b * dye.b/255)
end
end
weaponManifest.Name = "EQUIPMENT_" .. weaponBaseData.id .. "_" .. equipmentData.position
weaponManifest.Parent = renderCharacter
if weaponManifest:IsA("BasePart") then
weaponManifest.Anchored = false
weaponManifest.CanCollide = false
elseif weaponManifest:IsA("Model") then
for i, obj in pairs(weaponManifest:GetChildren()) do
if obj:IsA("BasePart") then
obj.Anchored = false
obj.CanCollide = false
end
end
end
if container then
container:Destroy()
container = nil
end
-- todo: very important
-- only do this for the primary weapon
local isMainHand = equipmentData.position == mapping.equipmentPosition.weapon
if _entityManifest and isMainHand then
local renderEntityData = entitiesBeingRendered[_entityManifest]
renderEntityData.currentPlayerWeapon = weaponManifest
renderEntityData.weaponBaseData = weaponBaseData
if weaponBaseData.equipmentType == "bow" then
if weaponManifest:FindFirstChild("AnimationController") then
local bowTool_Animations = animationInterface:registerAnimationsForAnimationController(weaponManifest.AnimationController, "bowToolAnimations_noChar").bowToolAnimations_noChar
renderEntityData.currentPlayerWeaponAnimations = bowTool_Animations
else
renderEntityData.currentPlayerWeaponAnimations = nil
end
else
renderEntityData.currentPlayerWeaponAnimations = nil
end
if associatePlayer == client then
network:fire("myClientCharacterWeaponChanged", weaponManifest)
end
end
-- attach weaponManifest
local isOffhand = equipmentData.position == mapping.equipmentPosition["offhand"]
local backMounted, hipMounted, neckMounted = false, false, false
if isOffhand then
local t = weaponBaseData.equipmentType
if t == "sword" or t == "shield" then
-- do nothing
elseif t == "dagger" then
hipMounted = true
elseif t == "amulet" then
neckMounted = true
else
backMounted = true
end
end
local gripCFrame = gripContainerOverrideCFrame or weaponBaseData.gripCFrame or weaponBaseData.attachmentOffset or CFrame.new()
gripCFrame = gripCFrame - gripCFrame.Position
if backMounted then
backMount.Part1 = weaponManifest:IsA("Model") and weaponManifest.PrimaryPart or weaponManifest
backMount.C0 = CFrame.new(-0.25, 0.25, 0.75) * CFrame.Angles(math.pi / 2, math.pi * 0.75, math.pi / 2)
backMount.C1 = gripCFrame
elseif hipMounted then
hipMount.Part1 = weaponManifest:IsA("Model") and weaponManifest.PrimaryPart or weaponManifest
hipMount.C0 = CFrame.new(-1, 0, 0) * CFrame.Angles(math.pi * 0.25, 0, 0)
hipMount.C1 = gripCFrame
elseif neckMounted then
neckMount.Part1 = weaponManifest:IsA("Model") and weaponManifest.PrimaryPart or weaponManifest
neckMount.C0 = CFrame.new(0, 0.75, 0)
neckMount.C1 = gripCFrame
else
local gripToAttachTo = weaponGripType == mapping.gripType.right and rightGrip or leftGrip
gripToAttachTo.Part1 = weaponManifest:IsA("Model") and weaponManifest.PrimaryPart or weaponManifest
gripToAttachTo.C0 = CFrame.new()
gripToAttachTo.C1 = gripContainerOverrideCFrame or weaponBaseData.gripCFrame or weaponBaseData.attachmentOffset or CFrame.new()
if weaponManifest:IsA("BasePart") then
if weaponBaseData.equipmentType == "dagger" or weaponBaseData.equipmentType == "sword" or weaponBaseData.equipmentType == "staff" or weaponBaseData.equipmentType == "greatsword" then
if not weaponManifest:FindFirstChild("topAttachment") then
local topAttachment = Instance.new("Attachment", gripToAttachTo.Part1)
topAttachment.Name = "topAttachment"
local part = gripToAttachTo.Part1
local size = part.Size
local points = {
Vector3.new(size.X / 2, 0, 0),
Vector3.new(0, size.Y / 2, 0),
Vector3.new(0, 0, size.Z / 2),
Vector3.new(-size.X / 2, 0, 0),
Vector3.new(0, -size.Y / 2, 0),
Vector3.new(0, 0, -size.Z / 2)
}
local gripPoint = (gripToAttachTo.C1 * gripToAttachTo.C0:Inverse()).Position
local bestPoint = nil
local bestDistance = 0
for _, point in pairs(points) do
local distance = (point - gripPoint).Magnitude
if distance > bestDistance then
bestPoint = point
bestDistance = distance
end
end
topAttachment.Position = bestPoint
end
if not weaponManifest:FindFirstChild("bottomAttachment") then
local projectionPosition = detection.projection_Box(gripToAttachTo.Part1.CFrame, gripToAttachTo.Part1.Size, gripToAttachTo.Part0.CFrame.p)
local bottomAttachment = Instance.new("Attachment", gripToAttachTo.Part1)
bottomAttachment.Name = "bottomAttachment"
bottomAttachment.Position = gripToAttachTo.Part1.CFrame:pointToObjectSpace(projectionPosition)
end
if not weaponManifest:FindFirstChild("Trail") then
local trail = assetFolder.Trail:Clone()
trail.Parent = gripToAttachTo.Part1
trail.Attachment0 = gripToAttachTo.Part1.topAttachment
trail.Attachment1 = gripToAttachTo.Part1.bottomAttachment
trail.Enabled = false
end
end
end
end
end
end
end
end
end
function item_manager.IterateThroughItems(renderCharacter,appearanceData)
local rightGrip, leftGrip, backMount, hipMount, neckMount =
renderCharacter["RightHand"]:FindFirstChild("Grip"),
renderCharacter["LeftHand"]:FindFirstChild("Grip"),
renderCharacter["UpperTorso"]:FindFirstChild("BackMount"),
renderCharacter["LowerTorso"]:FindFirstChild("HipMount"),
renderCharacter["UpperTorso"]:FindFirstChild("BackMount")
local currentlyEquipped = item_manager.getCurrentlyEquippedForRenderCharacter(renderCharacter)
for equipmentPosition, equipmentContainerData in pairs(currentlyEquipped) do
local isStillEquipped = false
for i, equipmentSlotData in pairs(appearanceData.equipment) do
if isCurrentlyEquipped(currentlyEquipped, equipmentSlotData) then
isStillEquipped = true
end
end
if not isStillEquipped then
if rightGrip.Part1 == equipmentContainerData.manifest then
rightGrip.Part1 = nil
elseif leftGrip.Part1 == equipmentContainerData.manifest then
leftGrip.Part1 = nil
elseif hipMount.Part1 == equipmentContainerData.manifest then
hipMount.Part1 = nil
elseif backMount.Part1 == equipmentContainerData.manifest then
backMount.Part1 = nil
elseif neckMount.Part1 == equipmentContainerData.manifest then
neckMount.Part1 = nil
end
currentlyEquipped[tostring(equipmentPosition)] = nil
equipmentContainerData.manifest:Destroy()
end
end
end
function item_manager.iterateThroughappearanceData(appearanceData,renderCharacter,bow_manager,hatEquipmentData,inventoryCountLookup)
for _, equipmentSlotData in pairs(appearanceData.equipment) do
if equipmentSlotData.position == mapping.equipmentPosition.upper or equipmentSlotData.position == mapping.equipmentPosition.lower or equipmentSlotData.position == mapping.equipmentPosition.head then
local dye = equipmentSlotData.dye
if equipmentSlotData.position == mapping.equipmentPosition.head then
hatEquipmentData = equipmentSlotData
end
local equipmentData = itemDataLookup[equipmentSlotData.id]
local equipmentFolder = itemLookup[equipmentData.module.Name]
if equipmentFolder:FindFirstChild("container") then
for _, accessoryPartContainer in pairs(equipmentFolder.container:GetChildren()) do
if renderCharacter:FindFirstChild(accessoryPartContainer.Name) then
if accessoryPartContainer:FindFirstChild("colorOverride") then
renderCharacter[accessoryPartContainer.Name].Color = accessoryPartContainer.Color
end
for _, accessoryPart in pairs(accessoryPartContainer:GetChildren()) do
if accessoryPart:IsA("BasePart") then
local accessory = accessoryPart:Clone()
accessory.Anchored = false
accessory.CanCollide = false
if dye then
local v = accessory
accessory.Color = Color3.new(v.Color.r * dye.r/255, v.Color.g * dye.g/255, v.Color.b * dye.b/255)
end
local projectionWeld = Instance.new("Motor6D", accessory)
projectionWeld.Name = "projectionWeld"
projectionWeld.Part0 = accessory
projectionWeld.Part1 = renderCharacter[accessoryPartContainer.Name]
projectionWeld.C0 = CFrame.new()
projectionWeld.C1 = accessoryPartContainer.CFrame:toObjectSpace(accessoryPart.CFrame)
accessory.Name = "!! EQUIPMENT !!"
accessory.Parent = renderCharacter
end
end
end
end
end
elseif equipmentSlotData.position == mapping.equipmentPosition.arrow then
local isBowEquipped = false do
for i, equip in pairs(appearanceData.equipment) do
if equip.position == mapping.equipmentPosition.weapon then
if itemDataLookup[equip.id].equipmentType == "bow" then
isBowEquipped = true
end
end
end
end
if isBowEquipped then
bow_manager.int__updateRenderCharacter(renderCharacter,inventoryCountLookup,equipmentSlotData,configuration,itemLookup)
end
end
end
end
function item_manager.createNetworkConnections(client,entitiesBeingRendered)
network:create("getCurrentlyEquippedForRenderCharacter", "BindableFunction", "OnInvoke", function(renderCharacter)
return item_manager.getCurrentlyEquippedForRenderCharacter(renderCharacter)
end)
network:create("getCurrentWeaponManifest", "BindableFunction", "OnInvoke", function(entityManifest)
entityManifest = entityManifest or (client.Character and client.Character.PrimaryPart)
if entityManifest and entitiesBeingRendered[entityManifest] then
local currentlyEquipped = item_manager.getCurrentlyEquippedForRenderCharacter(entitiesBeingRendered[entityManifest].entityContainer.entity)
return currentlyEquipped["1"] and currentlyEquipped["1"].manifest
end
end)
network:create("getRenderPlayerWeaponManifestEquipped", "BindableFunction", "OnInvoke", function(player)
local entityManifest = player.Character and player.Character.PrimaryPart
if entityManifest and entitiesBeingRendered[entityManifest] then
local currentlyEquipped = item_manager.getCurrentlyEquippedForRenderCharacter(entitiesBeingRendered[entityManifest].entityContainer.entity)
return currentlyEquipped["1"] and currentlyEquipped["1"].manifest
end
end)
network:create("hideWeapons", "BindableFunction", "OnInvoke", function(entity)
local equipment = item_manager.getCurrentlyEquippedForRenderCharacter(entity)
local partData = {}
local checks = {
equipment["1"] and equipment["1"].manifest,
equipment["11"] and equipment["11"].manifest
}
local function hidePart(part)
table.insert(partData, {part = part, transparency = part.Transparency})
part.Transparency = 1
end
for _, check in pairs(checks) do
if check:IsA("BasePart") then
hidePart(check)
end
for _, desc in pairs(check:GetDescendants()) do
if desc:IsA("BasePart") then
hidePart(desc)
end
end
end
return function()
for _, partDatum in pairs(partData) do
partDatum.part.Transparency = partDatum.transparency
end
end
end)
end
return item_manager
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/coreRenderServices/melee_manager.lua
================================================
local melee_manager = {}
function melee_manager.PlayAnimation(animationSequenceName, characterEntityAnimationTracks, animationName, animationToBePlayed, extraData)
if
animationSequenceName == "staffAnimations" or
animationSequenceName == "swordAnimations" or
animationSequenceName == "daggerAnimations" or
animationSequenceName == "greatswordAnimations" or
animationSequenceName == "dualAnimations" or
animationSequenceName == "swordAndShieldAnimations" or
animationSequenceName == "axeAnimations" or
animationSequenceName == "pickaxeAnimations"
then
local atkspd = (extraData and extraData.attackSpeed) or 0
characterEntityAnimationTracks[animationSequenceName][animationName]:Play(0.1, 1, (1 + atkspd))
else
if characterEntityAnimationTracks[animationSequenceName] then
if typeof(characterEntityAnimationTracks[animationSequenceName][animationName]) == "Instance" then
characterEntityAnimationTracks[animationSequenceName][animationName]:Play()
elseif typeof(characterEntityAnimationTracks[animationSequenceName][animationName]) == "table" then
animationToBePlayed = animationToBePlayed[1]
for _, obj in pairs(characterEntityAnimationTracks[animationSequenceName][animationName]) do
obj:Play()
end
end
end
end
end
return melee_manager
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/coreRenderServices/ragdoll_manager.lua
================================================
local ragdoll_manager = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage:WaitForChild("modules"))
local physics = modules.load("physics")
function ragdoll_manager.ragDollCharacter(entity, renderEntityData)
local ragdoll = entity:Clone()
ragdoll.Parent = entity.Parent
entity:Destroy()
local motorNames = {"Root", "Neck", "RightShoulder", "LeftShoulder", "RightElbow", "LeftElbow", "Waist", "RightWrist", "LeftWrist", "RightHip", "LeftHip", "RightKnee", "LeftKnee", "RightAnkle", "LeftAnkle"}
for _, motorName in pairs(motorNames) do
ragdoll:FindFirstChild(motorName, true):Destroy()
end
local rigAttachmentPairsByName = {}
for _, desc in pairs(ragdoll:GetDescendants()) do
if desc:IsA("Attachment") and desc.Name:find("RigAttachment") and (not desc.Name:find("Root")) then
local name = desc.Name
if not rigAttachmentPairsByName[name] then
rigAttachmentPairsByName[name] = {}
end
table.insert(rigAttachmentPairsByName[name], desc)
end
end
physics:setWholeCollisionGroup(ragdoll, "passthrough")
local constraints = Instance.new("Folder")
constraints.Name = "constraints"
constraints.Parent = ragdoll
for name, pair in pairs(rigAttachmentPairsByName) do
local constraint = Instance.new("BallSocketConstraint")
constraint.LimitsEnabled = true
constraint.TwistLimitsEnabled = true
constraint.Attachment0 = pair[1]
constraint.Attachment1 = pair[2]
constraint.Parent = constraints
pair[1].Parent.CanCollide = true
pair[2].Parent.CanCollide = true
end
local hitbox = renderEntityData.entityContainer:FindFirstChild("hitbox")
if hitbox then
local bp = Instance.new("BodyPosition")
bp.MaxForce = Vector3.new(1e6, 0, 1e6)
bp.Parent = ragdoll.LowerTorso
local connection
local function onHeartbeat()
if not bp.Parent then
connection:Disconnect()
return
end
bp.Position = hitbox.Position
end
connection = game:GetService("RunService").Heartbeat:Connect(onHeartbeat)
end
end
return ragdoll_manager
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/coreRenderServices/staff_manager.lua
================================================
local staff_manager = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage:WaitForChild("modules"))
local projectile = modules.load("projectile")
function staff_manager.PlayAnimation(animationToBePlayed,extraData,configuration)
if animationToBePlayed and not extraData.noRangeManaAttack and configuration.getConfigurationValue("doUseMageRangeAttack", game.Players.LocalPlayer) then
local magicBullet = assetFolder.mageBullet:Clone()
magicBullet.CanCollide = false
magicBullet.Parent = workspace.CurrentCamera
-- magicBullet.CFrame = entityManifest.CFrame * CFrame.new(0, 0, -1.5)
magicBullet.CFrame = CFrame.new(renderEntityData.currentPlayerWeapon.magic.WorldPosition)
local unitDirection, adjusted_targetPosition = projectile.getUnitVelocityToImpact_predictiveByAbilityExecutionData(
magicBullet.Position,
renderEntityData.weaponBaseData.projectileSpeed or 50, -- act as if you were shooting at full
extraData,
0.05
)
utilities.playSound("magicAttack", renderEntityData.currentPlayerWeapon)
projectile.createProjectile(
magicBullet.Position,
unitDirection,
renderEntityData.weaponBaseData.projectileSpeed or 40, --renderEntityData.weaponBaseData.projectileSpeed or 200,
magicBullet,
function(hitPart, hitPosition, hitNormal, hitMaterial)
tween(magicBullet, {"Transparency"},1,0.5)
for i,child in pairs(magicBullet:GetChildren()) do
if child:IsA("ParticleEmitter") or child:IsA("Light") then
child.Enabled = false
end
end
game.Debris:AddItem(magicBullet, 0.5)
-- for damien: todo: hitPart is nil
if associatePlayer == client and hitPart then
local canDamageTarget, trueTarget = damage.canPlayerDamageTarget(game.Players.LocalPlayer, hitPart)
if canDamageTarget and trueTarget then
-- (player, entityManifest, damagePosition, sourceType, sourceId, guid)
network:fire("requestEntityDamageDealt", trueTarget, hitPosition, "equipment", nil, "magic-ball")
end
end
end,
nil,
-- ignore list
{entityManifest; renderEntityData.entityContainer},
-- points to next position
true,
0.01,
0.8
)
if renderEntityData.currentPlayerWeapon and renderEntityData.currentPlayerWeapon:FindFirstChild("magic") and renderEntityData.currentPlayerWeapon.magic:FindFirstChild("castEffect") then
renderEntityData.currentPlayerWeapon.magic.castEffect:Emit(1)
end
end
end
return staff_manager
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/effectsClient.client.lua
================================================
local debris = game:GetService("Debris")
local modules = require(game:GetService("ReplicatedStorage"):WaitForChild("modules"))
local network = modules.load("network")
local effects = modules.load("effects")
local placeSetup = modules.load("placeSetup")
local tween = modules.load("tween")
local utilities = modules.load("utilities")
local projectile = modules.load("projectile")
local entitiesFolder = placeSetup.awaitPlaceFolder("entities")
local function createEffectPart()
local part = Instance.new("Part")
part.Anchored = true
part.CanCollide = false
part.TopSurface = Enum.SurfaceType.Smooth
part.BottomSurface = Enum.SurfaceType.Smooth
part.CastShadow = false
return part
end
local function lerp(a, b, w)
return a + (b - a) * w
end
local effectFunctions
effectFunctions = {
acidSplash = function(args)
local part = script.acidSplash:Clone()
part.Position = args.position
part.Parent = entitiesFolder
tween(part, {"Size", "Transparency"}, {Vector3.new(2, 2, 2) * args.radius, 1}, args.duration)
delay(args.duration, function()
part.emitter.Enabled = false
debris:AddItem(part, part.emitter.Lifetime.Max)
end)
end,
bloodHeal = function(args)
local playerManifest = args.playerManifest
local target = args.target
if not playerManifest then return end
if not target then return end
local renderContainer = network:invoke("getPlayerRenderFromManifest", playerManifest)
if not renderContainer then return end
local entity = renderContainer:FindFirstChild("entity")
if not entity then return end
local root = renderContainer.PrimaryPart
if not root then return end
local upperTorso = entity:FindFirstChild("UpperTorso")
if not upperTorso then return end
local function createEffectPart()
local part = Instance.new("Part")
part.Anchored = true
part.CanCollide = false
part.TopSurface = Enum.SurfaceType.Smooth
part.BottomSurface = Enum.SurfaceType.Smooth
return part
end
local function bloodEffect(partA, partB, spin)
local duration = 1
local bezierStretch = 16
local blood = script.blood:Clone()
local trail = blood.trail
blood.CFrame = partA.CFrame
blood.Parent = workspace.CurrentCamera
local offsetB = CFrame.Angles(0, 0, spin) * CFrame.new(0, bezierStretch, 0)
local offsetC = CFrame.Angles(0, 0, -spin) * CFrame.new(0, bezierStretch, 0)
effects.onHeartbeatFor(duration, function(dt, t, w)
local startToFinish = CFrame.new(partA.Position, partB.Position)
local finishToStart = CFrame.new(partB.Position, partA.Position)
local a = startToFinish.Position
local b = (startToFinish * offsetB).Position
local c = (finishToStart * offsetC).Position
local d = finishToStart.Position
local ab = a + (b - a) * w
local cd = c + (d - c) * w
local p = ab + (cd - ab) * w
blood.CFrame = CFrame.new(p)
end)
delay(duration, function()
blood.Transparency = 1
trail.Enabled = false
game:GetService("Debris"):AddItem(blood, trail.Lifetime)
end)
end
local function lifeStealEffect(target)
for blood = 1, 3 do
local spin = (blood - 2) * (math.pi / 3)
bloodEffect(target, upperTorso, spin)
end
delay(1, function()
-- restore sound
local restoreSound = script.restore:Clone()
restoreSound.Parent = root
restoreSound:Play()
debris:AddItem(restoreSound, restoreSound.TimeLength)
-- blood poof
local duration = 1
local sphere = createEffectPart()
sphere.Shape = Enum.PartType.Ball
sphere.Color = script.blood.Color
sphere.Material = Enum.Material.Neon
sphere.Size = Vector3.new()
sphere.CFrame = CFrame.new(upperTorso.Position)
sphere.Parent = entitiesFolder
tween(sphere, {"Size", "Transparency"}, {Vector3.new(8, 8, 8), 1}, duration)
effects.onHeartbeatFor(duration, function()
sphere.CFrame = CFrame.new(upperTorso.Position)
end)
debris:AddItem(sphere, duration)
end)
end
lifeStealEffect(target)
end,
lightning = function(args)
local startCFrame = args.startCFrame
local pointCount = args.pointCount or 14
local segmentDeltaY = args.segmentDeltaY or 4
local maxStutter = args.maxStutter or 4
local duration = args.duration or 0.5
local color = args.color or BrickColor.new("Electric blue").Color
local function lightningSegment(a, b)
local duration = 0.5
local part = createEffectPart()
part.Color = color
part.Material = Enum.Material.Neon
local distance = (b - a).magnitude
local midpoint = (a + b) / 2
part.Size = Vector3.new(0.25, 0.25, distance)
part.CFrame = CFrame.new(midpoint, b)
part.Parent = entitiesFolder
tween(
part,
{"Transparency"},
1,
duration
)
debris:AddItem(part, duration)
end
-- create a column of points above the spot
-- and move them randomly around a circle
-- to create a jagged line like lightning
-- don't bother storing the points, we don't
-- need to do that, we only need the previous
local cframe = startCFrame
-- start at 2 because the target point is a point, too
for pointNumber = 2, pointCount do
local theta = math.pi * 2 * math.random()
local dx = math.cos(theta) * maxStutter * math.random()
local dy = (pointNumber - 1) * segmentDeltaY
local dz = math.sin(theta) * maxStutter * math.random()
local nextCFrame = cframe * CFrame.new(dx, dy, dz)
lightningSegment(cframe.Position, nextCFrame.Position)
cframe = nextCFrame
end
end,
orbArrival = function(args)
effectFunctions.orbAnnouncement()
local rand = Random.new(1231996)
local orb = args.orb
local position = orb.PrimaryPart.Position
local orbParent = orb.Parent
orb.Parent = nil
local effect = script.orbArrival:Clone()
effect.Position = position
effect.Parent = entitiesFolder
effect.loop:Play()
-- repeated, accelerating lightning strikes
local maxPause = 1
local minPause = 0.05
local strikeCount = 32
for strikeNumber = 1, strikeCount do
for _ = 1, rand:NextInteger(1, 2) do
local cframe = CFrame.new(position) * CFrame.Angles(0, math.pi * 2 * rand:NextNumber(), 0) * CFrame.Angles(math.pi * 0.125 * rand:NextNumber(), 0, 0)
effectFunctions.lightning{startCFrame = cframe}
end
effect["strike"..rand:NextInteger(1, 6)]:Play()
local pause = lerp(maxPause, minPause, (strikeNumber - 1) / strikeCount)
wait((pause * 0.5) + (pause * 0.5 * rand:NextNumber()))
end
-- expand the circle in an explosion
local fadeDuration = 4
effect.emitter.Enabled = false
effect.BrickColor = BrickColor.new("Electric blue")
effect.explosion:Play()
wait(1)
tween(effect, {"Size"}, {Vector3.new(1, 1, 1) * 128}, fadeDuration / 4, Enum.EasingStyle.Quint, Enum.EasingDirection.Out)
tween(effect, {"Transparency"}, {1}, fadeDuration, Enum.EasingStyle.Linear)
effect.loop:Stop()
debris:AddItem(effect, fadeDuration)
-- make burning cracks on the ground
local raycastIgnoreList = projectile.makeIgnoreList{
placeSetup.awaitPlaceFolder("entityManifestCollection"),
placeSetup.awaitPlaceFolder("entityRenderCollection")
}
local function crack(cframe, stepLength, stepCount, sign)
if sign == nil then sign = 1 end
local here = cframe.Position
local wiggle = math.pi * 0.4
local castDelta = Vector3.new(0, 6, 0)
for stepNumber = 2, stepCount do
local there = here + cframe.LookVector * stepLength
local ray = Ray.new(there + castDelta, -castDelta * 2)
local part, point = workspace:FindPartOnRayWithIgnoreList(ray, raycastIgnoreList)
there = point
local segment = createEffectPart()
segment.Material = Enum.Material.Neon
segment.BrickColor = BrickColor.new("Electric blue")
segment.CFrame = CFrame.new((here + there) / 2, there)
segment.Size = Vector3.new(0.2, 0.2, (there - here).Magnitude)
local emitter = script.orbEmitter:Clone()
emitter.Parent = segment
segment.Parent = entitiesFolder
spawn(function()
for i=5,0,-1 do
emitter.Rate = math.random(0, i*2)
wait(2)
end
local segmentFadeDuration = 5
tween(segment, {"Transparency"}, {1}, segmentFadeDuration)
debris:AddItem(segment, segmentFadeDuration)
end)
if (stepNumber + 1) % 2 == 0 then
crack(cframe, stepLength, stepCount - stepNumber, sign)
end
here = there
sign = -sign
cframe = cframe * CFrame.Angles(0, wiggle * rand:NextNumber() * sign, 0)
cframe = cframe + (here - cframe.Position)
end
end
local crackStartPosition do
local ray = Ray.new(position, Vector3.new(0, -8, 0))
local part, point = workspace:FindPartOnRayWithIgnoreList(ray, raycastIgnoreList)
crackStartPosition = point
end
local startTheta = math.pi * 2 * rand:NextNumber()
local crackCount = 7
local deltaTheta = math.pi * 2 / crackCount
for crackNumber = 1, crackCount do
local theta = startTheta + deltaTheta * crackNumber
crack(CFrame.new(crackStartPosition) * CFrame.Angles(0, theta, 0), 2, 8)
end
orb.Parent = orbParent
orb.PrimaryPart.loop:Play()
end,
orbAnnouncement = function()
--[[
local adjectives = {
"a mystic",
"an electrifying",
"an arcane",
"a powerful",
"an evocative",
"an ancient",
"an overwhelming",
"a great",
}
local adjective = adjectives[math.random(1, #adjectives)]
]]
game.StarterGui:SetCore("ChatMakeSystemMessage", {
Text = "You feel a mystic presence echo through the night..."--[[..adjective.." presence echo through the night..."]],
Color = BrickColor.new("Electric blue").Color,
Font = Enum.Font.SourceSansBold,
})
end,
orbDeparture = function(args)
local orb = args.orb
local cframe = orb:GetPrimaryPartCFrame()
local dy = 0
local vy = 0
local ay = 0
local aya = 16
effects.onHeartbeatFor(5, function(dt, t, w)
ay = ay + aya * dt
vy = vy + ay * dt
dy = dy + vy * dt
orb:SetPrimaryPartCFrame(cframe + Vector3.new(0, dy, 0))
end)
end,
}
network:connect("effects_requestEffect", "OnClientEvent", function(effectName, args)
effectFunctions[effectName](args)
end)
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/entityRenderer.lua
================================================
-- Master script that handles entity rendering
local module = {}
local client = game.Players.LocalPlayer
local repo = script.Parent
-- render services
local coreRenderServices = require(repo:WaitForChild("coreRenderServices"))
local bow_manager = coreRenderServices("bow_manager")
local staff_manager = coreRenderServices("staff_manager")
local melee_manager = coreRenderServices("melee_manager")
local appearance_manager = coreRenderServices("appearance_manager")
local ragdoll_manager = coreRenderServices("ragdoll_manager")
local item_manager = coreRenderServices("item_manager")
-- services
local assetFolder = repo.Parent:WaitForChild("assets")
local RunService = game:GetService("RunService")
local HttpService = game:GetService("HttpService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- modules
local network
local tween
local utilities
local physics
local placeSetup
local projectile
local configuration
local events
-- assets
local ReplicatedStorageAssetFolder = ReplicatedStorage:WaitForChild("assets")
local defaultCharacterAppearance = require(ReplicatedStorage:WaitForChild("defaultCharacterAppearance"))
local defaultMonsterStateStates = require(ReplicatedStorage.defaultMonsterState).states
local entityManifestCollectionFolder
local entityRenderCollectionFolder
local animationInterface
local accessoryLookup = ReplicatedStorage.assets.accessories
local itemLookup = require(ReplicatedStorage.itemData)
local monsterLookup = require(ReplicatedStorage.monsterLookup)
local abilityLookup = require(ReplicatedStorage.abilityLookup)
local statusEffectLookup = require(ReplicatedStorage.statusEffectLookup)
local entitiesBeingRendered = {}
-- builds a table of whats currently equipped on a renderCharacter,
-- id rather do this than store what every renderCharacter is wearing and
-- keep track of it.
local function getInventoryCountLookupTableByItemId()
local lookupTable = {}
local inventoryLastGot = network:invoke("getCacheValueByNameTag", "inventory")
if inventoryLastGot then
for i, inventorySlotData in pairs(inventoryLastGot) do
if lookupTable[inventorySlotData.id] then
lookupTable[inventorySlotData.id] = lookupTable[inventorySlotData.id] + (inventorySlotData.stacks or 1)
else
lookupTable[inventorySlotData.id] = inventorySlotData.stacks or 1
end
end
end
return lookupTable
end
-- handles updating appearance of renderCharacter (SPECIFICALLY!!)
-- renderEntityContainer.entity is renderCharacter (renderEntityContainer is also shortened as entityContainer)
-- ^^ ONLY IF entityType == "character" ^^
local function int__updateRenderCharacter(renderCharacter, appearanceData, _entityManifest)
local associatePlayer do
if _entityManifest then
associatePlayer = game.Players:GetPlayerFromCharacter(_entityManifest.Parent)
end
end
appearanceData = appearanceData or defaultCharacterAppearance
appearanceData.equipment = appearanceData.equipment or defaultCharacterAppearance.equipment
appearanceData.accessories = appearanceData.accessories or defaultCharacterAppearance.accessories
-- wipe all previous additions
local accessories = {
["!! ACCESSORY !!"] = true,
["!! EQUIPMENT-UPPER !!"] = true,
["!! EQUIPMENT !!"] = true,
["!! WEAPON !!"] = true,
["!! ARROW !!"] = true,
}
for i, obj in pairs(renderCharacter:GetChildren()) do
if accessories[obj.Name] then
obj:Destroy()
end
end
appearance_manager.ApplySkinColor(appearanceData, renderCharacter, accessoryLookup, ReplicatedStorageAssetFolder)
local hatEquipmentData
local inventoryCountLookup = getInventoryCountLookupTableByItemId()
if appearanceData and appearanceData.equipment then
item_manager.iterateThroughappearanceData(appearanceData,renderCharacter,bow_manager,hatEquipmentData,inventoryCountLookup)
end
--[[
playerStoreForCurrentlyEquipped[equipmentData.position] = {
baseData = weaponBaseData;
manifest = weaponManifest;
equipmentData = equipmentData;
}
--]]
-- iterate through equipped on character and actual
item_manager.IterateThroughItems(renderCharacter,appearanceData)
-- equipping new stuff
item_manager.EquipNewItem(appearanceData,renderCharacter,_entityManifest,entitiesBeingRendered,animationInterface,associatePlayer,client,assetFolder)
if appearanceData and appearanceData.accessories then
appearance_manager.LoadAppearence(ReplicatedStorageAssetFolder.accessories,appearanceData,hatEquipmentData,itemLookup,renderCharacter)
end
if appearanceData and appearanceData.temporaryEquipment then
for temporaryEquipmentName, _ in pairs(appearanceData.temporaryEquipment) do
if ReplicatedStorage:FindFirstChild("temporaryEquipment") and ReplicatedStorage.temporaryEquipment:FindFirstChild(temporaryEquipmentName) then
local applicationFunction = require(ReplicatedStorage.temporaryEquipment[temporaryEquipmentName].application)
applicationFunction(renderCharacter)
end
end
end
if renderCharacter then
for i, obj in pairs(renderCharacter:GetDescendants()) do
if obj:IsA("BasePart") then
obj.CanCollide = false
end
end
end
end
local function int__assembleRenderCharacter(manifest)
local entityContainer = Instance.new("Model")
local clientPlayerHitbox = manifest:Clone()
clientPlayerHitbox.BrickColor = BrickColor.new("Hot pink")
clientPlayerHitbox.CanCollide = false
clientPlayerHitbox.Anchored = true
clientPlayerHitbox.Name = "hitbox"
local clientHitboxToServerHitboxReference = Instance.new("ObjectValue")
clientHitboxToServerHitboxReference.Name = "clientHitboxToServerHitboxReference"
clientHitboxToServerHitboxReference.Value = manifest
clientHitboxToServerHitboxReference.Parent = entityContainer
-- clear all unnecessary parts within the hitbox
-- we only want the part itself
clientPlayerHitbox:ClearAllChildren()
entityContainer.PrimaryPart = clientPlayerHitbox
clientPlayerHitbox.Parent = entityContainer
local characterBaseModel = ReplicatedStorage.playerBaseCharacter:Clone()
characterBaseModel.Name = "entity"
characterBaseModel.Parent = entityContainer
local projectionWeld = Instance.new("Motor6D")
projectionWeld.Name = "projectionWeld"
projectionWeld.Part0 = clientPlayerHitbox
projectionWeld.Part1 = characterBaseModel.PrimaryPart
projectionWeld.C0 = CFrame.new()
projectionWeld.C1 = CFrame.new(0, characterBaseModel:GetModelCFrame().Y - characterBaseModel.PrimaryPart.CFrame.Y, 0)
projectionWeld.Parent = clientPlayerHitbox
return entityContainer
end
local function dissassembleRenderEntityByManifest(entityManifest)
if entitiesBeingRendered[entityManifest] then
for i, connection in pairs(entitiesBeingRendered[entityManifest].connections) do
connection:disconnect()
end
if entitiesBeingRendered[entityManifest].entityContainer and entitiesBeingRendered[entityManifest].entityContainer:FindFirstChild("entity") and entitiesBeingRendered[entityManifest].entityContainer.entity:FindFirstChild("AnimationController") then
animationInterface:deregisterAnimationsForAnimationController(entitiesBeingRendered[entityManifest].entityContainer.entity.AnimationController)
else
-- passing nil will flush all registerations whos index no longer exists
-- just incase something wack happened..
animationInterface:deregisterAnimationsForAnimationController(nil)
end
entitiesBeingRendered[entityManifest].entityContainer:Destroy()
entitiesBeingRendered[entityManifest] = nil
end
end
local playerXpTagPairing = {}
local function int__connectEntityEvents(entityManifest, renderEntityData)
local associatePlayer = game.Players:GetPlayerFromCharacter(entityManifest.Parent)
-- we're freezing whether or not its a character
-- because we can't garauntee entityContainer will be the same.. i think thats why at least
-- we'll test...
-- local isEntityCharacter = entityManifest.entityType.Value == "character"
local previousKeyframeReached_event
local currentPlayingStateAnimation
local characterEntityAnimationTracks
local entityStatesData
local entityBaseData
local previousState = ""
local isWalkingSoundPlaying = false
local isIdleSoundPlaying = false
local isRunningSoundPlaying = false
local monsterAnimations = {}
local function populateMonsterAnimationsTable()
if entityManifest.entityType.Value == "monster" or entityManifest.entityType.Value == "pet" then
monsterAnimations = {}
-- please dont ask me why im putting it here
physics:setWholeCollisionGroup(renderEntityData.entityContainer.entity, "monstersLocal")
for i, animation in pairs(renderEntityData.entityContainer.entity.animations:GetChildren()) do
local animationTrack = renderEntityData.entityContainer.entity.AnimationController:LoadAnimation(animation)
local animPriority = "Idle"
if animation.Name == "attacking" or animation.Name == "death" then
animPriority = "Action"
elseif animation.Name == "dashing" or animation.Name == "damaged" then
animPriority = "Movement"
elseif animation.Name == "walking" or animation.Name == "idling" then
animPriority = "Core"
end
animationTrack.Priority = Enum.AnimationPriority[animPriority] or Enum.AnimationPriority.Idle
monsterAnimations[animation.Name] = animationTrack
end
end
end
local function populateEntityData()
if entityManifest.entityType.Value == "character" then
characterEntityAnimationTracks = animationInterface:registerAnimationsForAnimationController(renderEntityData.entityContainer.entity.AnimationController, "movementAnimations", "swordAndShieldAnimations", "dualAnimations", "greatswordAnimations", "swordAnimations", "daggerAnimations", "staffAnimations", "fishing-rodAnimations", "emoteAnimations", "bowAnimations", "axeAnimations","pickaxeAnimations")
elseif entityManifest.entityType.Value == "monster" or entityManifest.entityType.Value == "pet" then
entityBaseData = (entityManifest.entityType.Value == "monster") and monsterLookup[entityManifest.entityId.Value] or itemLookup[tonumber(entityManifest.entityId.Value)]
entityStatesData = utilities.copyTable(entityBaseData.statesData.states)
setmetatable(entityStatesData, {
__index = function(_, index)
return defaultMonsterStateStates[index]
end;
})
end
end
local function onCharacterAnimationTrackKeyframeReached(keyframeName)
if keyframeName == "footstep" then
-- do not play footstep sounds on stealthed characters
if entityManifest:FindFirstChild("isStealthed") then return end
local footStep = ""
local ignore = {workspace.CurrentCamera}
if workspace:FindFirstChild("placeFolders") then
table.insert(ignore, workspace.placeFolders)
end
local below = Ray.new(entityManifest.Position, Vector3.new(0,-5,0))
local belowPart, belowPosition, belowNormal, belowMaterial = workspace:FindPartOnRayWithIgnoreList(below, ignore, false, false)
if belowPart ~= nil and (belowPart:IsA("BasePart") or belowPart:IsA("Terrain")) then
if belowMaterial == Enum.Material.Grass or belowMaterial == Enum.Material.LeafyGrass then
footStep = "grass"
elseif belowMaterial == Enum.Material.Mud or belowMaterial == Enum.Material.Ground then
footStep = "dirt"
elseif belowMaterial == Enum.Material.Sand or belowMaterial == Enum.Material.Sandstone then
footStep = "sand"
elseif belowMaterial == Enum.Material.Snow or belowMaterial == Enum.Material.Ice then
footStep = "snow"
else
footStep = "stone"
end
end
local footStepSound
local possibleSounds = {}
for i = 1, 3 do
local sound = game.ReplicatedStorage.assets.sounds:FindFirstChild("footstep_"..footStep..(i>1 and tostring(i) or ""))
if sound then
table.insert(possibleSounds, sound)
end
end
if #possibleSounds > 0 then
footStepSound = possibleSounds[math.random(1,#possibleSounds)]
end
if footStepSound and renderEntityData.entityContainer.PrimaryPart then
local newSound = utilities.playSound(footStepSound.Name)
newSound.Parent = renderEntityData.entityContainer.PrimaryPart
newSound.Looped = false
newSound.Pitch = math.random(95,105) / 100
-- make ur own volume louder
if associatePlayer == client then
newSound.Volume = newSound.Volume * 1.5
newSound.EmitterSize = newSound.EmitterSize * 3
newSound.MaxDistance = newSound.MaxDistance * 3
end
newSound:Play()
game.Debris:AddItem(newSound,1.5)
end
end
end
local function onEntityStateChanged(newState)
if entityManifest.entityType.Value == "character" then
animationInterface:stopPlayingAnimationsByAnimationCollectionNameWithException(characterEntityAnimationTracks, "emoteAnimations", "consume_consumable")
if currentPlayingStateAnimation then
if typeof(currentPlayingStateAnimation) == "Instance" then
if currentPlayingStateAnimation.Looped or newState == "jumping" then
currentPlayingStateAnimation:Stop()
end
elseif typeof(currentPlayingStateAnimation) == "table" then
for ii, obj in pairs(currentPlayingStateAnimation) do
if obj.Looped or newState == "jumping" then
obj:Stop()
end
end
end
currentPlayingStateAnimation = nil
end
elseif entityManifest.entityType.Value == "monster" or entityManifest.entityType.Value == "pet" then
if renderEntityData.entityContainer:FindFirstChild("entity") and renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("walking") then
if newState == "walking" or (entityStatesData[newState] and (entityStatesData[newState].animationEquivalent == "walking")) or newState == "movement" or (entityStatesData[newState] and (entityStatesData[newState].animationEquivalent == "movement")) then
if not isWalkingSoundPlaying then
renderEntityData.entityContainer.entity.PrimaryPart.walking.Looped = true
renderEntityData.entityContainer.entity.PrimaryPart.walking:Play()
isWalkingSoundPlaying = true
end
elseif isWalkingSoundPlaying then
renderEntityData.entityContainer.entity.PrimaryPart.walking:Stop()
isWalkingSoundPlaying = false
end
end
if renderEntityData.entityContainer:FindFirstChild("entity") and renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("running") then
if newState == "running" or (entityStatesData[newState] and (entityStatesData[newState].animationEquivalent == "running")) then
if not isRunningSoundPlaying then
renderEntityData.entityContainer.entity.PrimaryPart.running.Looped = true
renderEntityData.entityContainer.entity.PrimaryPart.running:Play()
isRunningSoundPlaying = true
end
elseif isRunningSoundPlaying then
renderEntityData.entityContainer.entity.PrimaryPart.running:Stop()
isRunningSoundPlaying = false
end
end
if renderEntityData.entityContainer:FindFirstChild("entity") and renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("idling") then
if not isRunningSoundPlaying and not isWalkingSoundPlaying then
if not isIdleSoundPlaying then
renderEntityData.entityContainer.entity.PrimaryPart.idling.Looped = true
renderEntityData.entityContainer.entity.PrimaryPart.idling:Play()
isIdleSoundPlaying = true
end
elseif isIdleSoundPlaying then
renderEntityData.entityContainer.entity.PrimaryPart.idling:Stop()
isIdleSoundPlaying = false
end
end
if currentPlayingStateAnimation then
if entityStatesData[previousState] and not entityStatesData[previousState].doNotStopAnimation then
currentPlayingStateAnimation:Stop()
end
currentPlayingStateAnimation = nil
end
end
if newState == "dead" then
-- stop all animations
local function deathEffect(multi)
multi = multi or 1
local target = entityManifest.CFrame
if renderEntityData.entityContainer and renderEntityData.entityContainer:FindFirstChild("entity") then
if renderEntityData.entityContainer.entity:FindFirstChild("Torso") then
target = renderEntityData.entityContainer.entity.Torso.CFrame
elseif renderEntityData.entityContainer.entity:FindFirstChild("UpperTorso") then
target = renderEntityData.entityContainer.entity.UpperTorso.CFrame
end
end
local deathPart = Instance.new("Part")
deathPart.Size = entityManifest.Size * multi
deathPart.CFrame = target
deathPart.Transparency = 1
deathPart.CanCollide = false
deathPart.Anchored = true
local deathSound = Instance.new("Sound")
deathSound.SoundId = "rbxassetid://2199444861"
deathSound.Name = "deathSound"
deathSound.MaxDistance = 35
deathSound.Parent = deathPart
deathSound.Volume = 0.2
deathSound.PlaybackSpeed = 0.8 + math.random()/5
if entityManifest:FindFirstChild("monsterScale") and entityManifest.monsterScale.Value > 1.3 then
deathSound.Volume = deathSound.Volume * entityManifest.monsterScale.Value
deathSound.MaxDistance = deathSound.MaxDistance * (entityManifest.monsterScale.Value ^ 3)
deathSound.PlaybackSpeed = deathSound.PlaybackSpeed * (1 - entityManifest.monsterScale.Value/8)
end
deathSound:Play()
local effect = assetFolder.Death:Clone()
effect.Parent = deathPart
deathPart.Parent = workspace.CurrentCamera
local size = deathPart.Size
effect:Emit(3 * math.sqrt(size.X * size.Y * size.Z ))
game.Debris:AddItem(deathPart,3)
end
for i, animationTrack in pairs(renderEntityData.entityContainer.entity.AnimationController:GetPlayingAnimationTracks()) do
animationTrack:Stop()
end
if entityManifest.entityType.Value ~= "character" then
entityManifest.CanCollide = false
end
-- make the render also not collidable
for i, obj in pairs(renderEntityData.entityContainer.entity:GetDescendants()) do
if obj:IsA("BasePart") then
obj.CanCollide = false
end
end
if entityManifest.entityType.Value == "monster" or entityManifest.entityType.Value == "pet" then
local deathConnection
deathConnection = monsterAnimations.death.Stopped:connect(function()
deathConnection:disconnect()
deathEffect()
-- bye-bye
dissassembleRenderEntityByManifest(entityManifest)
end)
monsterAnimations.death.Looped = false
monsterAnimations.death:Play()
if renderEntityData.entityContainer.entity.PrimaryPart and renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("death") then
local hitsounds = {renderEntityData.entityContainer.entity.PrimaryPart.death}
if renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("death2") then
table.insert(hitsounds, renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("death2"))
end
if renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("death3") then
table.insert(hitsounds, renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("death3"))
end
if renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("death4") then
table.insert(hitsounds, renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("death4"))
end
local rand = math.random(#hitsounds)
local hitsound = hitsounds[rand]
if entityManifest:FindFirstChild("monsterScale") and entityManifest.monsterScale.Value > 1.3 and hitsound:FindFirstChild("scalePitch") == nil then
local scale = entityManifest.monsterScale.Value
hitsound.Volume = hitsound.Volume * scale
hitsound.EmitterSize = hitsound.EmitterSize * (scale ^ 2)
hitsound.MaxDistance = hitsound.MaxDistance * (scale ^ 3)
hitsound.PlaybackSpeed = 1 - ((scale-1) * 0.2)
end
local deathPart = Instance.new("Part")
deathPart.Anchored = true
deathPart.CanCollide = false
deathPart.Parent = workspace.CurrentCamera
deathPart.Size = Vector3.new(0.1,0.1,0.1)
deathPart.Transparency = 1
deathPart.CFrame = entityManifest.CFrame
hitsound.Parent = deathPart
hitsound:Play()
game.Debris:AddItem(deathPart,hitsound.TimeLength + 0.1)
end
local stateData = entityStatesData[newState]
spawn(function()
if stateData and stateData.execute and renderEntityData.entityContainer and renderEntityData.entityContainer:FindFirstChild("entity") then
stateData.execute(client, monsterAnimations.death, entityBaseData, renderEntityData.entityContainer)
end
end)
elseif entityManifest.entityType.Value == "character" then
local entity = renderEntityData.entityContainer and renderEntityData.entityContainer:FindFirstChild("entity")
if entity then
ragdoll_manager.ragDollCharacter(renderEntityData.entityContainer,renderEntityData)
end
end
elseif newState == "gettingUp" and entityManifest.entityType.Value == "character" then
local animation = characterEntityAnimationTracks.movementAnimations[newState]
if animation then
local connection
local function onAnimationStopped()
if connection then
connection:disconnect()
connection = nil
end
if associatePlayer == client then
-- client is the one gettingUp, so after the animation finishes we want to stop
-- the state
network:invoke("setCharacterArrested", false)
network:invoke("setCharacterMovementState", "isGettingUp", false)
end
end
connection = animation.Stopped:connect(onAnimationStopped)
animation.Looped = false
animation:Play()
if associatePlayer == client then
network:invoke("setCharacterArrested", true)
end
end
else
if entityManifest.entityType.Value == "monster" or entityManifest.entityType.Value == "pet" then
-- todo: can we remove monsterAnimations[newState] ?
if monsterAnimations[newState] or (entityStatesData[newState] and entityStatesData[newState].animationEquivalent and monsterAnimations[entityStatesData[newState].animationEquivalent]) then
local targetAnimation = monsterAnimations[newState] or monsterAnimations[entityStatesData[newState].animationEquivalent]
-- added support for animation variance for a single state
if monsterAnimations[newState.."2"] then
local chance = math.random(2)
if chance == 2 then
targetAnimation = monsterAnimations[newState.."2"]
end
end
local stateData = entityStatesData[newState]-- or (entityStatesData[newState].animationEquivalent and entityStatesData[entityStatesData[newState].animationEquivalent])
if targetAnimation then
currentPlayingStateAnimation = targetAnimation
currentPlayingStateAnimation.Priority = (entityStatesData[newState] and entityStatesData[newState].animationPriority) or Enum.AnimationPriority.Idle
currentPlayingStateAnimation.Looped = (entityStatesData[newState] and entityStatesData[newState].doNotLoopAnimation ~= true) or false
currentPlayingStateAnimation:Play()
else
targetAnimation = nil
end
-- yikesssss
if stateData.additional_animation_to_play_temp then
monsterAnimations[stateData.additional_animation_to_play_temp]:Play()
end
if stateData and stateData.execute then
-- client damaging thingy!
spawn(function()
stateData.execute(client, targetAnimation, entityBaseData, renderEntityData.entityContainer)
end)
end
end
elseif entityManifest.entityType.Value == "character" then
local weaponStateAppendment = item_manager.GetWeaponStateAppendment(renderEntityData)
local animationNameToLookFor = newState do
if entityManifest.entityId.Value ~= "" then
-- classifying this as a monster masking as a player,
-- use their states animation
if monsterLookup[entityManifest.entityId.Value] then
if monsterLookup[entityManifest.entityId.Value].statesData.states[newState].animationEquivalent then
animationNameToLookFor = monsterLookup[entityManifest.entityId.Value].statesData.states[newState].animationEquivalent
elseif defaultMonsterStateStates[newState].animationEquivalent then
animationNameToLookFor = defaultMonsterStateStates[newState].animationEquivalent
end
end
end
end
if weaponStateAppendment then
if associatePlayer and associatePlayer:FindFirstChild("class") and characterEntityAnimationTracks.movementAnimations[string.lower(associatePlayer.class.Value) .. "_" .. animationNameToLookFor .. weaponStateAppendment] then
animationNameToLookFor = string.lower(associatePlayer.class.Value) .. "_" .. animationNameToLookFor .. weaponStateAppendment
end
end
local currentlyEquipped = item_manager.getCurrentlyEquippedForRenderCharacter(renderEntityData.entityContainer.entity)
if characterEntityAnimationTracks.movementAnimations[animationNameToLookFor] or (currentlyEquipped["1"] and currentlyEquipped["1"].baseData.equipmentType and characterEntityAnimationTracks.movementAnimations[animationNameToLookFor .. "_" .. currentlyEquipped["1"].baseData.equipmentType .. weaponStateAppendment]) then
currentPlayingStateAnimation = item_manager.GetCurrentlyPlayingAnimation(animationNameToLookFor,weaponStateAppendment,characterEntityAnimationTracks,renderEntityData.entityContainer.entity)
if currentPlayingStateAnimation then
if previousKeyframeReached_event then
previousKeyframeReached_event:disconnect()
local footstep_sound = renderEntityData.entityContainer:FindFirstChild("footstep_sound", true)
if footstep_sound and footstep_sound.Playing then
footstep_sound.Looped = false
end
end
-- stop all emotes
animationInterface:stopPlayingAnimationsByAnimationCollectionName(characterEntityAnimationTracks, "emoteAnimations")
-- probably fix this.. i really hate that animations are two layered
if typeof(currentPlayingStateAnimation) == "Instance" then
previousKeyframeReached_event = currentPlayingStateAnimation.KeyframeReached:connect(onCharacterAnimationTrackKeyframeReached)
-- ber edit mess with weights here
if animationNameToLookFor == "walking" then
currentPlayingStateAnimation:Play(nil, (currentPlayingStateAnimation.Priority == Enum.AnimationPriority.Movement and 0.85) or 1)
else
currentPlayingStateAnimation:Play(nil, 1)
end
if animationNameToLookFor == "jumping" then
currentPlayingStateAnimation:AdjustSpeed(1.5)
end
elseif typeof(currentPlayingStateAnimation) == "table" then
previousKeyframeReached_event = currentPlayingStateAnimation[1].KeyframeReached:connect(onCharacterAnimationTrackKeyframeReached)
for ii, obj in pairs(currentPlayingStateAnimation) do
obj:Play()
if animationNameToLookFor == "jumping" then
obj:AdjustSpeed(1.5)
end
end
end
end
end
previousState = animationNameToLookFor
-- end it early
return
end
end
previousState = newState
end
function renderEntityData:playAnimation(animationSequenceName, animationName, extraData)
if characterEntityAnimationTracks[animationSequenceName] and characterEntityAnimationTracks[animationSequenceName][animationName] then
-- stop all emotes
animationInterface:stopPlayingAnimationsByAnimationCollectionName(characterEntityAnimationTracks, "emoteAnimations")
local associatePlayer = game.Players:GetPlayerFromCharacter(entityManifest.Parent)
if animationName == "consume_consumable" and extraData and extraData.id then
local itemBaseData = itemLookup[extraData.id]
local consumableManifest = itemBaseData.module:FindFirstChild("manifest")
if consumableManifest and renderEntityData.entityContainer and renderEntityData.entityContainer:FindFirstChild("entity") then
local consumableGrip = renderEntityData.entityContainer.entity:FindFirstChild("ConsumableGrip", true)
if consumableGrip then
consumableManifest = consumableManifest:Clone()
consumableManifest.CanCollide = false
consumableManifest.Anchored = false
for i, Child in pairs(consumableManifest:GetChildren()) do
if Child:IsA("BasePart") then
local motor6d = Instance.new("Motor6D")
motor6d.Part0 = consumableManifest
motor6d.Part1 = Child
motor6d.C0 = CFrame.new()
motor6d.C1 = Child.CFrame:toObjectSpace(consumableManifest.CFrame)
motor6d.Parent = Child
Child.CanCollide = false
Child.Anchored = false
end
end
consumableManifest.Parent = renderEntityData.entityContainer.entity
consumableGrip.Part1 = consumableManifest
local currentEquippedManifest = network:invoke("getCurrentWeaponManifest", entityManifest) --currentlyEquipped[1] and currentlyEquipped[1].manifest
if currentEquippedManifest then
if currentEquippedManifest:IsA("BasePart") then
currentEquippedManifest.Transparency = 1
end
for i,part in pairs(currentEquippedManifest:GetDescendants()) do
if part:isA("BasePart") then
part.Transparency = part.Transparency + 1
end
end
end
characterEntityAnimationTracks[animationSequenceName]["consume_loop"]:Play()
local connection
connection = characterEntityAnimationTracks[animationSequenceName]["consume_loop"].Stopped:connect(function()
end)
if itemBaseData.useSound and game.ReplicatedStorage:FindFirstChild("sounds") and consumableManifest then
local soundMirror = game.ReplicatedStorage.assets.sounds:FindFirstChild(itemBaseData.useSound)
if soundMirror then
local sound = Instance.new("Sound")
for property, value in pairs(game.HttpService:JSONDecode(soundMirror.Value)) do
sound[property] = value
end
sound.Parent = consumableManifest
sound.Volume = 0.8
sound.MaxDistance = 150
sound:Play()
end
end
delay(extraData.ANIMATION_DESIRED_LENGTH or 2, function()
if consumableManifest then
if consumableManifest:FindFirstChild("consumed") then
consumableManifest.consumed.Transparency = 1
elseif itemBaseData.useSound == "eat_food" then
consumableManifest.Transparency = 1
for i,part in pairs(consumableManifest:GetChildren()) do
if part:IsA("BasePart") then
part.Transparency = 1
end
end
end
end
delay(0.4, function()
if consumableGrip.Part1 == consumableManifest then
consumableGrip.Part1 = nil
end
if currentEquippedManifest then
if currentEquippedManifest:IsA("BasePart") then
currentEquippedManifest.Transparency = 0
end
for i,part in pairs(currentEquippedManifest:GetDescendants()) do
if part:isA("BasePart") then
part.Transparency = part.Transparency - 1
end
end
end
if consumableManifest then
consumableManifest:Destroy()
consumableManifest = nil
end
connection:disconnect()
end)
characterEntityAnimationTracks[animationSequenceName]["consume_loop"]:Stop()
end)
end
end
elseif animationName == "cast-line" and extraData and extraData.targetPosition then
local currentWeaponManifest = network:invoke("getCurrentWeaponManifest", entityManifest)
if not currentWeaponManifest or not currentWeaponManifest:FindFirstChild("line") then return end
characterEntityAnimationTracks[animationSequenceName][animationName]:Play()
wait(0.75)
if not currentWeaponManifest or not currentWeaponManifest:FindFirstChild("line") then return end
local startPosition = (currentWeaponManifest.CFrame * CFrame.new(0, currentWeaponManifest.Size.Y / 2, 0)).p
local unitDirection = ((Vector3.new(extraData.targetPosition.X, extraData.targetPosition.Y, extraData.targetPosition.Z) - startPosition).unit + Vector3.new(0, 0.08, 0)).unit--((Vector3.new(extraData.targetPosition.X, startPosition.Y, extraData.targetPosition.Z) - startPosition).unit + Vector3.new(0, 0.05, 0)).unit
if renderEntityData.fishingBob then
renderEntityData.fishingBob:Destroy()
renderEntityData.fishingBob = nil
end
renderEntityData.fishingBob = game.ReplicatedStorage.fishingBob:Clone()
renderEntityData.fishingBob.Parent = placeSetup.getPlaceFolder("entities")
currentWeaponManifest.line.Attachment1 = renderEntityData.fishingBob.Attachment
local castSound = utilities.playSound("fishingPoleCast_Short", currentWeaponManifest)
local t = 2*( math.abs(startPosition.Y - extraData.targetPosition.Y) ) /60
local dist = (extraData.targetPosition - startPosition).magnitude
local vel = math.clamp(dist/t, 1, 70)
projectile.createProjectile(
startPosition,
unitDirection,
vel, --2xheight / workspace.gravity
renderEntityData.fishingBob,
function(hitPart, hitPosition, hitNormal, hitMaterial)
if hitPart and game.CollectionService:HasTag(hitPart, "fishingSpot") then --if hitPart and hitPart == workspace.Terrain and hitMaterial == Enum.Material.Water then
if associatePlayer == client then
network:fire("fishingBobHit", true)
end
if renderEntityData.fishingBob and renderEntityData.fishingBob:FindFirstChild("splash") then
renderEntityData.fishingBob.splash:Emit(20)
end
if castSound then
castSound:Stop()
end
utilities.playSound("fishing_BaitSplash", renderEntityData.fishingBob)
network:invokeServer("playerRequest_startFishing", hitPosition)
else
if associatePlayer == client then
network:fire("fishingBobHit", false)
end
if renderEntityData.fishingBob then
game:GetService("Debris"):AddItem(renderEntityData.fishingBob, 1 / 30)
end
renderEntityData.fishingBob = nil
end
end,
function(t)
if currentWeaponManifest:FindFirstChild("line") then
currentWeaponManifest.line.Length = 25 * t
end
end
)
elseif animationName == "reel-line" then
local currentWeaponManifest = network:invoke("getCurrentWeaponManifest")
if not currentWeaponManifest or not currentWeaponManifest:FindFirstChild("line") then return end
characterEntityAnimationTracks[animationSequenceName][animationName]:Play()
if associatePlayer == client then
wait(0.75)
network:invoke("setCharacterMovementState", "isFishing", false)
network:invoke("setCharacterArrested", false)
local fish, fishVelocity
if renderEntityData.fishingBob then
local _, fishModel, fishVelocityGiven = network:invokeServer("playerRequest_reelFishingRod", renderEntityData.fishingBob.Position)
fish = fishModel
fishVelocity = fishVelocityGiven
end
if currentWeaponManifest and not fish then
currentWeaponManifest.line.Attachment1 = nil
elseif currentWeaponManifest and fish then
fish.Velocity = fishVelocity
end
end
if renderEntityData.fishingBob then
renderEntityData.fishingBob:Destroy()
renderEntityData.fishingBob = nil
end
elseif extraData and extraData.dance then
-- dance emotes
local currentEquippedManifest
if animationName ~= "point" then
currentEquippedManifest = network:invoke("getCurrentWeaponManifest", entityManifest) --currentlyEquipped[1] and currentlyEquipped[1].manifest
if currentEquippedManifest then
if currentEquippedManifest:IsA("BasePart") then
currentEquippedManifest.Transparency = 1
end
for i,part in pairs(currentEquippedManifest:GetDescendants()) do
if part:isA("BasePart") then
part.Transparency = part.Transparency + 1
end
end
end
end
local prop
local extraEmoteInfo = {
["handstand"] = {fadeTime = .3;};
["sit"] = {fadeTime = .3;};
["panic"] = {fadeTime = .3;};
["pushups"] = {fadeTime = .3;};
["point"] = {singleAction = true;};
["flex"] = {singleAction = true;};
["guitar"] = {singleAction = true;};
["tadaa"] = {singleAction = true;};
["cheer"] = {singleAction = true;};
}
if extraEmoteInfo[animationName] and extraEmoteInfo[animationName].fadeTime then
characterEntityAnimationTracks[animationSequenceName][animationName]:Play(extraEmoteInfo[animationName].fadeTime)
else
characterEntityAnimationTracks[animationSequenceName][animationName]:Play()
end
--to-do: potentially do a unified system for animations that hold
if animationName == "beg" then
prop = assetFolder.Plate:Clone()
local weld = Instance.new("WeldConstraint")
weld.Parent = prop
prop.CFrame = renderEntityData.entityContainer.entity.RightHand.CFrame *CFrame.Angles(math.pi,0,0) * CFrame.new(-.5,.16,-.2)
weld.Part1 = prop
weld.Part0 = renderEntityData.entityContainer.entity.RightHand
prop.Parent = workspace
delay(3.1, function()
--characterEntityAnimationTracks[animationSequenceName]["beg_hold"]:Play()
characterEntityAnimationTracks[animationSequenceName][animationName]:AdjustSpeed(0)
end)
end
local connection
connection = characterEntityAnimationTracks[animationSequenceName][animationName].Stopped:connect(function()
if prop then
prop:Destroy()
end
if currentEquippedManifest then
if currentEquippedManifest:IsA("BasePart") then
currentEquippedManifest.Transparency = 0
end
for i,part in pairs(currentEquippedManifest:GetDescendants()) do
if part:isA("BasePart") then
part.Transparency = part.Transparency - 1
end
end
end
-- kind of a messy solution but this is needed for emotes to properly work with the control script
if extraEmoteInfo[animationName] and extraEmoteInfo[animationName].singleAction then
control.endEmote()
end
connection:disconnect()
end)
else
local animationToBePlayed = characterEntityAnimationTracks[animationSequenceName][animationName]
if animationSequenceName == "bowAnimations" then
bow_manager.PlayAnimation(animationSequenceName)
elseif animationSequenceName == "staffAnimations" then
staff_manager.PlayAnimation(animationToBePlayed,extraData,configuration)
end
if animationToBePlayed then
melee_manager.PlayAnimation(animationSequenceName, characterEntityAnimationTracks, animationName, animationToBePlayed, extraData)
end
end
end
end
local function onEntityTypeChanged(newEntityType)
dissassembleRenderEntityByManifest(entityManifest)
-- ugly hack since the building function requires this function
-- EPIC CIRCULAR REQUIREMENTS!
network:invoke("assembleEntityByManifest", entityManifest)
end
local function onEntityIdChanged()
print("NateDebuggingSomeRandomFunctionWeHaveThatFiresWhenEntityIdChangesButDosentHaveAnythingInIt")
end
local monsterNameUI
local monsterNameTag
local function setupMonsterDisplayUI()
--local monsterNameUIPart = renderEntityData.entityContainer:FindFirstChild("MonsterHealthTag") or assetFolder.MonsterHealthTag:Clone()
--monsterNameUIPart.Parent = renderEntityData.entityContainer
--monsterNameUI = monsterNameUIPart.SurfaceGui
monsterNameUI = assetFolder.monsterHealth:Clone()
monsterNameUI.Parent = renderEntityData.entityContainer
monsterNameUI.Adornee = renderEntityData.entityContainer
monsterNameUI.Enabled = false
--monsterNameUIPart.Parent = renderEntityData.entityContainer
local monsterNamePart = renderEntityData.entityContainer:FindFirstChild("MonsterEnemyTag") or assetFolder.MonsterEnemyTag:Clone()
monsterNamePart.Parent = workspace.CurrentCamera
monsterNameTag = monsterNamePart.SurfaceGui
monsterNameTag.Enabled = false
local monsterScaled = false
local function monsterScale()
if not monsterScaled then
if entityManifest.monsterScale.Value > 1.3 then
monsterScaled = true
monsterNameTag.skull.Visible= true
end
end
end
if entityManifest:FindFirstChild("monsterScale") then
monsterScale()
else
local scaleConnection
scaleConnection = entityManifest.ChildAdded:connect(function(child)
if child.Name == "monsterScale" then
if scaleConnection then
scaleConnection:disconnect()
scaleConnection = nil
end
monsterScale()
end
end)
table.insert(renderEntityData.connections, scaleConnection)
end
monsterNamePart.Parent = renderEntityData.entityContainer
local level = entityManifest:FindFirstChild("level") and entityManifest.level.Value or 1
local levelText = "Lvl "..tostring(level)
monsterNameTag.level.Text = levelText
if renderEntityData.disableLevelUI then
monsterNameTag.level.Visible = false
end
local levelBounds = game.TextService:GetTextSize(levelText, monsterNameTag.level.TextSize, monsterNameTag.level.Font, Vector2.new()).X + 6
monsterNameTag.level.Size = UDim2.new(0, levelBounds, 1, -6)
local isMonsterPet = not not entityManifest:FindFirstChild("pet")
local monsterText = entityManifest.entityId:FindFirstChild("nickname") and entityManifest.entityId.nickname.Value or entityManifest.entityId.Value
local isNicknamed = entityManifest.entityId:FindFirstChild("nickname") ~= nil
if isMonsterPet and not entityManifest.entityId:FindFirstChild("nickname") then
monsterText = itemLookup[tonumber(entityManifest.entityId.Value)].name
end
if entityManifest:FindFirstChild("specialName") then
monsterText = entityManifest.specialName.Value
end
if entityManifest:FindFirstChild("monsterScale") and (not entityManifest:FindFirstChild("notGiant")) then
if entityManifest.monsterScale.Value > 4.5 then
monsterText = "Colossal " .. monsterText
elseif entityManifest.monsterScale.Value > 2.5 then
monsterText = "Super Giant " .. monsterText
elseif entityManifest.monsterScale.Value > 1.3 then
monsterText = "Giant " .. monsterText
end
end
if not isMonsterPet then
monsterNameTag.monster.AutoLocalize = true
monsterNameTag.monster.Text = monsterText
monsterNameTag.monster.Size = UDim2.new(0, game.TextService:GetTextSize(monsterText, monsterNameTag.monster.TextSize, monsterNameTag.monster.Font, Vector2.new()).X, 1, 0)
else
monsterNameTag.level.Visible = false
monsterNameTag.monster.Visible = false
monsterNameTag.nickname.Visible = true
monsterNameTag.nickname.AutoLocalize = not isNicknamed
monsterNameTag.nickname.Text = monsterText
monsterNameTag.nickname.Size = UDim2.new(0, game.TextService:GetTextSize(monsterText, monsterNameTag.nickname.TextSize, monsterNameTag.nickname.Font, Vector2.new()).X + 10, 1, -4)
end
end
local function cleanupMonsterDisplayUI()
local monsterNamePart = renderEntityData.entityContainer:FindFirstChild("MonsterEnemyTag")
if monsterNamePart then
monsterNamePart:Destroy()
end
end
local function setupCharacterDisplayUI()
local nameTag = renderEntityData.entityContainer.PrimaryPart:FindFirstChild("PlayerTag") or assetFolder.PlayerTag:Clone()
nameTag.Parent = renderEntityData.entityContainer.PrimaryPart
if associatePlayer then
local xpTag = assetFolder.xpTag:Clone()
xpTag.Parent = renderEntityData.entityContainer.PrimaryPart
xpTag.Enabled = true
playerXpTagPairing[associatePlayer] = xpTag
end
monsterNameUI = assetFolder.monsterHealth:Clone()
monsterNameUI.Parent = renderEntityData.entityContainer
monsterNameUI.Adornee = renderEntityData.entityContainer
monsterNameUI.Enabled = false
if associatePlayer and nameTag then
local function updateNameTagForCharacter()
local level = associatePlayer:FindFirstChild("level") and associatePlayer.level.Value or 0
local levelText = "Lvl." .. level
nameTag.SurfaceGui.top.level.Text = levelText
local class = associatePlayer:FindFirstChild("class") and associatePlayer.class.Value or "unknown"
if class:lower() ~= "adventurer" then
nameTag.SurfaceGui.top.class.Image = "rbxgameasset://Images/emblem_"..class:lower()
nameTag.SurfaceGui.top.class.Visible = true
else
nameTag.SurfaceGui.top.class.Visible = false
end
nameTag.SurfaceGui.bottom.guild.Visible = false
if associatePlayer:FindFirstChild("guildId") and associatePlayer.guildId.Value ~= "" then
local guildDataFolder = game.ReplicatedStorage:FindFirstChild("guildDataFolder")
if guildDataFolder then
spawn(function()
local guildDataValue = guildDataFolder:WaitForChild(associatePlayer.guildId.Value, 10)
if guildDataValue then
local guildData = HttpService:JSONDecode(guildDataValue.Value)
if guildData.name then
nameTag.SurfaceGui.bottom.guild.Text = guildData.name
local nameBounds = game.TextService:GetTextSize(guildData.name, nameTag.SurfaceGui.bottom.guild.TextSize, nameTag.SurfaceGui.bottom.guild.Font, Vector2.new()).X + 10
nameTag.SurfaceGui.bottom.guild.Size = UDim2.new(0, nameBounds, 1, -4)
nameTag.SurfaceGui.bottom.guild.Visible = true
end
end
end)
end
end
nameTag.SurfaceGui.top.input.Visible = false
for i,v in pairs(nameTag.SurfaceGui.top.input:GetChildren()) do
if v:IsA("GuiObject") then
v.Visible = false
end
end
if associatePlayer:FindFirstChild("input") then
local inputFrame = nameTag.SurfaceGui.top.input:FindFirstChild(associatePlayer.input.Value)
if inputFrame then
inputFrame.Visible = true
nameTag.SurfaceGui.top.input.Visible = true
end
end
nameTag.SurfaceGui.top.dev.Visible = associatePlayer:FindFirstChild("developer") ~= nil
nameTag.SurfaceGui.top.player.Text = associatePlayer.Name
local levelBounds = game.TextService:GetTextSize(levelText, nameTag.SurfaceGui.top.level.TextSize, nameTag.SurfaceGui.top.level.Font, Vector2.new()).X + 8
nameTag.SurfaceGui.top.level.Size = UDim2.new(0, levelBounds, 1, -4)
local nameBounds = game.TextService:GetTextSize(associatePlayer.Name, nameTag.SurfaceGui.top.player.TextSize, nameTag.SurfaceGui.top.player.Font, Vector2.new()).X + 10
nameTag.SurfaceGui.top.player.Size = UDim2.new(0, nameBounds, 1, -4)
local totalXBound = 0
for i,child in pairs(nameTag.SurfaceGui.top:GetChildren()) do
if child:IsA("GuiObject") and child.Visible then
totalXBound = totalXBound + child.AbsoluteSize.X
end
end
nameTag.SurfaceGui.curve.Size = UDim2.new(0, totalXBound + 10, 0.5, -4)
end
-- update once
updateNameTagForCharacter()
if associatePlayer:FindFirstChild("guildId") then
table.insert(renderEntityData.connections, associatePlayer.guildId.Changed:connect(updateNameTagForCharacter))
end
if associatePlayer:FindFirstChild("level") then
table.insert(renderEntityData.connections, associatePlayer.level.Changed:connect(updateNameTagForCharacter))
end
if associatePlayer:FindFirstChild("class") then
table.insert(renderEntityData.connections, associatePlayer.class.Changed:connect(updateNameTagForCharacter))
end
if associatePlayer:FindFirstChild("input") then
table.insert(renderEntityData.connections, associatePlayer.input.Changed:connect(updateNameTagForCharacter))
end
end
end
local function cleanupCharacterDisplayUI()
local nameTag = renderEntityData.entityContainer.PrimaryPart:FindFirstChild("PlayerTag")
if nameTag then
nameTag:Destroy()
end
local chatTag = renderEntityData.entityContainer:FindFirstChild("chatGui")
if chatTag then
chatTag:Destroy()
end
end
local currentHealth = entityManifest.health.Value
local isShowingDamageAnimation = false
local function onEntityHealthChanged(newHealth)
if not monsterNameUI then
if renderEntityData.entityContainer.PrimaryPart:FindFirstChild("monsterNameUI") then
monsterNameUI = renderEntityData.entityContainer.PrimaryPart.monsterNameUI
end
end
if not monsterNameTag then
if renderEntityData.entityContainer.PrimaryPart then
if renderEntityData.entityContainer.PrimaryPart:FindFirstChild("monsterNameTag") then
monsterNameTag = renderEntityData.entityContainer.PrimaryPart.monsterNameTag
end
end
end
if monsterNameTag and renderEntityData.entityContainer.Name ~= "Chicken" then
monsterNameTag.Enabled = true
end
-- should we show the health?
if monsterNameUI then
local healthUI = monsterNameUI
if entityBaseData and entityBaseData.boss then
monsterNameUI.Enabled = false
healthUI = network:invoke("prepareBossHealthUIForMonster", entityBaseData) or healthUI
if healthUI and newHealth <= 0 then
healthUI.Visible = false
end
else
if entityManifest.maxHealth.Value > newHealth then
if associatePlayer then
monsterNameUI.Enabled = true
else
monsterNameUI.Enabled = true
end
else
monsterNameUI.Enabled = false
end
end
local fill = healthUI.container.backgroundFill
local goal = UDim2.new(math.clamp(newHealth / entityManifest.maxHealth.Value, 0, 1), 0, 1, 0)
if fill.healthLag.Size.X.Scale < fill.currentHealthFill.Size.X.Scale then
fill.healthLag.Size = fill.currentHealthFill.Size
end
fill.healthLag.ImageColor3 = Color3.fromRGB(255, 23, 23)
fill.currentHealthFill.Size = goal
spawn(function()
wait(0.5)
if fill and fill:FindFirstChild("healthLag") and fill.currentHealthFill.Size == goal then
--if fill and fill:FindFirstChild("healthLag") and fill.healthLag.ImageColor3 == Color3.fromRGB(255, 43, 43) then
tween(fill.healthLag, {"Size","ImageColor3"}, {goal, Color3.fromRGB(255, 210, 38)}, 0.3)
end
end)
end
-- check if was damaged
if newHealth < currentHealth then
if not isShowingDamageAnimation then
isShowingDamageAnimation = true
local head = renderEntityData.entityContainer.entity:FindFirstChild("head") or renderEntityData.entityContainer.entity:FindFirstChild("body") or renderEntityData.entityContainer.PrimaryPart
if head:FindFirstChild("damageTaken") then
head.damageTaken:Play()
end
if monsterAnimations.damaged then
monsterAnimations.damaged.Looped = false
monsterAnimations.damaged:Play()
end
local hitsound
if renderEntityData.entityContainer.entity.PrimaryPart and renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("hit") then
local hitsounds = {renderEntityData.entityContainer.entity.PrimaryPart.hit}
if renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("hit2") then
table.insert(hitsounds, renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("hit2"))
end
if renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("hit3") then
table.insert(hitsounds, renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("hit3"))
end
if renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("hit4") then
table.insert(hitsounds, renderEntityData.entityContainer.entity.PrimaryPart:FindFirstChild("hit4"))
end
hitsound = hitsounds[math.random(1,#hitsounds)]
end
if hitsound then
if entityManifest:FindFirstChild("monsterScale") and entityManifest.monsterScale.Value > 1.3 and hitsound:FindFirstChild("scalePitch") == nil then
local scale = entityManifest.monsterScale.Value
hitsound.EmitterSize = hitsound.EmitterSize * scale
hitsound.MaxDistance = hitsound.MaxDistance * (scale ^ 2)
local scalePitch = Instance.new("PitchShiftSoundEffect")
scalePitch.Name = "scalePitch"
scalePitch.Octave = 0.9
scalePitch.Parent = hitsound
end
hitsound:Play()
end
spawn(function()
wait(0.5)
if isShowingDamageAnimation then
isShowingDamageAnimation = false
end
end)
end
end
currentHealth = newHealth
end
function renderEntityData:setWeaponState(weaponType, weaponState)
renderEntityData.weaponState = weaponState
-- force a refresh of the state animations
local needStateChange = item_manager.RefreshStateAnimation()
if needStateChange then
onEntityStateChanged(entityManifest.state.Value)
end
end
-- todo: refactor this to fit current statusEffect schema (does anything even use this?)
function renderEntityData:changeStatusEffectState(sourcePlayer, sourceType, sourceId, isEnabled)
if sourceType == "ability" and renderEntityData.entityContainer then
local playerData
if sourcePlayer == game.Players.LocalPlayer then
playerData = network:invoke("getLocalPlayerDataCache")
end
local value = entityManifest.activeAbilityExecutionData.Value
local success, abilityExecutionData = utilities.safeJSONDecode(value)
local abilityBaseData = abilityLookup[sourceId](playerData, abilityExecutionData)
if abilityBaseData then
if isEnabled and abilityBaseData.onStatusEffectBegan then
abilityBaseData:onStatusEffectBegan(renderEntityData.entityContainer)
elseif not isEnabled and abilityBaseData.onStatusEffectEnded then
abilityBaseData:onStatusEffectEnded(renderEntityData.entityContainer)
end
end
end
end
local previousStatusEffects = nil
local function onPlayerStatusEffectsChanged(currentStatusEffectsJSON)
local success, currentStatusEffects = utilities.safeJSONDecode(currentStatusEffectsJSON)
if success then
if previousStatusEffects then
local diff = {}
for i,v in pairs(currentStatusEffects) do
diff[v.id] = 1
end
for i,v in pairs(previousStatusEffects) do
diff[v.id] = (diff[v.id] or 0) - 1
end
for id, state in pairs(diff) do
-- 1 = added, 0 = same, -1 = removed
local statusEffectData = statusEffectLookup[id]
if statusEffectData then
if state == 1 and statusEffectData.__clientApplyTransitionEffectOnCharacter then
statusEffectData.__clientApplyTransitionEffectOnCharacter(renderEntityData.entityContainer)
elseif state == 1 and statusEffectData.__clientApplyStatusEffectOnCharacter then
statusEffectData.__clientApplyStatusEffectOnCharacter(renderEntityData.entityContainer)
elseif state == 0 then
-- do nuffin
elseif state == -1 and statusEffectData.__clientRemoveStatusEffectOnCharacter then
statusEffectData.__clientRemoveStatusEffectOnCharacter(renderEntityData.entityContainer)
end
end
end
previousStatusEffects = currentStatusEffects
else
for i, activeStatusEffectData in pairs(currentStatusEffects) do
local statusEffectData = statusEffectLookup[activeStatusEffectData.id]
if statusEffectData and statusEffectData.__clientApplyStatusEffectOnCharacter then
statusEffectData.__clientApplyStatusEffectOnCharacter(renderEntityData.entityContainer)
end
end
previousStatusEffects = currentStatusEffects
end
end
end
local previousCastingAbilityId = 0
local function onPlayerCastingAbilityIdChanged(castingAbilityId)
-- todo: make this not necessary
if associatePlayer == client then return end
local value = entityManifest.activeAbilityExecutionData.Value
local success, abilityExecutionData = utilities.safeJSONDecode(value)
if previousCastingAbilityId > 0 then
local previousCastingAbilityBaseData = abilityLookup[previousCastingAbilityId](nil, abilityExecutionData)
if previousCastingAbilityBaseData and previousCastingAbilityBaseData.onCastingEnded__client then
previousCastingAbilityBaseData:onCastingEnded__client(renderEntityData.entityContainer)
end
end
if castingAbilityId > 0 then
local castingAbilityBaseData = abilityLookup[castingAbilityId](nil, abilityExecutionData)
if castingAbilityBaseData and castingAbilityBaseData.onCastingBegan__client then
castingAbilityBaseData:onCastingBegan__client(renderEntityData.entityContainer)
end
end
previousCastingAbilityId = castingAbilityId
end
local previousAbilityExecutionData = {id = 0}
local function onActiveAbilityExecutionData_changed(value)
local success, abilityExecutionData = utilities.safeJSONDecode(value)
if success then
if previousAbilityExecutionData["ability-guid"] == abilityExecutionData["ability-guid"] and previousAbilityExecutionData["ability-state"] == "end" then
return false
end
local playerData
if associatePlayer == client then
playerData = network:invoke("getLocalPlayerDataCache")
end
if abilityExecutionData.id == 0 then
local abilityBaseData = abilityLookup[previousAbilityExecutionData.id](playerData, previousAbilityExecutionData)
if abilityBaseData and abilityBaseData.cleanup then
abilityBaseData:cleanup(renderEntityData.entityContainer)
end
previousAbilityExecutionData["ability-state"] = "end"
if abilityBaseData and abilityBaseData.abilityDecidesEnd and abilityBaseData.execute then
abilityBaseData:execute(renderEntityData.entityContainer, previousAbilityExecutionData, associatePlayer == client, associatePlayer == client and previousAbilityExecutionData["ability-guid"])
end
if associatePlayer == client then
network:fire("setIsPlayerCastingAbility", false)
end
else--if (associatePlayer ~= client or abilityExecutionData["ability-state"] ~= "begin") then
-- safeguard to prevent the client from double-firing
if abilityExecutionData["ability-guid"] == previousAbilityExecutionData["ability-guid"] and previousAbilityExecutionData.step and abilityExecutionData.step <= previousAbilityExecutionData.step then
return false
end
-- do this at the start since :execute yields
previousAbilityExecutionData = abilityExecutionData
local abilityBaseData = abilityLookup[abilityExecutionData.id](playerData, abilityExecutionData)
if abilityBaseData and abilityBaseData.execute then
abilityBaseData:execute(renderEntityData.entityContainer, abilityExecutionData, associatePlayer == client, associatePlayer == client and abilityExecutionData["ability-guid"])
if associatePlayer == client and not abilityBaseData.abilityDecidesEnd then
wait()
network:invoke("client_changeAbilityState", abilityExecutionData.id, "end", abilityExecutionData, abilityExecutionData.guid)
end
end
end
end
end
local function onCharacterAppearanceChanged(appearanceJSON)
local success, appearanceData = utilities.safeJSONDecode(appearanceJSON)
if success then
int__updateRenderCharacter(renderEntityData.entityContainer.entity, appearanceData, entityManifest)
end
end
local previousStatusEffectsV2
local function onEntityStatusEffectChanged(newStatusEffectsV2)
local success, currentStatusEffectsV2 = utilities.safeJSONDecode(newStatusEffectsV2)
if success then
if previousStatusEffectsV2 then
local diff = {}
for i,v in pairs(currentStatusEffectsV2) do
diff[v.statusEffectType] = 1
end
for i,v in pairs(previousStatusEffectsV2) do
diff[v.statusEffectType] = (diff[v.statusEffectType] or 0) - 1
end
for id, state in pairs(diff) do
-- 1 = added, 0 = same, -1 = removed
local statusEffectData = statusEffectLookup[id]
if statusEffectData then
if state == 1 and statusEffectData.__clientApplyTransitionEffectOnCharacter then
statusEffectData.__clientApplyTransitionEffectOnCharacter(renderEntityData.entityContainer)
elseif state == 1 and statusEffectData.__clientApplyStatusEffectOnCharacter then
statusEffectData.__clientApplyStatusEffectOnCharacter(renderEntityData.entityContainer)
elseif state == -1 and statusEffectData.__clientRemoveStatusEffectOnCharacter then
statusEffectData.__clientRemoveStatusEffectOnCharacter(renderEntityData.entityContainer)
end
end
end
previousStatusEffectsV2 = currentStatusEffectsV2
else
for i, activeStatusEffectData in pairs(currentStatusEffectsV2) do
local statusEffectData = statusEffectLookup[activeStatusEffectData.statusEffectType]
if statusEffectData then
if statusEffectData.__clientApplyStatusEffectOnCharacter then
statusEffectData.__clientApplyStatusEffectOnCharacter(renderEntityData.entityContainer)
end
end
end
previousStatusEffectsV2 = currentStatusEffectsV2
end
end
end
if not renderEntityData.entityContainer.entity:FindFirstChild("AnimationController") then
local animationController = Instance.new("AnimationController")
animationController.Parent = renderEntityData.entityContainer.entity
end
-- ALL CODE EXECUTION MUST GO AFTER THIS! --
populateEntityData()
if entityManifest.entityType.Value == "monster" or entityManifest.entityType.Value == "pet" then
if entityManifest.entityType.Value == "pet" then
-- make pets walkthroughable
for i, obj in pairs(renderEntityData.entityContainer.entity:GetDescendants()) do
if obj:IsA("BasePart") then
obj.CanCollide = false
end
end
end
populateMonsterAnimationsTable()
cleanupCharacterDisplayUI()
setupMonsterDisplayUI()
elseif entityManifest.entityType.Value == "character" then
onPlayerStatusEffectsChanged(entityManifest.statusEffects.Value)
onPlayerCastingAbilityIdChanged(entityManifest.castingAbilityId.Value)
cleanupMonsterDisplayUI()
setupCharacterDisplayUI()
table.insert(renderEntityData.connections, entityManifest.statusEffects.Changed:connect(onPlayerStatusEffectsChanged))
table.insert(renderEntityData.connections, entityManifest.activeAbilityExecutionData.Changed:connect(onActiveAbilityExecutionData_changed))
table.insert(renderEntityData.connections, entityManifest.castingAbilityId.Changed:connect(onPlayerCastingAbilityIdChanged))
table.insert(renderEntityData.connections, entityManifest.appearance.Changed:connect(onCharacterAppearanceChanged))
end
onEntityStateChanged(entityManifest.state.Value)
-- todo: fix
if entityManifest:FindFirstChild("statusEffectsV2") then
onEntityStatusEffectChanged(entityManifest.statusEffectsV2.Value)
table.insert(renderEntityData.connections, entityManifest.statusEffectsV2.Changed:connect(onEntityStatusEffectChanged))
end
table.insert(renderEntityData.connections, entityManifest.state.Changed:connect(onEntityStateChanged))
table.insert(renderEntityData.connections, entityManifest.entityType.Changed:connect(onEntityTypeChanged))
table.insert(renderEntityData.connections, entityManifest.entityId.Changed:connect(onEntityIdChanged))
table.insert(renderEntityData.connections, entityManifest.health.Changed:connect(onEntityHealthChanged))
if associatePlayer == client then
network:fire("myClientCharacterContainerChanged", renderEntityData.entityContainer)
end
end
local function playersXpGained(playerXpRewards)
for playerName,xpgained in pairs(playerXpRewards) do
local player = game.Players:FindFirstChild(playerName)
if player then
local xpTag = playerXpTagPairing[player]
if xpTag and xpTag.Parent then
spawn(function()
local timestamp = xpTag:FindFirstChild("timestamp")
if timestamp == nil then
timestamp = Instance.new("NumberValue")
timestamp.Name = "timestamp"
timestamp.Parent = xpTag
end
local difference = timestamp.Value - tick()
if difference <= 0 then
timestamp.Value = tick() + 0.2
else
timestamp.Value = timestamp.Value + 0.2
wait(difference)
end
local tag = xpTag.Frame.template:Clone()
tag.Text = "+"..tostring(math.floor(xpgained)).." XP"
tag.Parent = xpTag.Frame
tag.TextTransparency = 1
tag.TextStrokeTransparency = 1
tag.Visible = true
tween(tag, {"Position"},UDim2.new(0.5,0,0,0),2, Enum.EasingStyle.Linear)
tween(tag, {"TextTransparency", "TextStrokeTransparency"}, {0, 0.5}, 0.3)
delay(0.5,function()
if tag and tag.Parent then
tween(tag, {"TextTransparency", "TextStrokeTransparency"}, {1, 1}, 1.5)
end
end)
game.Debris:AddItem(tag, 2)
end)
end
end
end
end
local function int__updateMonsterNameTag(renderData)
local entityContainer = renderData.entityContainer
local nameTagPart = entityContainer:FindFirstChild("MonsterEnemyTag")
if nameTagPart then
local nameTag = nameTagPart:FindFirstChild("SurfaceGui")
if nameTag then
local focus = client.Character and client.Character.PrimaryPart or workspace.CurrentCamera
local distanceAway = utilities.magnitude(entityContainer.PrimaryPart.Position - focus.CFrame.p)
-- bite me
-- todo: options
local displayDistance = 35
local damagedByPlayer = renderData.entityManifest:FindFirstChild("damagedByPlayer") ~= nil
if damagedByPlayer then
displayDistance = 50
end
if not renderData.disableNameTagUI and distanceAway < displayDistance and not renderData.entityManifest:FindFirstChild("isStealthed") and renderData.entityManifest.entityId.Value ~= "Chicken" then
nameTag.Enabled = true
local position = Vector3.new(entityContainer.PrimaryPart.Position.X, entityContainer.PrimaryPart.Position.Y - (1 + entityContainer.PrimaryPart.Size.Y / 2), entityContainer.PrimaryPart.Position.Z)
nameTagPart.CFrame = (workspace.CurrentCamera.CFrame - workspace.CurrentCamera.CFrame.p) + position
local healthTag = entityContainer:FindFirstChild("monsterHealth")
if not renderData.disableHealthBarUI and healthTag then
healthTag.Enabled = damagedByPlayer
elseif healthTag then
healthTag.Enabled = false
end
local dif
if entityContainer.PrimaryPart:FindFirstChild("monsterScale") and entityContainer.PrimaryPart.monsterScale.Value > 1.3 then
displayDistance = displayDistance * entityContainer.PrimaryPart.monsterScale.Value -- andrew 7/25/19 changed to be scale value instead of 2
end
local fullDisplayCutoff = 5
if damagedByPlayer then
fullDisplayCutoff = 10
end
if distanceAway >= fullDisplayCutoff then
dif = (distanceAway - fullDisplayCutoff) / (displayDistance - fullDisplayCutoff)
else
dif = 0
end
if nameTag:FindFirstChild("level") then
nameTag.level.TextTransparency = dif
nameTag.level.curve.ImageTransparency = dif
nameTag.level.curve.shadow.ImageTransparency = dif
end
if nameTag:FindFirstChild("monster") then
nameTag.monster.TextTransparency = dif
nameTag.monster.TextStrokeTransparency = dif * 1.1
nameTag.monster.curve.ImageTransparency = dif
nameTag.monster.curve.shadow.ImageTransparency = dif
if nameTag.nickname.Text ~= "" then
nameTag.nickname.TextTransparency = dif
nameTag.nickname.BackgroundTransparency = dif
end
end
if nameTag:FindFirstChild("skull") and nameTag.skull.Visible then
nameTag.skull.ImageTransparency = dif
end
else
nameTag.Enabled = false
local healthTag = entityContainer:FindFirstChild("monsterHealth")
if healthTag then
healthTag.Enabled = false
end
end
end
end
end
local currentPartyInfo
local function updatePartyInfo(partyInfo)
currentPartyInfo = partyInfo or network:invokeServer("playerRequest_getMyPartyData")
end
local function isPlayerInParty(player)
if currentPartyInfo then
for i, entry in pairs(currentPartyInfo.members) do
if entry.player == player then
return true
end
end
end
end
local function int__updateCharacterNameTag(renderEntityData)
local entityContainer = renderEntityData.entityContainer
local entityManifest = renderEntityData.entityManifest
local displayRange = 35
local nameTagPart = renderEntityData.entityContainer.PrimaryPart:FindFirstChild("PlayerTag")
if entityManifest == nil then
if nameTagPart then
local nameTag = nameTagPart:FindFirstChild("SurfaceGui")
if nameTag then
nameTag.Enabled = false
end
end
return false
end
local focus = workspace.CurrentCamera
local distanceAway = utilities.magnitude(entityContainer.PrimaryPart.Position - focus.CFrame.p)
if nameTagPart then
local nameTag = nameTagPart:FindFirstChild("SurfaceGui")
if nameTag then
local nameTagColor = Color3.new(1,1,1)
local associatePlayer = game.Players:GetPlayerFromCharacter(entityManifest.Parent)
if not associatePlayer then return end
local isPartyMember = isPlayerInParty(associatePlayer)
if isPartyMember then
displayRange = 150
end
if not entityManifest:FindFirstChild("isStealthed") and distanceAway < displayRange and associatePlayer ~= client then
nameTag.Enabled = true
local player = associatePlayer
local healthTag = entityContainer:FindFirstChild("monsterHealth")
if healthTag then
if entityManifest.health.Value / entityManifest.maxHealth.Value < 1 and (not associatePlayer or (associatePlayer:FindFirstChild("isInPVP") and associatePlayer.isInPVP.Value)) then
healthTag.Enabled = true
healthTag.container.backgroundFill.currentHealthFill.ImageColor3 = Color3.fromRGB(77, 225, 69)
elseif isPartyMember then
healthTag.Enabled = true
healthTag.container.backgroundFill.currentHealthFill.ImageColor3 = Color3.fromRGB(226, 34, 40)
else
healthTag.Enabled = false
end
end
local fullDisplayCutoff = 20
if isPartyMember then
fullDisplayCutoff = 50
displayRange = 150
nameTagColor = Color3.fromRGB(100, 255, 255)
nameTag.top.party.Visible = true
elseif player:FindFirstChild("developer") then
nameTagColor = Color3.fromRGB(255, 255, 128)
else
nameTag.top.party.Visible = false
end
local position = Vector3.new(entityManifest.Position.X, entityManifest.Position.Y - (1.9 + entityManifest.Size.Y / 2), entityManifest.Position.Z)
nameTagPart.CFrame = (workspace.CurrentCamera.CFrame - workspace.CurrentCamera.CFrame.p) + position
local dif
if distanceAway >= fullDisplayCutoff then
dif = (distanceAway - fullDisplayCutoff) / (displayRange - fullDisplayCutoff)
else
dif = 0
end
nameTag.curve.ImageTransparency = dif
if nameTag.top:FindFirstChild("level") then
nameTag.top.level.TextTransparency = dif
nameTag.top.level.TextColor3 = nameTagColor
end
if nameTag.top:FindFirstChild("player") then
nameTag.top.player.TextTransparency = dif
nameTag.top.player.TextColor3 = nameTagColor
end
if nameTag.top:FindFirstChild("class") then
nameTag.top.class.ImageTransparency = dif
nameTag.top.class.ImageColor3 = nameTagColor
end
if nameTag.bottom:FindFirstChild("guild") then
nameTag.bottom.guild.TextTransparency = dif
nameTag.bottom.guild.BackgroundTransparency = dif
end
if nameTag.top:FindFirstChild("party") then
nameTag.top.party.image.ImageTransparency = dif
nameTag.top.party.image.ImageColor3 = nameTagColor
end
if nameTag.top:FindFirstChild("input") then
for i,object in pairs(nameTag.top.input:GetChildren()) do
if object:IsA("ImageLabel") then
object.ImageColor3 = nameTagColor
object.ImageTransparency = dif
end
end
end
if nameTag.top:FindFirstChild("dev") then
nameTag.top.dev.TextTransparency = dif
nameTag.top.dev.TextColor3 = nameTagColor
end
else
nameTag.Enabled = false
local healthTag = entityContainer:FindFirstChild("monsterHealth")
if healthTag then
healthTag.Enabled = false
end
end
end
end
end
-- this function automatically hooks players
-- into the renderCharacter update stream
local function assembleCharacterRenderEntity(entityManifest)
local associatePlayer = game.Players:GetPlayerFromCharacter(entityManifest.Parent) do
if associatePlayer then
if not associatePlayer:FindFirstChild("dataLoaded") then
return
end
end
end
local appearanceData = HttpService:JSONDecode(entityManifest.appearance.Value)
local entityContainer = int__assembleRenderCharacter(entityManifest)
entityContainer.Parent = entityRenderCollectionFolder
local renderEntityData = {
entityContainer = entityContainer;
entityManifest = entityManifest;
connections = {};
}
int__connectEntityEvents(entityManifest, renderEntityData)
entitiesBeingRendered[entityManifest] = renderEntityData
-- update the appearance
int__updateRenderCharacter(entityContainer.entity, appearanceData, entityManifest)
end
local function assembleMonsterRenderEntity(entityManifest)
local entityContainer = Instance.new("Model")
local clientPlayerHitbox = entityManifest:Clone()
clientPlayerHitbox.BrickColor = BrickColor.new("Hot pink")
clientPlayerHitbox.CanCollide = false
clientPlayerHitbox.Anchored = true
clientPlayerHitbox.Name = "hitbox"
local clientHitboxToServerHitboxReference = Instance.new("ObjectValue")
clientHitboxToServerHitboxReference.Name = "clientHitboxToServerHitboxReference"
clientHitboxToServerHitboxReference.Value = entityManifest
clientHitboxToServerHitboxReference.Parent = entityContainer
-- clear all unnecessary parts within the hitbox
-- we only want the part itself
clientPlayerHitbox:ClearAllChildren()
entityContainer.PrimaryPart = clientPlayerHitbox
clientPlayerHitbox.Parent = entityContainer
local isMonsterPet, monsterBaseStats, monsterEntityModel do
if not entityManifest:FindFirstChild("pet") then
isMonsterPet = false
monsterBaseStats = monsterLookup[entityManifest.entityId.Value]
monsterEntityModel = monsterLookup[entityManifest.entityId.Value].entity:Clone()
else
isMonsterPet = true
monsterBaseStats = itemLookup[tonumber(entityManifest.entityId.Value)]
monsterEntityModel = itemLookup[tonumber(entityManifest.entityId.Value)].entity:Clone()
end
if monsterEntityModel then
if entityManifest:FindFirstChild("colorVariant") then
for i, obj in pairs(monsterEntityModel:GetDescendants()) do
if obj:IsA("BasePart") and obj:FindFirstChild("doNotDye") == nil then
if obj:FindFirstChild("colorOverride") then
local a = entityManifest.colorVariant.Value
obj.Color = Color3.new(math.clamp(a.r,0,1),math.clamp(a.g,0,1),math.clamp(a.b,0,1))
else
local a = obj.Color
local b = entityManifest.colorVariant.Value
obj.Color = Color3.new(math.clamp(a.r*b.r,0,1), math.clamp(a.g*b.g,0,1), math.clamp(a.b*b.b,0,1))
end
end
end
end
if entityManifest:FindFirstChild("specialName") then
for i, obj in pairs(monsterEntityModel:GetDescendants()) do
if obj:IsA("BasePart") and obj.Name == "variation_"..entityManifest:FindFirstChild("specialName").Value then
obj.Transparency = 0
obj.CanCollide = true
elseif obj:IsA("BasePart") and obj.Name == "variation_default" then
obj.Transparency = 1
obj.CanCollide = false
end
end
end
end
end
if entityManifest:FindFirstChild("monsterScale") then
utilities.scale(monsterEntityModel, entityManifest.monsterScale.Value)
end
local projectionWeld = Instance.new("Motor6D")
projectionWeld.Name = "projectionWeld"
projectionWeld.Part0 = clientPlayerHitbox
projectionWeld.Part1 = monsterEntityModel.PrimaryPart
projectionWeld.C0 = CFrame.new()
projectionWeld.C1 = CFrame.new(0, monsterEntityModel:GetModelCFrame().Y - monsterEntityModel.PrimaryPart.CFrame.Y, 0)
projectionWeld.Parent = clientPlayerHitbox
-- set it up to render
monsterEntityModel.Parent = entityContainer
entityContainer.Parent = entityRenderCollectionFolder
local renderEntityData = {
entityContainer = entityContainer;
entityManifest = entityManifest;
disableHealthBarUI = not not entityManifest:FindFirstChild("isPassive");
disableLevelUI = not not entityManifest:FindFirstChild("isPassive") or not not entityManifest:FindFirstChild("hideLevel");
connections = {};
}
if isMonsterPet then
renderEntityData.disableHealthBarUI = true
end
if entitiesBeingRendered[entityManifest] then
dissassembleRenderEntityByManifest(entityManifest)
end
int__connectEntityEvents(entityManifest, renderEntityData)
entitiesBeingRendered[entityManifest] = renderEntityData
return entityContainer
end
local function showDamageAtPosition(damagePosition, isSecondary)
local part
if typeof(damagePosition) == "Instance" then
part = damagePosition
else
part = Instance.new("Part")
part.CFrame = CFrame.new(damagePosition)
part.Size = Vector3.new(0.2,0.2,0.2)
part.Transparency = 1
part.Anchored = true
part.CanCollide = false
part.Name = "DamagePositionPart"
part.Parent = workspace.CurrentCamera
game.Debris:AddItem(part,3)
end
local hitsound = Instance.new("Sound")
hitsound.SoundId = "rbxassetid://2065833626"
hitsound.MaxDistance = isSecondary and 200 or 1000
hitsound.Volume = isSecondary and 0.25 or 1.5
hitsound.EmitterSize = isSecondary and 1 or 5
hitsound.Parent = part
hitsound:Play()
game.Debris:AddItem(hitsound, 5)
if not isSecondary then
local hit = part:FindFirstChild("hitParticle") or assetFolder.hitParticle:Clone()
hit.Parent = part
hit:Emit(3)
end
end
local function isManifestValid(manifest)
return
manifest.Parent
and (manifest.Parent == workspace or manifest.Parent:IsDescendantOf(workspace))
end
local function updateEntitiesBeingRendered(entitiesToRender)
for i, manifest in pairs(entitiesToRender) do
local renderData = entitiesBeingRendered[manifest]
if renderData and isManifestValid(manifest) and renderData.entityContainer and renderData.entityContainer.PrimaryPart then
renderData.entityContainer.PrimaryPart.CFrame = manifest.CFrame
-- update display stuff
if manifest.entityType.Value == "character" then
int__updateCharacterNameTag(renderData)
elseif manifest.entityType.Value == "monster" or manifest.entityType.Value == "pet" then
int__updateMonsterNameTag(renderData)
end
else
dissassembleRenderEntityByManifest(manifest)
end
end
end
local function getManifestFromEntityContainer(entityContainer)
for manifest, entityData in pairs(entitiesBeingRendered) do
if entityData.entityContainer == entityContainer then
return manifest
end
end
return nil
end
local DISTANCE_TO_RENDER_IN_ENTITY = 300
local function int__updateNearbyEntities()
animationInterface = require(script.Parent:WaitForChild("animationInterface"))
while true do
if configuration.getConfigurationValue("doFixShadowCloneJutsu", client) then
for i, entityContainer in pairs(entityRenderCollectionFolder:GetChildren()) do
local manifest = getManifestFromEntityContainer(entityContainer)
if not manifest or not manifest:IsDescendantOf(workspace) then
if manifest then
entitiesBeingRendered[manifest] = nil
end
entityContainer:Destroy()
end
end
end
for i, player in pairs(game.Players:GetPlayers()) do
if player.Character and player.Character.Parent ~= entityManifestCollectionFolder then
player.Character.Parent = entityManifestCollectionFolder
end
end
if client.Character and client.Character.PrimaryPart then
local clientPosition = workspace.CurrentCamera.CFrame.Position
local entities = utilities.getEntities()
for _, entityManifest in pairs(entities) do
local distanceAway = (entityManifest.Position - clientPosition).magnitude
local alwaysRendered = (entityManifest:FindFirstChild("alwaysRendered") ~= nil)
if alwaysRendered then
if not entitiesBeingRendered[entityManifest] then
assembleMonsterRenderEntity(entityManifest)
end
else
if distanceAway <= DISTANCE_TO_RENDER_IN_ENTITY and not entitiesBeingRendered[entityManifest] then
if entityManifest.entityType.Value == "character" then
assembleCharacterRenderEntity(entityManifest)
elseif entityManifest.entityType.Value == "monster" or entityManifest.entityType.Value == "pet" then
assembleMonsterRenderEntity(entityManifest)
end
elseif distanceAway > DISTANCE_TO_RENDER_IN_ENTITY * 1.05 and entitiesBeingRendered[entityManifest] and not entitiesBeingRendered[entityManifest].isPinned then
if entityManifest.entityType.Value == "character" then
dissassembleRenderEntityByManifest(entityManifest)
elseif entityManifest.entityType.Value == "monster" or entityManifest.entityType.Value == "pet" then
dissassembleRenderEntityByManifest(entityManifest)
end
end
end
end
end
wait(1)
end
end
local function getAlertColor(scale)
local alertColor = Color3.fromRGB(255, 89, 92);
if scale > 4.5 then
alertColor = Color3.fromRGB(255, 64, 0);
elseif scale >= 2.5 then
alertColor = Color3.fromRGB(214, 19, 146);
end
return alertColor
end
local function updateMapColor()
local colorCorrection = game.Lighting:FindFirstChild("giantMonsterColor")
if colorCorrection == nil then
colorCorrection = assetFolder.giantMonsterColor:Clone()
colorCorrection.Parent = game.Lighting
end
local largestScale = 0
for i, manifest in pairs(game.CollectionService:GetTagged("giantEnemy")) do
if manifest:FindFirstChild("health") and manifest.health.Value > 0 then
if manifest:FindFirstChild("monsterScale") and manifest.monsterScale.Value > largestScale then
largestScale = manifest.monsterScale.Value
end
end
end
if largestScale >= 1.3 and game.PlaceId ~= 3303140173 then
local color = getAlertColor(largestScale)
local mapColor = Color3.new(math.clamp(color.r * 1.2,0,1), math.clamp(color.g * 1.2,0,1), math.clamp(color.b * 1.2,0,1))
tween(colorCorrection,
{"Brightness", "Contrast", "Saturation", "TintColor"},
{0, 0.1, 0, mapColor},
0.5
)
else
-- giant enemy gone? Return to normal state
tween(colorCorrection,
{"Brightness", "Contrast", "Saturation", "TintColor"},
{0, 0, 0, Color3.new(1,1,1)},
1
)
end
end
local giantEnemySpawning
local lastGiantEnemyAdded
local function giantEnemyAdded(entityManifest)
local scaleTag = entityManifest:WaitForChild("monsterScale", 60)
if not scaleTag then
return false
end
local scale = scaleTag.Value
local alertColor = getAlertColor(scale)
local alertText
if scale > 4.5 then
alertText = "SWEET MOTHER OF MUSHROOM! A COLOSSAL " .. entityManifest.Name .. " has been spotted!";
elseif scale > 2.5 then
alertText = "GET OUT OF HERE! A super giant " .. entityManifest.Name .. " has been spotted!";
else
alertText = "Run for your life! A giant " .. entityManifest.Name .. " has been spotted!";
end
network:fire("alert", {
text = alertText;
textColor3 = Color3.new(0,0,0);
backgroundColor3 = alertColor;
backgroundTransparency = 0;
textStrokeTransparency = 1;
font = Enum.Font.SourceSansBold;
}, 6, "giantEnemySpawned")
game.StarterGui:SetCore("ChatMakeSystemMessage", {
Text = alertText;
Color = alertColor;
Font = Enum.Font.SourceSansBold;
})
local smoke = assetFolder.giantEnemySmoke:Clone()
smoke.Color = ColorSequence.new(alertColor, Color3.new(0,0,0))
smoke.Rate = 80 * scale
smoke.Parent = entityManifest
local beam = assetFolder.beam:Clone()
beam.CFrame = beam.CFrame - beam.Position + entityManifest.Position
beam.Anchored = true
beam.CanCollide = false
beam.Parent = workspace.CurrentCamera
beam.Color = alertColor
utilities.playSound("giantEnemyBoom", beam)
tween(beam, {"Transparency"}, 1, 2)
tween(beam.Mesh, {"Scale"}, Vector3.new(10000, 20, 20), 2)
game.Debris:AddItem(beam, 3)
if (workspace.CurrentCamera.CFrame.Position - entityManifest.Position).magnitude < 200 then
network:invoke("cameraShake")
end
local colorCorrection = game.Lighting:FindFirstChild("giantMonsterColor")
if colorCorrection == nil then
colorCorrection = assetFolder.giantMonsterColor:Clone()
colorCorrection.Parent = game.Lighting
end
lastGiantEnemyAdded = entityManifest
giantEnemySpawning = true
local max = math.max(alertColor.r, alertColor.g, alertColor.b)^2
local intenseColor = Color3.new((alertColor.r ^ 3)/max,(alertColor.g ^ 3)/max ,(alertColor.b ^ 3)/max)
tween(colorCorrection,
{"Brightness", "Contrast", "Saturation", "TintColor"},
{0.2, 0.8, -1, intenseColor},
0.3
)
delay(0.5, function()
if lastGiantEnemyAdded == entityManifest then
giantEnemySpawning = false
updateMapColor()
end
end)
end
game.CollectionService:GetInstanceRemovedSignal("giantEnemy"):connect(function(entityManifest)
if not giantEnemySpawning then
updateMapColor()
end
end)
game.CollectionService:GetInstanceAddedSignal("giantEnemy"):connect(giantEnemyAdded)
spawn(function()
for i, enemy in pairs(game.CollectionService:GetTagged("giantEnemy")) do
giantEnemyAdded(enemy)
end
end)
local function signal_damage(entityManifest, damageInfo)
local renderEntityData = entitiesBeingRendered[entityManifest]
if renderEntityData then
local associatePlayer
if entityManifest.Parent and entityManifest.Parent:IsA("Model") then
associatePlayer = game.Players:GetPlayerFromCharacter(entityManifest.Parent)
end
local isSecondary = false do
if associatePlayer ~= client and (damageInfo.sourcePlayerId == nil or damageInfo.sourcePlayerId ~= client.UserId) then
isSecondary = true
if damageInfo.damage > 0 then
network:fire("monsterDamagedAtPosition", entityManifest, true)
end
end
end
if damageInfo.sourcePlayerId == client.UserId and entityManifest:FindFirstChild("damagedByPlayer") == nil then
local damagedTag = Instance.new("BoolValue")
damagedTag.Name = "damagedByPlayer"
damagedTag.Parent = entityManifest
end
if associatePlayer == client and damageInfo.damage > 0 then
network:fire("monsterDamagedAtPosition", entityManifest)
end
local container = renderEntityData.entityContainer
local damageIndicator = container:FindFirstChild("damageIndicator")
if damageIndicator == nil then
damageIndicator = ReplicatedStorageAssetFolder.entities.damageIndicator:Clone()
local thickness = math.max((container.PrimaryPart.Size.X + container.PrimaryPart.Size.Z) / 2, 3)
damageIndicator.Size = UDim2.new(thickness, 50, 6, 75)
damageIndicator.Parent = container
end
if not damageIndicator.Adornee then
damageIndicator.Adornee = container.PrimaryPart
damageIndicator.Enabled = true
end
local template = damageIndicator.template:Clone()
local offset = 0.5 - (math.random() - 0.5) * 0.5
template.Text = tostring(math.floor(math.abs(damageInfo.damage) or 0))
template.TextTransparency = 1
template.TextStrokeTransparency = 1
template.Position = UDim2.new(offset,0,0.85,0)
template.Parent = damageIndicator
game.Debris:AddItem(template, 3)
template.Size = UDim2.new(0.7,0,0.1,0)
template.Visible = true
if damageInfo.damage < 0 then
isSecondary = false
end
template.ZIndex = (isSecondary and 1) or 2
local goalPosition = UDim2.new(offset, 0, 0, 0.3)
local goalTransparency = isSecondary and 0.7 or 0
local goalSize = UDim2.new(0.7, 0, 0.3, 0)
local finalSize = UDim2.new(0.7,0,0.1,0)
if isSecondary then
local startTime = tick()
local tweenConnection = RunService.Heartbeat:connect(function(step)
local t = (tick()-startTime)/1.5
template.Position = UDim2.new(offset,0, 0.85 - 0.55*t ,0)
if t > 0.5 then
t = (t - 0.5) * 2
template.Size = UDim2.new(0.7, 0, 0.3 - 0.2*t, 0)
template.TextTransparency = 0.5 + t/2
template.TextStrokeTransparency = 0.5 + t/t
else
t = t * 2
template.Size = UDim2.new(0.7, 0, 0.1 + 0.2*t, 0)
template.TextTransparency = 1 - t/2
template.TextStrokeTransparency = 1 - t/2
end
end)
delay(1.5, function()
tweenConnection:disconnect()
tweenConnection = nil
end)
else
tween(template, {"Position"}, goalPosition, 1.5)
tween(template, {"TextTransparency", "TextStrokeTransparency", "Size"}, {goalTransparency, goalTransparency, goalSize}, 0.75)
delay(0.75, function()
tween(template,{"TextTransparency","TextStrokeTransparency","Size"},{1,1,finalSize},0.75)
end)
end
template.TextColor3 = Color3.fromRGB(255, 251, 117)
template.Font = Enum.Font.SourceSans
-- healing
if damageInfo.damage < 0 then
template.TextColor3 = Color3.fromRGB(0, 255, 213)
template.Font = Enum.Font.SourceSansBold
else
if associatePlayer == client then
template.TextColor3 = Color3.fromRGB(204, 0, 255)
if damageInfo.supressed then
template.TextColor3 = Color3.fromRGB(176, 137, 200)
end
elseif associatePlayer and isSecondary then
template.TextColor3 = Color3.fromRGB(204, 0, 255)
template.TextTransparency = 0.5
if damageInfo.supressed then
template.TextColor3 = Color3.fromRGB(176, 137, 200)
end
else
if damageInfo.isCritical then
template.TextColor3 = Color3.fromRGB(255, 175, 83)
end
if damageInfo.supressed then
template.TextColor3 = Color3.fromRGB(150, 150, 150)
end
end
if damageInfo.isCritical then
template.Font = Enum.Font.SourceSansBold
end
end
end
end
local function onEntityManifestCollectionFolderChildAdded(entityManifest)
--assuming Damiens up to some mischief in here for monster rework
--[[
if entityManifest:FindFirstChild("monsterScale") then
giantEnemyAdded(entityManifest)
else
utilities.connectEventHelper(entityManifest.ChildAdded, function(child)
if child.Name == "monsterScale" and child.Value > 1.3 then
giantEnemyAdded(entityManifest)
return true
end
end)
end
]]
end
local function int__replicateAnimationFromPlayer(player, animationSequenceName, animationName, extraData)
local entityManifest = player.Character and player.Character.PrimaryPart
if entitiesBeingRendered[entityManifest] then
entitiesBeingRendered[entityManifest]:playAnimation(animationSequenceName, animationName, extraData)
end
end
local function onPlayerAppliedScroll(serverPlayer, scrollItemId, successfullyApplied)
if not serverPlayer or not serverPlayer.Character or not serverPlayer.Character.PrimaryPart then return end
local realItem = itemLookup[scrollItemId]
local clientCharacterContainer = entitiesBeingRendered[serverPlayer.Character.PrimaryPart] and entitiesBeingRendered[serverPlayer.Character.PrimaryPart].entityContainer
if clientCharacterContainer then
if realItem and realItem.module then
local manifest = realItem.module:FindFirstChild("manifest")
if manifest then
if manifest:IsA("MeshPart") then
local representation = manifest:Clone()
representation.Transparency = 1
representation.CanCollide = false
representation.Anchored = false
representation.Name = "scrollUseRepresentation"
local originalSize = representation.Size
representation.Parent = workspace.CurrentCamera
representation.Size = originalSize / 10
tween(representation,{"Size","Transparency"},{originalSize, 0},0.3)
local positionOffset = Instance.new("Vector3Value")
positionOffset.Value = Vector3.new(0,3,0)
local function render()
if representation then
if clientCharacterContainer and clientCharacterContainer.PrimaryPart then
representation.CFrame = CFrame.new(clientCharacterContainer.PrimaryPart.Position + positionOffset.Value)
else
representation:Destroy()
end
end
end
local connection
if serverPlayer == game.Players.LocalPlayer then
connection = RunService.RenderStepped:connect(render)
else
connection = RunService.Heartbeat:connect(render)
end
tween(positionOffset,{"Value"},Vector3.new(0,5,0),0.3)
if representation then
if successfullyApplied then
utilities.playSound("scrollSuccess", representation)
else
utilities.playSound("scrollFail", representation)
end
end
wait(0.5)
if successfullyApplied then
local sparkles = assetFolder.scrollSuccess.Sparkles:Clone()
sparkles.Enabled = false
sparkles.Parent = representation
sparkles:Emit(30)
local rayHolder = assetFolder.scrollSuccess.Attachment:Clone()
rayHolder.Parent = representation
wait(0.1)
tween(positionOffset,{"Value"},Vector3.new(0,5,0),0.7,nil,Enum.EasingDirection.In)
tween(representation,{"Transparency"},1,0.7)
wait(3)
else
wait(0.1)
local explode = Instance.new("Explosion")
explode.DestroyJointRadiusPercent = 0
explode.Parent = workspace
explode.Position = representation.Position
connection:disconnect()
representation.Anchored = false
representation.CanCollide = true
representation.Velocity = Vector3.new(math.random(-100,100),math.random(-100,100),math.random(-100,100))
wait(3)
end
pcall(function()
connection:disconnect()
connection = nil
end)
if representation then
representation:Destroy()
end
end
end
end
end
end
local function getMyClientCharacterContainer()
while not client.Character or not client.Character.PrimaryPart or not entitiesBeingRendered[client.Character.PrimaryPart] do
wait(0.1)
end
return entitiesBeingRendered[client.Character.PrimaryPart].entityContainer
end
local function createRenderCharacterContainerFromCharacterAppearanceData(manifestContainer, appearanceData)
local renderCharacterContainer = int__assembleRenderCharacter(manifestContainer.PrimaryPart)
int__updateRenderCharacter(renderCharacterContainer.entity, appearanceData)
return renderCharacterContainer
end
local function applyCharacterAppearanceToRenderCharacter(entity, appearanceData)
int__updateRenderCharacter(entity, appearanceData)
end
local function assembleEntityByManifest(entityManifest)
if entityManifest.entityType.Value == "character" then
return assembleCharacterRenderEntity(entityManifest)
elseif entityManifest.entityType.Value == "monster" or entityManifest.entityType.Value == "pet" then
return assembleMonsterRenderEntity(entityManifest)
end
end
function module.init(Modules)
network = Modules.network
tween = Modules.tween
utilities = Modules.utilities
physics = Modules.physics
placeSetup = Modules.placeSetup
projectile = Modules.projectile
configuration = Modules.configuration
events = Modules.events
events:registerForEvent("playersXpGained", playersXpGained)
entityManifestCollectionFolder = placeSetup.awaitPlaceFolder("entityManifestCollection")
entityRenderCollectionFolder = placeSetup.awaitPlaceFolder("entityRenderCollection")
network:connect("signal_damage", "OnClientEvent", signal_damage)
network:create("getMyClientCharacterContainer", "BindableFunction", "OnInvoke", getMyClientCharacterContainer)
--run manager connections
item_manager.createNetworkConnections(client,entitiesBeingRendered)
network:connect("playerAppliedScroll", "OnClientEvent", onPlayerAppliedScroll)
network:create("myClientCharacterContainerChanged", "BindableEvent")
-- todo: convert all manifestContainer calls to just manifest!
network:create("createRenderCharacterContainerFromCharacterAppearanceData", "BindableFunction", "OnInvoke", createRenderCharacterContainerFromCharacterAppearanceData)
network:create("createRenderMonsterContainer", "BindableFunction", "OnInvoke", function(entityManifest)
local renderMonsterContainer = assembleMonsterRenderEntity(entityManifest)
return renderMonsterContainer
end)
-- todo: convert all manifestContainer calls to just manifest!
network:create("applyCharacterAppearanceToRenderCharacter", "BindableFunction", "OnInvoke", applyCharacterAppearanceToRenderCharacter)
network:create("myClientCharacterDied", "BindableEvent")
network:create("myClientCharacterWeaponChanged", "BindableEvent")
network:create("assembleEntityByManifest", "BindableFunction", "OnInvoke", assembleEntityByManifest)
network:create("setStopRenderingPlayers", "BindableFunction", "OnInvoke", function() end)
network:create("monsterDamagedAtPosition", "BindableEvent", "Event", showDamageAtPosition)
network:create("getRenderCharacterContainerByEntityManifest", "BindableFunction", "OnInvoke", function(entityManifest)
if entityManifest and entitiesBeingRendered[entityManifest] then
return entitiesBeingRendered[entityManifest].entityContainer
end
end)
network:create("getPlayerRenderDataByNameTag", "BindableFunction", "OnInvoke", function(player, nameTag)
local entityManifest = player.Character and player.Character.PrimaryPart
if entityManifest and entitiesBeingRendered[entityManifest] then
return entitiesBeingRendered[entityManifest][nameTag]
end
end)
network:create("getEntityManifestByRenderEntityContainer", "BindableFunction", "OnInvoke", function(renderEntityContainer)
for entityManifest, v in pairs(entitiesBeingRendered) do
if renderEntityContainer == v.entityContainer then
return entityManifest
end
end
return nil
end)
network:create("setRenderDataByNameTag", "BindableFunction", "OnInvoke", function(entityManifest, nameTag, value)
if entityManifest and entitiesBeingRendered[entityManifest] then
entitiesBeingRendered[entityManifest][nameTag] = value
end
end)
network:create("getRenderDataByNameTag", "BindableFunction", "OnInvoke", function(entityManifest, nameTag)
if entityManifest and entitiesBeingRendered[entityManifest] then
return entitiesBeingRendered[entityManifest][nameTag]
end
return nil
end)
network:connect("replicatePlayerAnimationSequence", "OnClientEvent", function(player, ...)
int__replicateAnimationFromPlayer(player, ...)
end)
network:create("playPlayerAnimationSequenceOnClientCharacter", "BindableEvent", "Event", function(...)
int__replicateAnimationFromPlayer(client, ...)
end)
network:create("getPlayerRenderStateByPlayerName","BindableFunction","OnInvoke",function(playerName)
return "not added"
end)
network:create("getPlayerRenderFromPlayerInstance", "BindableFunction", "OnInvoke", function(playerInstance)
return error("NOT YET IMPLEMENTED")
end)
network:create("getPlayerRenderFromManifest", "BindableFunction", "OnInvoke", function(playerCharacterManifest)
local data = entitiesBeingRendered[playerCharacterManifest]
if data then
return data.entityContainer
end
end)
-- todo: replication
network:create("replicateClientCharacterWeaponStateChanged", "BindableEvent", "Event", function(weaponType, weaponState)
local entityManifest = client.Character and client.Character.PrimaryPart
if entityManifest and entitiesBeingRendered[entityManifest] then
return entitiesBeingRendered[entityManifest]:setWeaponState(weaponType, weaponState)
end
end)
network:connect("signal_myPartyDataChanged", "OnClientEvent", updatePartyInfo)
network:create("getMovementAnimationForCharacter", "BindableFunction", "OnInvoke", function(animationController, state, weaponTypeEquipped, weaponState)
local animation = state
if not animationInterface then
animationInterface = require(script.Parent:WaitForChild("animationInterface"))
end
if weaponTypeEquipped and animationInterface.rawAnimationData.movementAnimations[animation .. "_" .. weaponTypeEquipped] then
animation = animation .. "_" .. weaponTypeEquipped
if weaponState and animationInterface.rawAnimationData.movementAnimations[animation .. "_" .. weaponState] then
animation = animation .. "_" .. weaponState
end
end
return animationInterface.getSingleAnimation(animationController, "movementAnimations", animation)
end)
local priorityCount = 30
local priorityDistance = 50
local deferredEntities = {}
local priorityEntities = {}
RunService:BindToRenderStep("updateEntityRendering", 50, function()
deferredEntities = {}
priorityEntities = {}
local n = 0
local player = game.Players.LocalPlayer
local playerPosition = player.Character and player.Character.PrimaryPart and player.Character.PrimaryPart.Position
for manifest, renderData in pairs(entitiesBeingRendered) do
if n <= priorityCount and (manifest.Position - playerPosition).magnitude <= priorityDistance then
table.insert(priorityEntities, manifest)
else
table.insert(deferredEntities, manifest)
end
end
updateEntitiesBeingRendered(priorityEntities)
end)
RunService.Heartbeat:connect(function()
updateEntitiesBeingRendered(deferredEntities)
end)
-- -- giant message stuff
entityManifestCollectionFolder.ChildAdded:connect(onEntityManifestCollectionFolderChildAdded)
-- initialize parties
updatePartyInfo()
-- initialize updates
spawn(int__updateNearbyEntities)
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/firefly.client.lua
================================================
-- Pretty fireflies
-- Author: Polymorphic
local runService = game:GetService("RunService")
local fireflies = {}
local player = game.Players.LocalPlayer
local fireflyVelocity = 5
local fireflyCount = 30
local fireflyOrigin
local fireflyOriginOffset = Vector3.new(200, 10, 200)
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local placeSetup = modules.load("placeSetup")
local utilities = modules.load("utilities")
local tween = modules.load("tween")
--local firefliesFolder = placeSetup.getPlaceFolder("fireflies")
local firefliesFolder = Instance.new("Folder")
firefliesFolder.Name = "fireflies"
firefliesFolder.Parent = workspace.CurrentCamera
local function assignTargetCFrame()
return
CFrame.new(workspace.CurrentCamera.CFrame.p)
* CFrame.new(
math.random(-fireflyOriginOffset.X / 2, fireflyOriginOffset.X / 2),
math.random(-fireflyOriginOffset.Y / 2, fireflyOriginOffset.Y / 2),
math.random(-fireflyOriginOffset.Z / 2, fireflyOriginOffset.Z / 2)
)
end
local function updateFireflies(t, step)
local currentTime = tick()
for i, fireflyTable in pairs(fireflies) do
local alpha = (currentTime - fireflyTable.startTime) / fireflyTable.duration
fireflyTable.firefly.CFrame = fireflyTable.startCFrame:lerp(fireflyTable.targetCFrame, alpha) + Vector3.new(0, math.sin(currentTime - fireflyTable.offsetY), 0)
if alpha >= 1 then
local targetCFrame = assignTargetCFrame()
local duration = utilities.magnitude(targetCFrame.p - fireflyTable.firefly.Position) / fireflyVelocity
fireflyTable.duration = duration
fireflyTable.startCFrame = fireflyTable.firefly.CFrame
fireflyTable.targetCFrame = targetCFrame;
fireflyTable.startTime = tick()
end
end
end
local function onCharacterAdded(character)
--[[
if game.PlaceId ~= 3323943158 then
if not character.PrimaryPart then
local hitbox = character:WaitForChild("hitbox", 15)
if hitbox then
character.PrimaryPart = hitbox
end
end
fireflyOrigin = character.PrimaryPart
else -- if cutscene
fireflyOrigin = workspace:WaitForChild("path"):WaitForChild("cutscenewagon").PrimaryPart
end
]]
end
local fireflyConnection
local function main()
if player.Character then
onCharacterAdded(player.Character)
end
player.CharacterAdded:connect(onCharacterAdded)
while wait(1) do
local fireflyOrigin = workspace.CurrentCamera.CFrame
if not fireflyOrigin then return end
for i, fireflyTable in pairs(fireflies) do
if utilities.magnitude(fireflyTable.firefly.Position - fireflyOrigin.Position) >= utilities.magnitude(fireflyOriginOffset) * 1.1 then
game.Debris:AddItem(fireflyTable.firefly, 0.5)
tween(fireflyTable.firefly, {"Transparency"}, 1, 0.5)
table.remove(fireflies, i)
end
end
if game.Lighting.ClockTime >= 18 or game.Lighting.ClockTime <= 6.15 then
if not fireflyConnection then
fireflyConnection = runService.Stepped:connect(updateFireflies)
end
if fireflyOrigin and #firefliesFolder:GetChildren() < fireflyCount then
local firefly = game.ReplicatedStorage.firefly:Clone()
if game.ReplicatedStorage:FindFirstChild("fireflyColor") then
local col = game.ReplicatedStorage.fireflyColor.Value
firefly.Color = col
if firefly:FindFirstChild("ParticleEmitter") then
firefly.ParticleEmitter.Color = ColorSequence.new(col)
end
if firefly:FindFirstChild("PointLight") then
firefly.PointLight.Color = col
end
end
firefly.Parent = firefliesFolder
firefly.CFrame = assignTargetCFrame()
firefly.Transparency = 1
tween(firefly, {"Transparency"}, 0, 0.5)
local targetCFrame = assignTargetCFrame()
local duration = utilities.magnitude(targetCFrame.p - firefly.Position) / fireflyVelocity
table.insert(fireflies, {
firefly = firefly;
duration = duration;
startCFrame = firefly.CFrame;
targetCFrame = targetCFrame;
startTime = tick();
offsetX = math.random(9001);
offsetY = math.random(9001);
})
end
else
if #firefliesFolder:GetChildren() > 0 then
local target = firefliesFolder:GetChildren()[1]
for i, fireflyData in pairs(fireflies) do
if fireflyData.firefly == target then
table.remove(fireflies, i)
end
end
target:Destroy()
else
if fireflyConnection then
fireflyConnection:disconnect()
fireflyConnection = nil
end
end
end
end
end
main()
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/guiLoader.client.lua
================================================
-- Annoying hack to get around Roblox's dumb gui replication changes
-- Author: berezaa
if not game:IsLoaded() then
repeat wait() until game:IsLoaded()
end
local localPlayer = game.Players.LocalPlayer
local uiSystem = game.ReplicatedStorage:WaitForChild("StarterGuiFolder")
function rebuildUiLocally(character)
for i, child in ipairs(uiSystem:GetChildren()) do
child:Clone().Parent = localPlayer.PlayerGui
end
end
localPlayer.CharacterAdded:Connect(rebuildUiLocally)
if localPlayer.Character then
rebuildUiLocally()
end
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/inputController/characterController.lua
================================================
-- will move when deployed if necessary
local class = {}
class.__index = class
function class.new(object)
--print("Character Object:", object)
local self = setmetatable({}, class)
self.character = game.Players.LocalPlayer.Character
self.state = ""
self.animations = {}
--print(self.character:GetFullName())
self.events = {
["StateHandler"] = self.character.hitbox.state.Changed:Connect(function(value)
--print("State: ", value)
self.state = value
end)
}
--[[for _,anim in pairs() do
self.animations[anim] = self.character.Humanoid:LoadAnimation(anim)
end]]
end
return class
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/inputController/init.client.lua
================================================
local player = game.Players.LocalPlayer
local inputService = game:GetService("UserInputService")
local characterController = require(script.characterController)
player.CharacterAdded:Connect(function()
wait(3) -- temporary debug wait
characterController.new(player)
end)
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/itemInterface.lua
================================================
-- Author: Polymorphic
local module = {}
local itemsToRenderSFX = {}
local characterHitboxPart
local runService = game:GetService("RunService")
local player = game.Players.LocalPlayer
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local utilities = modules.load("utilities")
local placeSetup = modules.load("placeSetup")
local itemLookup = require(replicatedStorage.itemData)
local itemsFolder = placeSetup.awaitPlaceFolder("items")
local function onCharacterAdded(character)
characterHitboxPart = character.PrimaryPart
end
-- todo: optimize items not relevant to player
local function onItemAdded(item)
if item:WaitForChild("MASK_MOTOR", 20) then
local degreeOffset = math.random(360)
local offset = CFrame.Angles(math.pi * math.sin(16 * degreeOffset), degreeOffset, math.pi * math.cos(16 * degreeOffset))
table.insert(itemsToRenderSFX, {manifest = item; offset = offset})
end
-- make items that you cannot pick up transparent
if item:WaitForChild("owners", 20) then
wait()
if utilities.playerCanPickUpItem(player, item) then
if item:FindFirstChild("Legendary") then
utilities.playSound("legendaryItemDrop", item)
item:WaitForChild("Trail",1)
item.Trail.Color = ColorSequence.new(Color3.fromRGB(138, 11, 170))
elseif item:FindFirstChild("Rare",1) then
utilities.playSound("rareItemDrop", item)
item:WaitForChild("Trail",1)
item.Trail.Color = ColorSequence.new(Color3.fromRGB(255, 213, 0))
elseif item.Name == "monster idol" then
utilities.playSound("idolDrop", item)
else
utilities.playSound("itemDrop", item)
end
else
for i,part in pairs(item:GetDescendants()) do
if part:IsA("BasePart") and part.Transparency < 1 then
part.Transparency = 1 - ((1 - part.Transparency) * 0.3)
if part.Material == Enum.Material.Glass then
part.Material = Enum.Material.SmoothPlastic
end
elseif part:IsA("ParticleEmitter") or part:IsA("Beam") or part:IsA("Trail") or part:IsA("PointLight") or part:IsA("Light") then
part.Enabled = false
end
end
if item:IsA("BasePart") then
item.Transparency = 1 - ((1 - item.Transparency) * 0.3)
if item.Material == Enum.Material.Glass then
item.Material = Enum.Material.SmoothPlastic
end
end
end
end
end
local tau = 2 * math.pi
local function renderItemSFX()
local degree = math.rad(tick() % 360)
local applicationOffset = Vector3.new(0, 0.75 + 0.35 * math.sin(32 * degree), 0)
local rotationOffset = CFrame.Angles(0, math.pi * math.cos(16 * degree), math.pi / 6)
for i, item in pairs(itemsToRenderSFX) do
if item.manifest and item.manifest.Parent then
item.manifest.MASK_MOTOR.C1 = (rotationOffset * item.offset) + applicationOffset
else
table.remove(itemsToRenderSFX, i)
end
end
end
local function main()
-- queue character
if player.Character then
onCharacterAdded(player.Character)
end
player.CharacterAdded:connect(onCharacterAdded)
-- add items to queue
for i, item in pairs(itemsFolder:GetChildren()) do
spawn(function() onItemAdded(item) end)
end
itemsFolder.ChildAdded:connect(onItemAdded)
runService.Heartbeat:connect(renderItemSFX)
end
main()
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/loadData.lua
================================================
local module = {}
local TeleportService = game:GetService("TeleportService")
local teleportData = TeleportService:GetLocalPlayerTeleportData() or {}
local arrivingFrom = teleportData.arrivingFrom
local providedTimeStamp = teleportData.dataTimestamp
local accessoriesData = teleportData.playerAccessories
function module.init(Modules)
local network = Modules.network
local slot, accessories
if arrivingFrom then
-- todo: blackout UI
slot = TeleportService:GetTeleportSetting("dataSlot")
if accessoriesData and type(accessoriesData) == "string" then
accessories = game:GetService("HttpService"):JSONDecode(accessoriesData)
end
else
slot = 1
end
network:invokeServer("loadPlayerData", slot, providedTimeStamp, accessories)
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/mapInteraction.lua
================================================
-- a collection of interactable map elements
-- Author: berezaa
local module = {}
local player = game.Players.LocalPlayer
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local tween = modules.load("tween")
local bouncingParts = {}
local function partTouched(part, hit)
-- automatic debounce
if game.CollectionService:HasTag(part, "ActivePart") then
game.CollectionService:RemoveTag(part, "ActivePart")
spawn(function()
wait(0.1)
game.CollectionService:AddTag(part, "ActivePart")
end)
if player.Character and player.Character.PrimaryPart and part:FindFirstChild("HitDebounce") == nil and hit:IsDescendantOf(player.Character) then
for i, p in pairs(bouncingParts) do
if p == part then
return
end
end
if game.CollectionService:HasTag(part,"Bounce") then
local size = part.Size
local difference = (player.Character.PrimaryPart.Position - part.position).unit
if math.abs(difference.X) < 0.2 then
difference = Vector3.new(0,difference.Y,difference.Z)
end
if math.abs(difference.Y) < 0.2 then
difference = Vector3.new(difference.X,0,difference.Z)
end
if math.abs(difference.Z) < 0.2 then
difference = Vector3.new(difference.X,difference.Y,0)
end
local coef = 1 + ((size.X * size.Y * size.Z) ^ (1/3))
local soundMirror = game.ReplicatedStorage.assets.sounds:FindFirstChild("bounce")
if soundMirror then
local sound = Instance.new("Sound")
for property, value in pairs(game.HttpService:JSONDecode(soundMirror.Value)) do
sound[property] = value
end
sound.PlaybackSpeed = math.clamp(1.5 - coef / 50, 0.5, 1.5)
sound.Volume = math.clamp(coef/25, 0.2, 2)
sound.Parent = part
sound:Play()
game.Debris:AddItem(sound,10)
end
spawn(function()
local initSize = part.Size
table.insert(bouncingParts, part)
--tween(part, {"Size"},{Vector3.new(part.Size.X*1.2, part.Size.Y*1.2, part.Size.Z*1.2)},.4)
--wait(.4)
tween(part, {"Size"},{initSize*1.5},.3, Enum.EasingStyle.Quad)
wait(.3)
tween(part, {"Size"},{initSize},.7, Enum.EasingStyle.Bounce)
wait(.7)
for i, p in pairs(bouncingParts) do
if p == part then
table.remove(bouncingParts,i)
end
end
end)
network:fire("applyJoltVelocityToCharacter", difference * coef * 15)
elseif part.Name == "shopPart" then
network:invoke("openShop")
end
end
end
end
network:create("touchedActivePart","BindableEvent","Event",partTouched)
local function check(part)
if part:IsA("BasePart") then
if (part.Parent.Name == "shroom" or part.Parent.Name == "Flower") and part.Parent:FindFirstChild("Destroyed") == nil then
local size = part.Size
if size.X * size.Y * size.Z <= 25 then
-- parts that a basic attack can destroy
game.CollectionService:AddTag(part,"Destroyable")
part.CanCollide = false
elseif part.Name == "MushPart" then
-- parts that you can bounce on
game.CollectionService:AddTag(part,"Bounce")
game.CollectionService:AddTag(part,"ActivePart")
part.CanCollide = true
end
elseif part.Name == "shopPart" then
game.CollectionService:AddTag(part,"ActivePart")
end
end
if game.CollectionService:HasTag(part,"ActivePart") then
part.Touched:connect(function(hit)
partTouched(part, hit)
end)
end
end
spawn(function()
for i,child in pairs(workspace:GetDescendants()) do
check(child)
end
workspace.DescendantAdded:connect(check)
end)
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/npcMarkers.lua
================================================
-- Pretty golden punctuation
-- Author: berezaa
local module = {}
--[[
local assetFolder = script.Parent.Parent:WaitForChild("assets")
local replicatedStorage = game:GetService("ReplicatedStorage")
local collectionService = game:GetService("CollectionService")
local runService = game:GetService("RunService")
local questLookup = require(replicatedStorage.questLookup)
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local tween = modules.load("tween")
local utilities = modules.load("utilities")
local levels = modules.load("levels")
local mapping = modules.load("mapping")
local questUtil = modules.load("client_quest_util")
local placeSetup = modules.load("placeSetup")
local foilage = placeSetup.getPlaceFolder("foilage")
local utilTable = {}
utilTable.network = network
utilTable.utilities = utilities
utilTable.levels = levels
utilTable.quest_util = questUtil
utilTable.mapping = mapping
local function getPlayerQuestStateByQuestId(questId)
local quests = network:invoke("getCacheValueByNameTag", "quests")
for i, playerQuestData in pairs(quests.active) do
if playerQuestData.id == questId then
local objectiveSteps = 0
local objectiveStepsDone = 0
-- hacky fix for now but at least it wont break
if playerQuestData.currentObjective > #playerQuestData.objectives then
return mapping.questState.completed
end
for ii, playerStepData in pairs(playerQuestData.objectives[playerQuestData.currentObjective].steps) do
objectiveSteps = objectiveSteps + 1
if playerStepData.requirement.amount <= playerStepData.completion.amount then
objectiveStepsDone = objectiveStepsDone + 1
end
end
if objectiveStepsDone > 0 and objectiveStepsDone == objectiveSteps and playerQuestData.objectives[playerQuestData.currentObjective].started then
--if playerQuestData.currentObjective == #playerQuestData.objectives then
-- return mapping.questState.handing
--else
return mapping.questState.objectiveDone
--end
else
if playerQuestData.objectives[playerQuestData.currentObjective].started then
return mapping.questState.active
else
return mapping.questState.unassigned
end
return mapping.questState.active
end
end
end
for i, completePlayerQuestData in pairs(quests.completed) do
if completePlayerQuestData.id == questId then
return mapping.questState.completed
end
end
return mapping.questState.unassigned
end
local function checkIfObjectiveStartedByQuestId(questId)
local quests = network:invoke("getCacheValueByNameTag", "quests")
for i, playerQuestData in pairs(quests.active) do
if playerQuestData.id == questId then
return playerQuestData.objectives[playerQuestData.currentObjective].started
end
end
return true
end
local colors = {
active = Color3.fromRGB(255, 179, 25);
inactive = Color3.fromRGB(88, 88, 88);
}
local questData
local questMarkers = {}
local updatingParts = false
local rings = {}
local function updateDialoguePart(dialoguePart)
updatingParts = true
local existingPart = questMarkers[dialoguePart]
if existingPart then
existingPart:Destroy()
end
local ySize = 4
--if dialoguePart.Parent and dialoguePart.Parent:IsA("Model") and dialoguePart:isDescendantOf(game.Workspace) then
--ySize = 4
--ySize = dialoguePart.Parent:GetExtentsSize().Y / 2 !this line was lagging the client like crazy when there were enough quest/shop givers in one place!
--end
local markerPart
local markerStyle
if dialoguePart:FindFirstChild("inventory") then
markerPart = "Money"
markerStyle = "active"
elseif dialoguePart:FindFirstChild("dialogue") then
local dialogue = require(dialoguePart.dialogue)
if dialogue.flagForQuest then
local flagForQuest = dialogue.flagForQuest
if type(flagForQuest) == "function" then
flagForQuest = flagForQuest(utilTable)
end
local questData = questLookup[flagForQuest]
local questState = getPlayerQuestStateByQuestId(flagForQuest)
local questCanBeStarted = questUtil.masterCanStartQuest(flagForQuest) -- tests for all quest requirements
local currentObjectiveAndStarted = questUtil.getQuestObjectiveAndStarted(flagForQuest)
local canContinue = true
local objectiveChoiceTable = dialogue.getObjectiveOptionsTable(utilTable)
if currentObjectiveAndStarted < 0 then
if objectiveChoiceTable[currentObjectiveAndStarted *-1] == nil then
canContinue = false
end
else
if objectiveChoiceTable[currentObjectiveAndStarted] == nil then
canContinue = false
end
end
-- objective unassigned logic
if currentObjectiveAndStarted < 0 and canContinue then
local actualObjective = currentObjectiveAndStarted * -1
objectiveChoiceTable = objectiveChoiceTable[actualObjective]
for i, choice in pairs(objectiveChoiceTable) do
if choice.isStarterNPC ~= nil and not choice.isStarterNPC then
canContinue = false
end
end
elseif canContinue then
-- objective handing logic
objectiveChoiceTable = objectiveChoiceTable[currentObjectiveAndStarted]
for i, choice in pairs(objectiveChoiceTable) do
if choice.isHanderNPC ~= nil and not choice.isHanderNPC then
canContinue = false
end
end
end
if canContinue then
if questData and questState ~= mapping.questState.completed then
if questState == mapping.questState.handing or questState == mapping.questState.objectiveDone then
-- quest to hand in takes max priority
markerPart = "Question"
markerStyle = "active"
elseif questState == mapping.questState.unassigned then
if questCanBeStarted then
-- valid quest to do
markerPart = "Exclaim"
markerStyle = "active"
elseif markerPart == nil then
-- quest out of requirement
-- ber edit remove marker from quests you cant do
-- markerPart = "Exclaim" -- if they don't have the requirements don't show the marker at all
-- markerStyle = "inactive"
end
elseif questState == mapping.questState.active and markerPart == nil then
markerPart = "Question"
markerStyle = "inactive"
end
end
end
end
elseif dialoguePart:FindFirstChild("QuestObjectiveTag") then
markerPart = "Exclaim"
markerStyle = "active"
end
if markerPart then
local primaryPart = dialoguePart.Parent.PrimaryPart or dialoguePart
local radius = dialoguePart:FindFirstChild("range") and dialoguePart.range.Value or 8
local realMarker = assetFolder:FindFirstChild(markerPart)
if realMarker and dialoguePart:IsDescendantOf(game.workspace) then
realMarker = realMarker:Clone()
questMarkers[dialoguePart] = realMarker
realMarker.Anchored = true
realMarker.Color = colors[markerStyle] or colors["inactive"]
local primaryPart = dialoguePart.Parent.PrimaryPart or dialoguePart
realMarker.CFrame = primaryPart.CFrame * CFrame.Angles(-math.pi/2, 0, 0) + Vector3.new(0, ySize + realMarker.Size.Y/2 + 3, 0)
realMarker.Parent = foilage
local realMarkerMask = realMarker:Clone()
realMarkerMask.Parent = realMarker
realMarkerMask.Material = Enum.Material.Neon
realMarkerMask.Transparency = 0.7
if markerStyle == "inactive" then
realMarker.Transparency = 0.4
end
end
end
end
collectionService:GetInstanceAddedSignal("interact"):connect(function(interaction)
updateDialoguePart(interaction)
end)
collectionService:GetInstanceRemovedSignal("interact"):connect(function(interaction)
local existingPart = questMarkers[interaction]
if existingPart then
existingPart:Destroy()
end
end)
local function updateAllParts()
local dialogueParts = collectionService:GetTagged("interact")
for i,dialoguePart in pairs(dialogueParts) do
updateDialoguePart(dialoguePart)
end
end
spawn(function()
wait(3)
updateAllParts()
end)
local function onDataChange(key, value)
if key == "quests" or key == "class" or key == "level" then
updateAllParts()
end
end
network:create("npcmarkers_force_update", "BindableEvent", "Event", function(player)
updateAllParts()
end)
network:connect("propogationRequestToSelf", "Event", onDataChange)
local angleOffset = 0
local heightOffset = -0.25
-- todo: yikes
runService.Heartbeat:connect(function()
for rootPart, marker in pairs(questMarkers) do
if rootPart.Parent then
if rootPart.Parent.PrimaryPart then
rootPart = rootPart.Parent.PrimaryPart
end
local baseCF = rootPart.CFrame * CFrame.Angles(-math.pi/2, 0, 0) + Vector3.new(0, 5.75, 0)
marker.CFrame = baseCF * CFrame.Angles(0, 0, math.rad(angleOffset)) + Vector3.new(0, heightOffset, 0)
local markerMask = marker:FindFirstChild(marker.Name)
if markerMask then
markerMask.CFrame = marker.CFrame
end
else
questMarkers[rootPart] = nil
end
end
end)
spawn(function()
while runService.Heartbeat:wait() do
angleOffset = angleOffset + 1
if angleOffset >= 360 then
angleOffset = 0
end
end
end)
spawn(function()
while runService.Heartbeat:wait() do
for i=1,100 do
heightOffset = heightOffset + 1/200
runService.Heartbeat:wait()
end
for i=1,100 do
heightOffset = heightOffset - 1/200
runService.Heartbeat:wait()
end
end
end)
]]
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/playerDataPropogationCache.lua
================================================
local module = {}
-- this is the service that will handle data propogation between client and server, effectively acting as the gatekeeper for data
-- leaving the client to the server, and entering the client from the server.
-- IMPORTANT! This can all be editted by the client, never assume the player's client cache data to be true. This will only have negative
-- implications on players that do edit the cache (ie, a malicious player COULD edit their client cache inventory to include
-- any items they dont own, and they could initiate a trade with the item appearing in the trade *ON THEIR SIDE*. The server will
-- validate and reject this when it sees they're attempting to trade an item they don't own, so IMO it's not a huge issue)
-- btw: I'm doing this so that we don't /need/ ten different connection listeners throughout the client, especially if, say,
-- for a trade window we want to just get the inventory. allows us to skip requesting for the information, and as long as the
-- player isn't being malicious then there will be no issues. players who maliciouslly edit their cache will be faced with a server
-- rejection message, have their cache flushed, and then need to wait for the client to fetch the cache again. no fun for them!
-- Author: Polymorphic
local cache = {}
local network
local utilities
-- this runs when the server sends information to the client, store it.
-- server will not spam this.
local function onPropogateCacheDataRequestToClientReceived(propogationNameTag, propogationData)
cache[propogationNameTag] = propogationData
network:fire("propogationRequestToSelf", propogationNameTag, propogationData)
end
local function onFlushPropogationCache(propogationCacheLookupTable)
for propogationNameTag, propogationData in pairs(propogationCacheLookupTable) do
onPropogateCacheDataRequestToClientReceived(propogationNameTag, propogationData)
end
end
local function __impFlushPropogationCache()
local propogationCacheLookupTable = network:invokeServer("getPropogationCacheLookupTable")
onFlushPropogationCache(propogationCacheLookupTable)
end
-- todo: client spam limit?
function module:flushPropogationCache()
__impFlushPropogationCache()
end
function module:getByPropogationNameTag(propogationNameTag)
if cache[propogationNameTag] then
if type(cache[propogationNameTag]) == "table" then
return utilities.copyTable(cache[propogationNameTag])
else
return cache[propogationNameTag]
end
end
return nil
end
function module.init(Modules)
network = Modules.network
utilities = Modules.utilities
network:create("propogationRequestToSelf", "BindableEvent")
network:create("propogationRequestReceived", "BindableEvent")
network:connect("propogateCacheDataRequest", "OnClientEvent", onPropogateCacheDataRequestToClientReceived)
network:connect("clientFlushPropogationCache", "OnClientEvent", onFlushPropogationCache)
spawn(function()
module:flushPropogationCache()
end)
network:create("getLocalPlayerDataCache", "BindableFunction", "OnInvoke", function()
return cache
end)
network:create("getCacheValueByNameTag", "BindableFunction", "OnInvoke", function(nameTag)
return module:getByPropogationNameTag(nameTag)
end)
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/resetButtonCallback.lua
================================================
local module = {}
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
network = modules.load("network")
local resetBind = Instance.new("BindableEvent")
resetBind.Event:connect(function()
local success = true
if not game.ReplicatedStorage:FindFirstChild("safeZone") then
success = network:invoke("promptActionFullscreen","Are you sure you wish to kill your character?")
end
if success then
network:invokeServer("playerRequest_respawnMyCharacter")
end
end)
game.StarterGui:SetCore("ResetButtonCallback",resetBind)
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/resources.lua
================================================
-- Resources
-- Rocky28447
-- June 2, 2020
local Resources = {}
local CollectionService = game:GetService("CollectionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Modules
local Thread
local network
local function getNodeTypeMetadataFromNode(node)
local containingFolder = node:FindFirstAncestorWhichIsA("Folder")
local isNodeGroup = CollectionService:HasTag(containingFolder, "resourceNodeGroupFolder")
local nodeTypeMetadata = isNodeGroup and containingFolder.Parent.Metadata or containingFolder.Metadata
return nodeTypeMetadata
end
function Resources:DoEffect(node, effect)
local nodeMetadata = getNodeTypeMetadataFromNode(node)
local effectFolder = nodeMetadata.EffectsStorage:FindFirstChild(effect)
if effectFolder then
for _, effect in pairs (effectFolder:GetChildren()) do
local effectClone = effect:Clone()
effectClone.Parent = node.PrimaryPart
if effectClone:IsA("Sound") then
effectClone:Play()
elseif effectClone:IsA("ParticleEmitter") then
effectClone:Emit(effectClone.Rate)
end
Thread.Delay(10, function()
effectClone:Destroy()
end)
end
end
end
function Resources:NodeReplenished(node)
local nodeMetadata = require(getNodeTypeMetadataFromNode(node))
local onReplenish = nodeMetadata.Animations.OnReplenish
if nodeMetadata.DestroyOnDeplete then
for _, c in pairs (node:GetDescendants()) do
if c:IsA("BasePart") then
c.Transparency = 0
c.CanCollide = true
end
end
else
if node:FindFirstChild("DropPoints") then
for _, dropPoint in pairs (node.DropPoints:GetChildren()) do
dropPoint.Value.Transparency = 0
end
end
end
if onReplenish and type(onReplenish) == "function" then
onReplenish(node)
else
self:DoEffect(node, "Replenish")
end
CollectionService:AddTag(node.PrimaryPart, "attackable")
end
function Resources:NodeDepleted(node)
local nodeMetadata = require(getNodeTypeMetadataFromNode(node))
local onDeplete = nodeMetadata.Animations.OnDeplete
if nodeMetadata.DestroyOnDeplete then
for _, c in pairs (node:GetDescendants()) do
if c:IsA("BasePart") then
c.Transparency = 1
c.CanCollide = false
end
end
if not onDeplete or type(onDeplete) ~= "function" then
self:DoEffect(node, "Deplete")
end
end
if onDeplete and type(onDeplete) == "function" then
onDeplete(node)
end
CollectionService:RemoveTag(node.PrimaryPart, "attackable")
end
function Resources:Start()
network:connect("resourceHarvested", "OnClientEvent", function(node, dropPoint)
local nodeMetadata = require(getNodeTypeMetadataFromNode(node))
local onHarvest = nodeMetadata.Animations.OnHarvest
if onHarvest and type(onHarvest) == "function" then
onHarvest(node, dropPoint)
else
self:DoEffect(node, "Harvest")
end
if dropPoint then
dropPoint.Transparency = 1
dropPoint.CanCollide = false
end
end)
network:connect("resourceReplenished", "OnClientEvent", function(node)
self:NodeReplenished(node)
end)
network:connect("resourceDepleted", "OnClientEvent", function(node)
self:NodeDepleted(node)
end)
local depletedNodes = network:invokeServer("getDepletedResourceNodes")
for _, node in pairs(depletedNodes) do
self:NodeDepleted(node)
end
end
function Resources:Init()
Modules = require(ReplicatedStorage.modules)
Thread = Modules.load("thread")
network = Modules.load("network")
end
Resources:Init()
Resources:Start()
return Resources
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/sitting.lua
================================================
local module = {}
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local currentPlayerSeatPart = nil
local player = game.Players.LocalPlayer
local function seatPlayer(seatPart)
if not player or not player.Character or not player.Character.PrimaryPart then return false end
-- sit the player in the proper position
player.Character.PrimaryPart.CFrame = script.Parent.CFrame + Vector3.new(0, 0.5, 0)
player.Character.PrimaryPart.Anchored = true
-- change the state to isSitting, isSitting is authoritative
network:invoke("setCharacterMovementState", "isSitting", true, script.Parent)
currentPlayerSeatPart = seatPart
-- success!
return true
end
local function unseatPlayer()
end
local function isPlayerSitting()
end
local function getPlayerSeat()
end
local function main()
network:create("seatPlayer", "BindableFunction", "OnInvoke", seatPlayer)
network:create("unseatPlayer", "BindableFunction", "OnInvoke", unseatPlayer)
network:create("getPlayerSeat", "BindableFunction", "OnInvoke", getPlayerSeat)
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/teleportation.client.lua
================================================
local teleService = game:GetService("TeleportService")
local sessionId = teleService:GetTeleportSetting("sessionId")
local joinTime = teleService:GetTeleportSetting("joinTime")
local telePartyInfo = teleService:GetTeleportSetting("partyInfo")
local teleportUITemplate = game.ReplicatedStorage:WaitForChild("teleportUI")
local teleportUITemplate_death = game.ReplicatedStorage:WaitForChild("teleportUIDeath")
local currentTeleportUI
local Player = game.Players.LocalPlayer
spawn(function()
local existingTeleportUI = teleService:GetArrivingTeleportGui()
if existingTeleportUI then
existingTeleportUI.Status.Text = "Arriving..."
repeat wait() until Player:FindFirstChild("PlayerGui")
existingTeleportUI.Parent = Player.PlayerGui
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local tween = modules.load("tween")
Player:WaitForChild("DataLoaded")
tween(existingTeleportUI.Blackout,{"BackgroundTransparency"},1,1)
tween(existingTeleportUI.logo,{"ImageTransparency"},1,1)
tween(existingTeleportUI.Description,{"TextTransparency","TextStrokeTransparency"},1,1)
tween(existingTeleportUI.Destination,{"TextTransparency","TextStrokeTransparency"},1,1)
tween(existingTeleportUI.Status,{"TextTransparency","TextStrokeTransparency"},1,1)
wait(1)
existingTeleportUI:Destroy()
end
game.ContentProvider:PreloadAsync({teleportUITemplate:WaitForChild("swoosh")})
game.ContentProvider:PreloadAsync({teleportUITemplate:WaitForChild("gradient")})
end)
local teleporting = false
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local tween = modules.load("tween")
local utilities = modules.load("utilities")
network:create("clientRequestingTeleport","BindableEvent")
network:create("forceSpawn","BindableEvent")
Player.CharacterAdded:Connect(function()
wait()
network:fire("forceSpawn")
end)
teleService.TeleportInitFailed:connect(function(player, teleportResult, errorMessage)
if player == game.Players.LocalPlayer then
currentTeleportUI = teleService:GetArrivingTeleportGui() or currentTeleportUI
if currentTeleportUI then
currentTeleportUI.Status.Text = "Teleport failed! ("..teleportResult.Name..")"
wait(1.5)
currentTeleportUI:Destroy()
teleporting = false
end
end
end)
local preppingTeleportUI
local function prepTeleportUI(destination, teleportType)
-- map to non-demo version and display that info
local placeIdMapping = utilities.placeIdMapping
for placeIdString, mappingId in pairs(placeIdMapping) do
if destination == mappingId then
destination = tonumber(placeIdString)
break
end
end
if preppingTeleportUI then
return false
end
preppingTeleportUI = true
if teleportType == "death" then
local teleportUI = teleportUITemplate_death:Clone()
teleportUI.Parent = Player.PlayerGui
teleportUI.Enabled = true
teleportUI.Blackout.Visible = true
teleportUI.Blackout.BackgroundTransparency = 1
tween(teleportUI.Blackout, {"BackgroundTransparency"}, 0, 0.5)
teleService:SetTeleportGui(teleportUITemplate_death)
wait(0.5)
spawn(function()
wait(1)
preppingTeleportUI = false
end)
return teleportUI
end
teleportUITemplate.Thumbnail.Image = "https://www.roblox.com/Thumbs/Asset.ashx?width=768&height=432&assetId="..destination
local teleportUI = nil
if not teleportUI then
teleportUI = teleportUITemplate:Clone()
currentTeleportUI = teleportUI
teleportUI.Blackout.BackgroundTransparency = 0
teleportUI.Blackout.Position = UDim2.new(-1,0,0.5,0)
teleportUI.Thumbnail.ImageTransparency = 1
teleportUI.gradient.ImageTransparency = 1
teleportUI.logo.ImageTransparency = 1
teleportUI.swoosh:Play()
teleportUI.Description.TextTransparency = 1
teleportUI.Destination.TextTransparency = 1
teleportUI.Status.TextTransparency = 1
tween(teleportUI.Blackout,{"Position"},UDim2.new(0,0,0.5,0),0.3)
end
teleportUI.Parent = Player.PlayerGui
teleportUI.Enabled = true
teleService:SetTeleportGui(teleportUITemplate)
local runService = game:GetService("RunService")
teleportUI.spinner.Visible = true
spawn(function()
while teleporting do
teleportUI.spinner.Rotation = teleportUI.spinner.Rotation + 2
runService.RenderStepped:wait()
end
end)
local preloadDone
local infoDone
spawn(function()
teleportUI.Status.Text = "Loading..."
wait(0.3)
tween(teleportUI.Status,{"TextTransparency"},0,0.5)
end)
spawn(function()
game.ContentProvider:PreloadAsync({teleportUI.logo, teleportUI.Thumbnail})
preloadDone = true
end)
local info
spawn(function()
info = game.MarketplaceService:GetProductInfo(destination,Enum.InfoType.Asset )
infoDone = true
end)
local start = tick()
repeat wait() until preloadDone and infoDone or (tick() - start > 10)
local dif = tick() - start
if dif < 0.3 then
wait(0.3 - dif)
end
teleportUI.gradient.ImageTransparency = 0
teleportUI.Thumbnail.UIScale.Scale = 1
tween(teleportUI.logo,{"ImageTransparency"},0,0.7)
tween(teleportUI.Thumbnail,{"ImageTransparency"},0,1)
tween(teleportUI.Thumbnail.UIScale,{"Scale"}, 1.05, 1)
tween(teleportUI.Description,{"TextTransparency"},0,0.5)
tween(teleportUI.Destination,{"TextTransparency"},0,0.5)
if info then
teleportUI.Destination.Text = info.Name
teleportUI.Description.Text = info.Description
teleportUI.Status.Text = "Preparing to travel..."
teleportUITemplate.Destination.Text = info.Name
teleportUITemplate.Description.Text = info.Description
teleportUITemplate.Status.Text = "Traveling to location..."
teleService:SetTeleportGui(teleportUITemplate)
else
teleportUI.Status.Text = "Can't find travel info"
teleportUITemplate.Status.Text = "Can't find travel info"
end
spawn(function()
wait(1)
preppingTeleportUI = false
end)
return teleportUI
end
network:connect("signal_teleport", "OnClientEvent", prepTeleportUI)
local function teleportTo(placeId)
local Player = game.Players.LocalPlayer
local teleService = game:GetService("TeleportService")
if Player:FindFirstChild("AnalyticsSessionId") then
teleService:SetTeleportSetting("sessionId",Player.AnalyticsSessionId.Value)
end
if Player:FindFirstChild("JoinTime") then
teleService:SetTeleportSetting("joinTime",Player.JoinTime.Value)
end
local teleportUI = prepTeleportUI(placeId)
wait(0.5)
teleportUI.Status.Text = "Traveling to location..."
wait(0.5)
teleportUI.Body.Position = UDim2.new(0,0,0,0)
teleportUI.Blackout.BackgroundTransparency = 0
game.GuiService.SelectedObject = nil
teleService:Teleport(placeId,nil,nil,teleportUI)
end
network:create("teleportPlayerTo","BindableFunction","OnInvoke",teleportTo)
local currentPartyInfo
local function updateTeleportPart(teleportPart)
if currentPartyInfo then
teleportPart.CanCollide = true
if not game.CollectionService:HasTag(teleportPart, "interact") then
game.CollectionService:AddTag(teleportPart, "interact")
end
else
teleportPart.CanCollide = false
if game.CollectionService:HasTag(teleportPart, "interact") then
game.CollectionService:RemoveTag(teleportPart, "interact")
end
end
end
local function prepDataForTeleport(destination)
local teleportUI = prepTeleportUI(destination)
local starttime = os.time()
local analyticsSessionId
if Player:FindFirstChild("AnalyticsSessionId") then
analyticsSessionId = Player.AnalyticsSessionId.Value
end
local joinTime
if Player:FindFirstChild("JoinTime") then
joinTime = Player.JoinTime.Value
end
local dataSlot = Player:FindFirstChild("dataSlot") and Player.dataSlot.Value or 1
local TimeStamp = network:invokeServer("saveDataForTeleportation")
if TimeStamp then
if analyticsSessionId then
teleService:SetTeleportSetting("sessionId",analyticsSessionId)
end
if joinTime then
teleService:SetTeleportSetting("joinTime",joinTime)
end
teleService:SetTeleportSetting("lastTimeStamp",TimeStamp)
teleService:SetTeleportSetting("arrivingTeleportId", game.PlaceId)
teleService:SetTeleportSetting("dataSlot",dataSlot)
teleportUI.Status.Text = "Traveling to location..."
local difference = os.time() - starttime
if difference <= 1 then
wait(1-difference)
end
teleportUI.Blackout.BackgroundTransparency = 0
game.GuiService.SelectedObject = nil
wait()
end
return TimeStamp, teleportUI
end
local function externalTP(destination)
if Player:FindFirstChild("DataLoaded") == nil or Player:FindFirstChild("teleporting") or teleporting then
return false
end
teleporting = true
local Timestamp, teleportUI = prepDataForTeleport(destination)
if Timestamp then
teleService:Teleport(destination,nil,nil,teleportUI)
else
-- failed to save data, abort teleportation
end
end
network:connect("externalTeleport", "OnClientEvent", externalTP)
network:create("localPrepareForTeleport", "BindableFunction", "OnInvoke", function(destination)
teleporting = true
local teleportUI = prepTeleportUI(destination)
end)
local function activate(teleportPart)
updateTeleportPart(teleportPart)
local destination = teleportPart:WaitForChild("teleportDestination").Value
local db = false
teleportPart.Touched:connect(function(hit)
if Player:FindFirstChild("DataLoaded") == nil or Player:FindFirstChild("teleporting") then
return false
end
if Player.Character and hit == Player.Character.PrimaryPart and (Player.Character.PrimaryPart.Position - teleportPart.Position).magnitude < 50 then
if db then
return false
end
local inParty = currentPartyInfo ~= nil
local isForced = teleportPart:FindFirstChild("forced") and teleportPart.forced.Value
if inParty and (not isForced) then
network:fire("applyJoltVelocityToCharacter", teleportPart.CFrame.lookVector * 5)
return
end
if not teleporting then
teleporting = true
local teleportUI = prepTeleportUI(destination)
local success, fail = network:invokeServer("playerRequest_useTeleporter", teleportPart)
if success then
else
teleportUI:Destroy()
teleporting = false
end
end
end
end)
end
local parts = game.CollectionService:GetTagged("teleportPart")
game.CollectionService:GetInstanceAddedSignal("teleportPart"):connect(activate)
for i,part in pairs(parts) do
spawn(function()
activate(part)
end)
end
game.ReplicatedStorage.ChildAdded:Connect(function(Child)
if Child.Name == "spawnPoints" then
network:fire("forceSpawn")
end
end)
local function getPartyLeaderUserId(partyInfo)
for i,member in pairs(partyInfo.members) do
local player = member.player
if player and member.isLeader then
return player.userId
end
end
end
local function updatePartyInfo(partyInfo)
partyInfo = partyInfo or network:invokeServer("playerRequest_getMyPartyData")
currentPartyInfo = partyInfo
for i,teleportPart in pairs(game.CollectionService:GetTagged("teleportPart")) do
updateTeleportPart(teleportPart)
end
if partyInfo and partyInfo.teleportState == "teleporting" and not teleporting then
network:invoke("setCharacterArrested",true)
local partyTeleportInfo = {party_guid = partyInfo.guid; partyLeaderUserId = getPartyLeaderUserId(partyInfo)}
teleService:SetTeleportSetting("partyInfo", partyTeleportInfo)
if not currentPartyInfo.teleportDestination then
error("AHHHHHH PANIC NO TELEPORT DESTINATION WHY HAVE YOU FORSAKEN ME LIKE THIS")
end
teleporting = true
local teleportUI = prepTeleportUI(currentPartyInfo.teleportDestination)
network:fireServer("signal_playerReadyToGroupTeleport")
--[[
local Timestamp, teleportUI = prepDataForTeleport(currentPartyInfo.teleportDestination)
if Timestamp then
else
-- failed to save data, abort teleportation
end
]]
end
end
updatePartyInfo()
network:connect("signal_myPartyDataChanged", "OnClientEvent", updatePartyInfo)
-- super hacky but so are humanoids so sue me
for i=1,3 do
wait()
network:fire("forceSpawn")
end
-- wait for data to report analytics
Player:WaitForChild("DataLoaded", 60)
-- Begin analytics setup
if sessionId and joinTime then
-- network:invokeServer("requestContinueSession",sessionId,joinTime)
else
-- network:invokeServer("requestNewSession")
end
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/contents/treasureChests.client.lua
================================================
-- Author: berezaa
-- Handles chest opening and makes previously-accessed chests already open
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local tween = modules.load("tween")
local utilities = modules.load("utilities")
game.Players.LocalPlayer:WaitForChild("dataLoaded", 60)
local treasureChests = {}
local billboards = {}
local assetsFolder = replicatedStorage:WaitForChild("assets")
local assetFolder = script.Parent.Parent:WaitForChild("assets")
local progressUi = assetFolder:WaitForChild("chestBillboard")
local function addBillboardToChest(treasureChest)
if treasureChest:FindFirstChild("progressUi") == nil then
local ui = progressUi:Clone()
ui.ImageLabel.ImageTransparency = 1
ui.TextLabel.TextTransparency = 1
ui.TextLabel.TextStrokeTransparency = 1
ui.Adornee = treasureChest.PrimaryPart
local totalChests = 0
local openedChests = 0
for _, chestInfo in pairs(treasureChests) do
totalChests = totalChests + 1
if chestInfo.open then
openedChests = openedChests + 1
end
end
ui.TextLabel.Text = tostring(openedChests) .. "/" .. tostring(totalChests)
ui.Enabled = true
ui.Parent = treasureChest
tween(ui.ImageLabel, {"ImageTransparency"}, 0, 1)
tween(ui.TextLabel, {"TextTransparency","TextStrokeTransparency"}, 0, 1)
table.insert(billboards, ui)
end
end
local INTERVAL = 30 * 60 * 24
local function getTime()
-- 7AM/PM PT, 10AM/PM ET
return os.time() - INTERVAL / 12
end
local playerTreasureData = network:invoke("getCacheValueByNameTag", "treasure")
network:connect("propogationRequestToSelf", "Event", function(key, data)
if key == "treasure" then
playerTreasureData = data
end
end)
-- create model for chest and add it to world, hide bounding box
local function registerTreasureChest(chestRoot)
local isOldStyle = game.CollectionService:HasTag(chestRoot.PrimaryPart, "interact")
local chestPropsModule = chestRoot:FindFirstChild("chestProps") or assetsFolder.defaultChestProps
local chestProps = require(chestPropsModule)
local defaultChestProps = require(assetsFolder.defaultChestProps)
-- wierd assignment here. It's getting the chest model from the chest props, or falling back on default if undefined
-- this should probably be cleaned up but it's 12:50AM and im tired
-- ~ nimblz
local chestModel = assetsFolder.chests:FindFirstChild(
chestProps.chestModel or defaultChestProps.chestModel
) or assetsFolder.chests:FindFirstChild("defaultChest")
if isOldStyle then
chestModel = chestRoot
else
chestModel = chestModel:Clone()
chestModel.Parent = chestRoot
end
local animationController = chestModel:WaitForChild("AnimationController")
local chestOpenAnimation = chestModel:WaitForChild("chestOpen")
local chestOpenLoopAnimation = chestModel:WaitForChild("chestOpenLoop")
local chestLockedTrack = Instance.new("Animation", chestModel)
chestLockedTrack.Name = "chestLocked"
chestLockedTrack.AnimationId = "rbxassetid://3916391981"
local openTrack = animationController:LoadAnimation(chestOpenAnimation);
local openLoopTrack = animationController:LoadAnimation(chestOpenLoopAnimation);
local lockedTrack = animationController:LoadAnimation(chestLockedTrack)
openTrack.Looped = false
openTrack.Priority = Enum.AnimationPriority.Action
lockedTrack.Looped = false
lockedTrack.Priority = Enum.AnimationPriority.Action
openLoopTrack.Looped = true
openLoopTrack.Priority = Enum.AnimationPriority.Core
local chestRootPart = chestRoot.PrimaryPart or chestRoot:WaitForChild("RootPart")
local chestModelRootPart = chestModel.PrimaryPart
chestModel:SetPrimaryPartCFrame(chestRootPart.CFrame * CFrame.new(0, (-chestRootPart.Size.Y/2) + (chestModelRootPart.Size.Y/2), 0))
chestRootPart.Transparency = 1
if not isOldStyle then
local attackScript = assetFolder.attackableScript:Clone()
attackScript.Parent = chestModelRootPart
game.CollectionService:AddTag(chestModelRootPart, "attackable")
end
local chest = {
chestRoot = chestRoot;
chestModel = chestModel;
controller = animationController;
openTrack = openTrack;
lockedTrack = lockedTrack;
openLoopTrack = openLoopTrack;
open = false;
}
treasureChests[chestRoot.Name] = chest
local today = math.floor(getTime() / INTERVAL)
local chestData = playerTreasureData["place-"..game.PlaceId].chests[chestModel.Name]
local specialContents = chestModel:FindFirstChild("inventory") or chestModel:FindFirstChild("ironChest") or chestModel:FindFirstChild("goldChest")
local chestOpen
if specialContents then
chestOpen = chestData and chestData.open
else
chestOpen = chestData and (chestData.open >= today)
end
if chestOpen then
chest.openLoopTrack:Play()
chest.open = true
if chestModel:FindFirstChild("Glow") then
chestModel.Glow.Transparency = 1
end
end
end
for i,treasureChest in pairs(game.CollectionService:GetTagged("treasureChest")) do
coroutine.wrap(function() registerTreasureChest(treasureChest) end)()
end
game.CollectionService:GetInstanceAddedSignal("treasureChest"):connect(registerTreasureChest)
local function openTreasureChest(treasureChest)
local chestInfo = treasureChests[treasureChest.Name]
if chestInfo and not chestInfo.open then
local chestModel = chestInfo.chestModel
local glow = chestModel:FindFirstChild("Glow")
utilities.playSound("chest_unlock", chestModel.PrimaryPart)
local lock = chestModel:FindFirstChild("Lock")
if lock then
tween(lock, {"Transparency"}, 1, 1)
end
local rewards, status = network:invokeServer("playerRequest_openTreasureChest", treasureChest)
if status then
return nil, status
end
chestInfo.open = true
local track
if rewards then
track = chestInfo.openTrack
else
track = chestInfo.lockedTrack
end
if rewards then
track:Play()
track.KeyframeReached:connect(function(key)
if key == "opened" then
chestInfo.openLoopTrack:Play()
if glow then
glow.Transparency = 0
tween(glow,{"Transparency"},1,1)
if glow:FindFirstChild("ParticleEmitter") then
glow.ParticleEmitter.Color = ColorSequence.new(glow.Color)
glow.ParticleEmitter:Emit(50)
end
end
utilities.playSound("chest_reward", glow)
end
end)
-- addBillboardToChest(chestModel)
elseif status and typeof(status) == "number" then
glow.Transparency = 1
track:Play()
track.KeyframeReached:connect(function(key)
if key == "opened" then
chestInfo.openLoopTrack:Play()
end
end)
if script.Parent.Parent:FindFirstChild("goldChest") or script.Parent.Parent:FindFirstChild("ironChest") then
local alert = {
text = "You've already opened this chest.";
textColor3 = Color3.new(1,1,1);
backgroundColor3 = Color3.new(0.9,0.3,0.2);
backgroundTransparency = 0;
textStrokeTransparency = 1;
id = "goldchest"..script.Parent.Parent.Name;
}
-- Modules.notifications.alert(alert, 3)
network:fire("alert", {text = alert}, 2)
else
for i=0,2 do
local alert = {
text = "Chest can be opened again in " .. utilities.timeToString(status - i);
textColor3 = Color3.new(1,1,1);
backgroundColor3 = Color3.new(0.9,0.75,0.2);
backgroundTransparency = 0;
textStrokeTransparency = 1;
id = "chest"..script.Parent.Parent.Name;
}
-- Modules.notifications.alert(alert, 3)
network:fire("alert", {text = alert}, 1)
wait(1)
end
local alert = {
text = "Chest can be opened again in " .. utilities.timeToString(status - 3);
textColor3 = Color3.new(1,1,1);
backgroundColor3 = Color3.new(0.9,0.75,0.2);
backgroundTransparency = 0;
textStrokeTransparency = 1;
id = "chest"..script.Parent.Parent.Name;
}
-- Modules.notifications.alert(alert, 3)
network:fire("alert", alert, 0.5)
end
end
return rewards, status
end
return nil, "You cant open that."
end
network:create("openTreasureChest_client", "BindableFunction", "OnInvoke", openTreasureChest)
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/modules/AeroProxy.lua
================================================
-- Aero Proxy
-- Rocky28447
-- June 27, 2020
--[[
A proxy module to allow main menu Aero code to interface with
the obfuscated network module. Only include functions that the
Aero-side will need.
AeroProxy.getGameSaveData()
AeroProxy.renderCharacter(mask, appearanceData)
]]
local AeroProxy = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Modules = require((ReplicatedStorage.modules))
local network = Modules.load("network")
function AeroProxy.getGameSaveData(fileNum)
local globalDataSuccess, globalDataFromServer = network:invokeServer("loadGame")
return globalDataFromServer
end
function AeroProxy.renderCharacter(mask, appearanceData, player)
return network:invoke("createRenderCharacterContainerFromCharacterAppearanceData", mask, appearanceData, player)
end
function AeroProxy.getMovementAnimationForCharacter(character, animation)
local animationController = character.entity:WaitForChild("AnimationController")
local currentEquipment = network:invoke("getCurrentlyEquippedForRenderCharacter", character.entity)
local weaponType do
if currentEquipment[1] then
weaponType = currentEquipment[1].baseData.equipmentType
end
end
return network:invoke("getMovementAnimationForCharacter", animationController, animation, weaponType, nil)
end
return AeroProxy
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/modules/cannonScriptLocal.lua
================================================
local module = {}
module.isActive = false
module.interactPrompt = "FIRE!" -- prompt text
module.instant = true
local player = game.Players.LocalPlayer
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local utilities = modules.load("utilities")
local tween = modules.load("tween")
function module.init()
network:invoke("setCharacterArrested", true, script.Parent.Parent.target.CFrame * CFrame.Angles(-math.pi/2, 0, 0))
-- ssss sounds
network:fireServer("signal_playerHasDecidedThatTheyWantToUseTheCannon", script.Parent.Parent)
end
network:connect("signal_playerReadyToBeBOOMEDByTheCannon", "OnClientEvent", function(cannon)
if cannon == script.Parent.Parent then
network:invoke("setCharacterArrested", false)
local cannonStrength = 280
if script.Parent:FindFirstChild("strength") then
cannonStrength = script.Parent.strength.Value
end
network:fire("applyJoltVelocityToCharacter", script.Parent.Parent.target.CFrame.LookVector * cannonStrength)
end
end)
function module.close()
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/modules/doorScript.lua
================================================
local module = {}
module.isActive = false
if script.Parent.Name == "exit" then
module.interactPrompt = "Exit"
else
module.interactPrompt = "Enter"
end
-- prompt text
module.instant = true
local player = game.Players.LocalPlayer
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local utilities = modules.load("utilities")
local function getTargetDoor()
for i,door in pairs(game.CollectionService:GetTagged("door")) do
if door ~= script.Parent and door.Parent and door.Parent.Name == script.Parent.Parent.Name then
return door
end
end
end
function module.init()
local target = getTargetDoor()
if target and player.Character and player.Character.PrimaryPart then
if game.ReplicatedStorage.assets.sounds:FindFirstChild("door") then
utilities.playSound("door", player.Character.PrimaryPart)
end
player.Character:SetPrimaryPartCFrame(player.Character.PrimaryPart.CFrame - player.Character.PrimaryPart.Position + target.Position + target.CFrame.lookVector * 8)
end
end
function module.close()
end
return module
--
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/modules/escapeRope.lua
================================================
local module = {}
module.isActive = false
module.interactPrompt = "ESCAPE"
-- prompt text
module.instant = true
local player = game.Players.LocalPlayer
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local function getTargetDoor()
return script.Parent.Parent.Target
end
function module.init()
local target = getTargetDoor()
if target and player.Character and player.Character.PrimaryPart then
if game.ReplicatedStorage.assets.sounds:FindFirstChild("ladder") then
game.ReplicatedStorage.assets.sounds.ladder:Play()
end
--player.Character:SetPrimaryPartCFrame(player.Character.PrimaryPart.CFrame - player.Character.PrimaryPart.Position + target.Position)
network:fireServer("playerRequest_activateEscapeRope", script.Parent)
end
end
function module.close()
end
return module
--
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/modules/firepitLocal.lua
================================================
local module = {}
module.isActive = false
module.interactPrompt = "Ignite" -- prompt text
module.instant = true
local player = game.Players.LocalPlayer
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
function module.init()
network:fireServer("igniteFirePit",script.Parent)
end
function module.close()
end
return module
--
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/modules/loadUiControl.lua
================================================
return function()
spawn(function()
script.Parent:WaitForChild("blackout")
script.Parent.blackout.Visible = true
script.Parent.blackout.BackgroundTransparency = 0
end)
local customizeTable
local runService = game:GetService("RunService")
local loading = true
local loadingassets = true
--[[
spawn(function()
script.Parent:WaitForChild("spinner")
while loading do
script.Parent.spinner.Rotation = script.Parent.spinner.Rotation + 2
runService.RenderStepped:wait()
end
script.Parent.spinner.Visible = false
end)
]]
local assets = game.ReplicatedStorage:WaitForChild("assets")
local function loadassets()
local contentProvider = game:GetService("ContentProvider")
local teleService = game:GetService("TeleportService")
-- no need to load assets if you are arriving from a teleport
local arrivingFrom = teleService:GetTeleportSetting("arrivingTeleportId")
if arrivingFrom and (arrivingFrom ~= 2015602902 and arrivingFrom ~= 2376885433) then
return false
end
local contentList = {}
table.insert(contentList, game.ReplicatedStorage:WaitForChild("characterAnimations"))
table.insert(contentList, assets:WaitForChild("sounds"))
table.insert(contentList, game:GetService("StarterGui"))
-- table.insert(contentList, game.ReplicatedStorage:WaitForChild("itemData"))
-- table.insert(contentList, game.ReplicatedStorage:WaitForChild("abilityLookup"))
table.insert(contentList, assets:WaitForChild("accessories"))
spawn(function()
script.Parent.spinner.Visible = true
local maxQueueSize = contentProvider.RequestQueueSize
while loadingassets do
local queueSize = contentProvider.RequestQueueSize
if queueSize > maxQueueSize then
maxQueueSize = queueSize
end
local loadedAssetCount = maxQueueSize - queueSize
-- contents.value.Text = tostring(loadedAssetCount) .. "/" .. tostring(maxQueueSize)
script.Parent.spinner.Rotation = script.Parent.spinner.Rotation + 2
runService.RenderStepped:wait()
end
end)
-- make sure the loading UI is loaded in
contentProvider:PreloadAsync({script.Parent})
contentProvider:PreloadAsync(contentList)
script.Parent.spinner.Image = "rbxgameasset://accept"
script.Parent.spinner.ImageColor3 = Color3.fromRGB(132, 255, 98)
script.Parent.spinner.Rotation = 0
script.Parent.blackout.BorderColor3 = Color3.fromRGB(132, 255, 98)
loadingassets = false
end
wait()
local player = game.Players.LocalPlayer
local rank = player:GetRankInGroup(4238824)
local isAdmin = true--rank >=5 or player:IsInGroup(5018342)
local isLegend = rank >= 2
if not isAdmin then
script.Parent.menustatus.Text = "The game is currently closed. Please come back another time."
return false
end
script.Parent.blackout.TextLabel.Visible = false
spawn(loadassets)
script.Parent.menustatus.Text = "Loading game files..."
local replicatedStorage = game:GetService("ReplicatedStorage")
local modules = require(replicatedStorage.modules)
local network = modules.load("network")
local levels = modules.load("levels")
local tween = modules.load("tween")
local configuration = modules.load("configuration")
local utilities = modules.load("utilities")
local money = require(script.Parent.money)
local deathScreen = require(script.Parent.deathScreen.deathScreen)
deathScreen.init({network = network, levels = levels, tween = tween, utilities = utilities, money = money })
--local isAdmin = game.Players.LocalPlayer:GetRankInGroup(4238824) >=3 or game.Players.LocalPlayer:IsInGroup(5018342)
local isAdmin = true--game.Players.LocalPlayer:GetRankInGroup(4238824) >=5
if not isAdmin then
wait(5)
script.Parent.main.Visible = false
script.Parent.landing.Visible = false
script.Parent.leftBar.Visible = false
wait(1)
tween(script.Parent.blackout, {"BackgroundTransparency"},{1},3 )
script.Parent.menustatus.Text = "The game is currently closed. Please check back later."
return false
end
script.Parent.menustatus.Text = "Loading data from server..."
local globalDataSuccess, globalDataFromServer = network:invokeServer("loadGame")
local globalData
if globalDataSuccess then
globalData = globalDataFromServer
else
script.Parent.menustatus.Text = "Failed to load data."
wait(3)
end
loading = false
script.Parent.Notice.Visible = false
script.Parent.menustatus.Visible = false
script.Parent.blackout.Visible = true
script.Parent.blackout.TextLabel.Visible = false
--script.Parent.Enabled = true
script.Parent.landing.Visible = false
script.Parent.customize.Visible = false
script.Parent.main.Visible = false
script.Parent.onlineFriends.Visible = false
script.Parent.main.Frame.PlayButton.Visible = false
local input = require(script.Parent:WaitForChild("input"))
if input.mode.Value == "xbox" or game:GetService("UserInputService").GamepadEnabled then
game.GuiService.GuiNavigationEnabled = true
game.GuiService.AutoSelectGuiEnabled = true
game.GuiService.SelectedObject = script.Parent.landing.play
end
local blur
local mainCharacter
-- scan for friends every minute
spawn(function()
local friendsTag = {}
while true do
--print("!!getting friends")
local friendInfo
local success, fail = pcall(function()
friendInfo = game.Players.LocalPlayer:GetFriendsOnline()
end)
if success then
--print("!!starting Handshake")
local friendsInfo = network:invokeServer("fetchPlayerFriendsInfo", friendInfo)
--print("!!finished handshake",#friendsInfo)
if friendsInfo then
for i,tag in pairs(friendsTag) do
if tag then
tag:Destroy()
end
end
friendsTag = {}
for i,friend in pairs(friendsInfo) do
local tag = script.Parent.onlineFriends.SampleFriend:Clone()
tag.username.Text = friend.UserName
tag.location.Text = friend.LastLocation
tag.thumbnail.Image = "https://www.roblox.com/headshot-thumbnail/image?userId=".. friend.VisitorId .."&width=100&height=100&format=png"
tag.Parent = script.Parent.onlineFriends
tag.Visible = true
table.insert(friendsTag,tag)
end
script.Parent.onlineFriends.title.Visible = false
if #friendsTag > 0 then
script.Parent.onlineFriends.title.Visible = true
end
end
--print("!!Finished logic")
wait(30)
else
warn("GetOnlineFriends failed!")
wait(10)
end
end
end)
function noticeskip()
script.Parent.Notice.Visible = false
script.Parent.main.Visible = true
spawn(function()
local serverMessage = require(script.Parent.main.serverMessage.serverMessage)
serverMessage.init()
end)
script.Parent.onlineFriends.Visible = true
if input.mode.Value == "xbox" then
game.GuiService.GuiNavigationEnabled = true
game.GuiService.SelectedObject = input.getBestButton(script.Parent.main.Frame)
end
-- network:invoke("lockCameraPosition",script.Parent.CameraMainPos.Value,0.5)
-- if blur then
-- tween(blur, {"Size"}, 0, 0.5)
-- end
end
function land()
script.Parent.landing.Visible = false
game.StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.Chat, true)
game.StarterGui:SetCore("ChatBarDisabled", false)
game.StarterGui:SetCore("ChatActive", true)
game.StarterGui:SetCore("ChatWindowPosition", UDim2.new(0.7,-10,0.7,-30))
script.Parent.Notice.Visible = true
noticeskip()
if input.mode.Value == "xbox" then
game.GuiService.GuiNavigationEnabled = true
game.GuiService.SelectedObject = input.getBestButton(script.Parent.Notice)
end
end
land()
script.Parent.Notice.ok.MouseButton1Click:connect(noticeskip)
game.Players.LocalPlayer.PlayerGui:SetTopbarTransparency(1)
--game:GetService("StarterGui"):SetCore("TopbarEnabled", false)
local function updateInputMode()
if input.mode.Value == "xbox" then
script.Parent.customize.buttons.UIListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
script.Parent.customize.shirtColor.UIGridLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
script.Parent.customize.hairColor.UIGridLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
script.Parent.customize.hairColor.Position = UDim2.new(0.5,0,0,90)
script.Parent.customize.hairColor.AnchorPoint = Vector2.new(0.5,0)
script.Parent.customize.shirtColor.Position = UDim2.new(0.5,0,0,90)
script.Parent.customize.shirtColor.AnchorPoint = Vector2.new(0.5,0)
else
script.Parent.customize.buttons.UIListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Left
script.Parent.customize.shirtColor.UIGridLayout.HorizontalAlignment = Enum.HorizontalAlignment.Left
script.Parent.customize.hairColor.UIGridLayout.HorizontalAlignment = Enum.HorizontalAlignment.Left
script.Parent.customize.hairColor.Position = UDim2.new(0,5,0,90)
script.Parent.customize.hairColor.AnchorPoint = Vector2.new(0,0)
script.Parent.customize.shirtColor.Position = UDim2.new(0,5,0,90)
script.Parent.customize.shirtColor.AnchorPoint = Vector2.new(0,0)
end
end
input.mode.Changed:connect(updateInputMode)
updateInputMode()
script.Parent:WaitForChild("CameraLookat")
script.Parent:WaitForChild("CameraOrigin")
--network:invoke("lockCameraPosition",script.Parent.CameraMainPos.Value)
local runService = game:GetService("RunService")
local lookup = assets:WaitForChild("accessories")
local coolCamera = true
--[[
spawn(function()
while coolCamera do
local camSize = workspace.CurrentCamera.ViewportSize
local ray = workspace.CurrentCamera:ScreenPointToRay(camSize.X,camSize.Y,300)
local hitpart, hitpos = workspace:FindPartOnRay(ray)
if hitpos then
local lookat = script.Parent.CameraLookat.Value
lookat = Vector3.new(hitpos.x + lookat.x, hitpos.y + lookat.y, hitpos.z + lookat.z)/2
workspace.CurrentCamera.CFrame = CFrame.new(script.Parent.CameraOrigin.Value, lookat)
end
runService.RenderStepped:wait()
end
end)
]]
local renderedItems = Instance.new("Folder")
renderedItems.Name = "renderedOptions"
renderedItems.Parent = workspace
local characterRender
local rand = Random.new(os.time())
local characterTable = {}
renderedItems.DescendantAdded:connect(function(object)
if object.Name == "bodyPart" then
local part = object.Parent
part.Color = lookup:FindFirstChild("skinColor"):FindFirstChild(tostring(characterTable.accessories.skinColorId)).Value
elseif object.Name == "hair_Head" and object:IsA("BasePart") then
object.Color = lookup:FindFirstChild("hairColor"):FindFirstChild(tostring(characterTable.accessories.hairColorId)).Value
elseif object.Name == "shirt" or object.Name == "shirtTag" then
if object.Name == "shirtTag" then
object = object.Parent
end
if object:IsA("BasePart") then
object.Color = lookup:FindFirstChild("shirtColor"):FindFirstChild(tostring(characterTable.accessories.shirtColorId)).Value
end
end
end)
repeat wait() until not loadingassets
wait(1)
tween(script.Parent.blackout,{"BackgroundTransparency"},1,1)
local pastLanding
local function getItemFromHitpart(hitpart)
for i,renderItem in pairs(renderedItems:GetChildren()) do
if hitpart:IsDescendantOf(renderItem) then
return renderItem
end
end
end
local selectedItem
local function selectItem(item)
if selectedItem and selectedItem.Parent then
selectedItem.PrimaryPart.Transparency = 1
selectedItem = nil
end
if item then
selectedItem = item
item.PrimaryPart.Transparency = 0.3
end
end
local inputModule = input
local _input
function InputChanged(input, processed)
if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then
_input = input
end
end
local cameraTravelTime = 3
local cameraTravelDistance = 15
local cameraTargetGoal = CFrame.new(script.Parent.CameraOrigin.Value, script.Parent.CameraLookat.Value)
local cameraTargetStart = cameraTargetGoal -cameraTargetGoal.lookVector*cameraTravelDistance
local cameraTarget = cameraTargetStart
local startTime = tick()
runService:BindToRenderStep("render", Enum.RenderPriority.Camera.Value-1, function()
local arrived
if tick() - startTime < cameraTravelTime then
local movementVector = cameraTargetGoal.lookVector*cameraTravelDistance
local t = math.clamp((tick()-startTime)/cameraTravelTime, 0, 1)
cameraTarget = cameraTargetStart + movementVector * t^(1/7)
elseif not arrived then
arrived = true
cameraTarget = cameraTargetGoal
end
local camSway = 0
local hitpart, hitpos
if --[[pastLanding and renderedItems and]] script.Parent.customize.Visible then
cameraTarget = script.Parent.CameraTablePos.Value
if cameraTarget == script.Parent.CameraTablePos.Value then
camSway = 50
end
if _input then
workspace.CurrentCamera.CFrame = script.Parent.CameraTablePos.Value
local ray = workspace.CurrentCamera:ScreenPointToRay(_input.Position.X,_input.Position.Y,0)
local renderedChildren = renderedItems:GetChildren()
if #renderedChildren > 0 and (inputModule.mode.Value == "pc" or inputModule.mode.Value == "mobile") then
local hitpart, hitpos = workspace:FindPartOnRayWithWhitelist(Ray.new(ray.Origin, ray.Direction * 250), renderedChildren, true)
if hitpart then
local item = getItemFromHitpart(hitpart)
selectItem(item)
else
selectItem()
end
end
end
-- add camera sway effect, to a lessor extent
elseif not pastLanding then
camSway = 100
end
if camSway > 0 then
if _input and arrived then
local ray = workspace.CurrentCamera:ScreenPointToRay(_input.Position.X,_input.Position.Y,camSway)
hitpart, hitpos = workspace:FindPartOnRay(ray)
if hitpos then
local lookat = cameraTarget.Position + cameraTarget.lookVector * 50
lookat = Vector3.new(hitpos.x + lookat.x * 25, hitpos.y + lookat.y * 25, hitpos.z + lookat.z * 25)/26
--workspace.CurrentCamera.CFrame = CFrame.new(cameraTarget.Position, lookat)
_input = nil
-- network:invoke("lockCameraPosition",CFrame.new(cameraTarget.Position, lookat), 0.2)
end
elseif not arrived then
-- network:invoke("lockCameraPosition",CFrame.new(cameraTarget.Position, cameraTarget.Position + cameraTarget.lookVector))
end
end
end)
local connection = game:GetService("UserInputService").InputChanged:connect(InputChanged)
local mouseDownTime = 0
game:GetService("UserInputService").InputBegan:connect(function(input, absorbed)
if not absorbed and (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch) then
mouseDownTime = tick()
InputChanged(input, absorbed)
end
end)
local currentCategory
game:GetService("UserInputService").InputEnded:connect(function(input, absorbed)
if not absorbed and (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch) and tick() - mouseDownTime <= 2 then
if selectedItem then
--print("WOAH")
local currentDisplayCategory = currentCategory
if currentDisplayCategory == "skinColor" then
currentDisplayCategory = "skinColorId"
end
characterTable.accessories[currentDisplayCategory] = tonumber(selectedItem.Name)
network:invoke("applyCharacterAppearanceToRenderCharacter", characterRender.entity, characterTable)
end
end
end)
local referralFrame = script.Parent.Notice.content.referral
local referralPending
script.Parent.Notice.content.referral.invite.Frame.send.Activated:connect(function()
if not referralPending then
referralPending = true
referralFrame.invite.Visible = false
referralFrame.status.Visible = true
referralFrame.status.wait.Visible = true
referralFrame.status.status.Visible = false
local success, status = network:invokeServer("sendReferral", referralFrame.invite.Frame.code.TextBox.Text)
if game.Players.LocalPlayer:FindFirstChild("acceptedReferral") == nil then
referralFrame.status.wait.Visible = false
referralFrame.status.status.Visible = true
referralFrame.status.status.Text = status
if not success then
wait(3)
referralFrame.status.Visible = false
referralFrame.invite.Visible = true
end
end
referralPending = false
end
end)
game.Players.LocalPlayer.ChildAdded:connect(function(Child)
if Child.Name == "acceptedReferral" then
referralFrame.status.status.Text = "Referral accepted! Play now to recieve 200 free Ethyr."
referralFrame.status.Visible = true
referralFrame.invite.Visible = false
referralFrame.status.status.Visible = true
referralFrame.status.wait.Visible = false
end
end)
local closing
game.Players.LocalPlayer.ChildRemoved:connect(function(Child)
if referralFrame.status.Visible and game.Players.LocalPlayer:FindFirstChild("acceptedReferral") == nil and game.Players.LocalPlayer:FindFirstChild("messagePending") == nil and not closing then
closing = true
referralFrame.status.status.Visible = true
referralFrame.status.Visible = true
referralFrame.invite.Visible = false
referralFrame.status.wait.Visible = false
referralFrame.status.status.Text = "Referral timed out. Please confirm you entered the username correctly and that the player is online"
wait(3)
closing = false
referralFrame.status.Visible = false
referralFrame.invite.Visible = true
end
end)
local function reset()
for i,oslot in pairs(script.Parent.main.Frame.DataSlots:GetChildren()) do
if oslot:IsA("ImageButton") then
oslot.ImageColor3 = Color3.fromRGB(126, 126, 126)
oslot.PlayerData.Frame.ImageColor3 = Color3.fromRGB(36, 36, 36)
end
end
end
local playButton = script.Parent.main.play
script.Parent.DataSlot.Changed:Connect(function()
reset()
local slot = script.Parent.DataSlot.Value
local real = script.Parent.main.Frame.DataSlots:FindFirstChild(tostring(slot))
-- if slot and real and real.PlayerData.Visible then
if slot and real then
-- script.Parent.main.Frame.PlayButton.Slot.Text = "Slot " .. tostring(slot)
playButton.Visible = true
real.ImageColor3 = Color3.fromRGB(57, 212, 255)
real.PlayerData.Frame.ImageColor3 = Color3.fromRGB(11, 73, 111)
playButton.Flutter.ImageTransparency = 0.3
playButton.Flutter.Size = UDim2.new(1,0,1,0)
playButton.Flutter.Visible = true
tween(playButton.Flutter,{"Size","ImageTransparency"},{UDim2.new(3,0,3,0),1},0.3)
else
playButton.Visible = false
end
end)
local teleporting = false
local debounce = false
local function getModelAveragePosition(model)
local preavg = Vector3.new()
local totalcount = 0
for i,part in pairs(model:GetChildren()) do
if part:IsA("BasePart") then
preavg = preavg + part.Position
totalcount = totalcount + 1
end
end
return preavg / totalcount
end
local function updateColors()
for i,object in pairs(renderedItems:GetDescendants()) do
if object.Name == "bodyPart" then
local part = object.Parent
part.Color = lookup:FindFirstChild("skinColor"):FindFirstChild(tostring(characterTable.accessories.skinColorId or 1)).Value
elseif object.Name == "hair_Head" and object:IsA("BasePart") then
object.Color = lookup:FindFirstChild("hairColor"):FindFirstChild(tostring(characterTable.accessories.hairColorId or 1)).Value
elseif object.Name == "shirt" or object:FindFirstChild("shirtTag") then
object.Color = lookup:FindFirstChild("shirtColor"):FindFirstChild(tostring(characterTable.accessories.shirtColorId or 1)).Value
end
end
network:invoke("applyCharacterAppearanceToRenderCharacter", characterRender.entity, characterTable)
end
local function createRepresentationOfItem(item)
local repre
if item:IsA("Color3Value") then
--print("ello")
repre = assets.entities.colorRepre:Clone()
repre.value.Color = item.Value
repre.Name = item.Name
else
repre = item:Clone()
end
local avgpos = getModelAveragePosition(repre)
-- reparent everything to root
for i,part in pairs(repre:GetDescendants()) do
if part:IsA("BasePart") then
if part.Parent == repre and part:FindFirstChild("colorOverride") == nil then
local bodyPartTag = Instance.new("BoolValue")
bodyPartTag.Name = "bodyPart"
bodyPartTag.Parent = part
else
part.Parent = repre
end
part.Anchored = true
end
end
-- create a PrimaryPart using the avgPos
local primaryPart = Instance.new("Part")
primaryPart.Size = Vector3.new(2,0.5,2)
primaryPart.CFrame = CFrame.new(avgpos - Vector3.new(0,2,0))
primaryPart.Parent = repre
primaryPart.Anchored = true
primaryPart.TopSurface = Enum.SurfaceType.Smooth
primaryPart.Material = Enum.Material.Neon
primaryPart.Transparency = 1
local immuneTag = Instance.new("BoolValue")
immuneTag.Name = "colorOverride"
immuneTag.Parent = primaryPart
repre.PrimaryPart = primaryPart
return repre
end
local displayConnection
local lastXboxSelected
local function generateCoolButtons()
script.Parent.customize.xboxButtons:ClearAllChildren()
for i,item in pairs(renderedItems:GetChildren()) do
if item and item.PrimaryPart then
local vector, onScreen = workspace.CurrentCamera:WorldToScreenPoint(item.PrimaryPart.Position)
if onScreen then
local button = script.Parent.customize.sampleXboxButton:Clone()
button.Name = item.Name
button.Parent = script.Parent.customize.xboxButtons
button.Visible = input.mode.Value == "xbox"
button.Position = UDim2.new(0, vector.X, 0, vector.Y + game.GuiService:GetGuiInset().Y)
button.Activated:connect(function()
if input.mode.Value == "xbox" then
local currentDisplayCategory = currentCategory
if currentCategory == "skinColor" then
currentDisplayCategory = "skinColorId"
end
characterTable.accessories[currentDisplayCategory] = tonumber(button.Name)
network:invoke("applyCharacterAppearanceToRenderCharacter", characterRender.entity, characterTable)
end
end)
button.SelectionGained:connect(function()
lastXboxSelected = item
selectItem(item)
end)
button.SelectionLost:connect(function()
if lastXboxSelected == item then
selectItem(nil)
end
end)
end
end
end
end
local function inputCheck()
if input.mode.Value == "mobile" and workspace.CurrentCamera.ViewportSize.Y <= 700 then
script.Parent.Notice.UIScale.Scale = 0.65
script.Parent.main.UIScale.Scale = 0.7
script.Parent.main.Size = UDim2.new(1.43, 0,1.43, 0)
script.Parent.customize.UIScale.Scale = 0.7
script.Parent.customize.Size = UDim2.new(1.43, 0,1.43, 0)
else
script.Parent.Notice.UIScale.Scale = 1
script.Parent.main.UIScale.Scale = 1
script.Parent.main.Size = UDim2.new(1, 0,1, 0)
script.Parent.customize.UIScale.Scale = 1
script.Parent.customize.Size = UDim2.new(1, 0,1, 0)
end
end
inputCheck()
input.mode.Changed:connect(function()
for i,button in pairs(script.Parent.customize.xboxButtons:GetChildren()) do
if button:IsA("GuiObject") then
button.Visible = input.mode.Value == "xbox"
end
end
inputCheck()
end)
local function displayCategory(categoryName)
renderedItems:ClearAllChildren()
local items = lookup:FindFirstChild(categoryName)
if items then
local x = 0
local z = 0
local target = workspace:WaitForChild("Tabletop")
for i,item in pairs(items:GetChildren()) do
local repre = createRepresentationOfItem(item)
local cf = target.CFrame * CFrame.new((z * 6) - target.Size.X/2 + 1.1, target.Size.Y/2 + (z * 3.2), (x * 2.5) - target.Size.Z/2 + 1.1)
repre:SetPrimaryPartCFrame(cf)
repre.Parent = renderedItems
x = x + 1
if x > 4 then
x = 0
z = z + 1
end
end
end
for i,button in pairs(script.Parent.customize.buttons:GetChildren()) do
if button:IsA("ImageButton") then
button.ImageColor3 = Color3.fromRGB(103, 255, 212)
end
end
local newbutton = script.Parent.customize.buttons:FindFirstChild(categoryName)
if newbutton then
newbutton.ImageColor3 = Color3.fromRGB(255, 255, 255)
end
currentCategory = categoryName
script.Parent.customize.hairColor.Visible = false
script.Parent.customize.shirtColor.Visible = false
if categoryName == "hair" then
script.Parent.customize.hairColor.Visible = true
elseif categoryName == "undershirt" then
script.Parent.customize.shirtColor.Visible = true
end
generateCoolButtons()
end
for i,button in pairs(script.Parent.customize.buttons:GetChildren()) do
if button:IsA("ImageButton") then
button.Activated:connect(function()
displayCategory(button.Name)
end)
end
end
local function playButtonActivated()
if debounce then
return false
end
debounce = true
local slot = script.Parent.DataSlot.Value
local real = script.Parent.main.Frame.DataSlots:FindFirstChild(tostring(slot))
-- if slot and real and real.PlayerData.Visible and not teleporting then
if slot and real and not teleporting then
-- if real:FindFirstChild("customize") and not script.Parent.customize.Visible then
if not (globalData and globalData.saveSlotData["-slot"..slot]) and not script.Parent.customize.Visible then
script.Parent.main.Visible = false
script.Parent.onlineFriends.Visible = false
script.Parent.customize.Visible = true
if input.mode.Value == "xbox" then
game.GuiService.GuiNavigationEnabled = true
game.GuiService.SelectedObject = input.getBestButton(script.Parent.customize)
end
characterTable.accessories = {}
for i,category in pairs(lookup:GetChildren()) do
local children = category:GetChildren()
if #children > 0 then
local categoryName = category.Name
if categoryName == "skinColor" or categoryName == "shirtColor" or categoryName == "hairColor" then
categoryName = categoryName .. "Id"
end
characterTable.accessories[categoryName] = 1;
end
end
displayCategory("hair")
for i,color in pairs(lookup:WaitForChild("hairColor"):GetChildren()) do
if color:IsA("Color3Value") then
local buttonRepre = script.Parent.customize.colorButtonSample:Clone()
buttonRepre.ImageColor3 = color.value
buttonRepre.Name = color.Name
buttonRepre.Parent = script.Parent.customize.hairColor
buttonRepre.Visible = true
-- change hairColor
buttonRepre.Activated:connect(function()
characterTable.accessories["hairColorId"] = tonumber(buttonRepre.Name)
updateColors()
end)
end
end
for i,color in pairs(lookup:WaitForChild("shirtColor"):GetChildren()) do
if color:IsA("Color3Value") then
local buttonRepre = script.Parent.customize.colorButtonSample:Clone()
buttonRepre.ImageColor3 = color.value
buttonRepre.Name = color.Name
buttonRepre.Parent = script.Parent.customize.shirtColor
buttonRepre.Visible = true
-- change shirtColor
buttonRepre.Activated:connect(function()
characterTable.accessories["shirtColorId"] = tonumber(buttonRepre.Name)
updateColors()
end)
end
end
characterRender = network:invoke("createRenderCharacterContainerFromCharacterAppearanceData", workspace:WaitForChild("characterMask"), characterTable)
characterRender.Parent = workspace
local animationController = characterRender.entity:WaitForChild("AnimationController")
local currentEquipment = network:invoke("getCurrentlyEquippedForRenderCharacter", characterRender.entity)
local weaponType do
if currentEquipment[1] then
weaponType = currentEquipment[1].baseData.equipmentType
end
end
local track = network:invoke("getMovementAnimationForCharacter", animationController, "idling", weaponType, nil)
if track then
if typeof(track) == "Instance" then
track:Play()
elseif typeof(track) == "table" then
for ii, obj in pairs(track) do
obj:Play()
end
end
end
workspace.CurrentCamera.FieldOfView = 30
-- Rob if you're seeing this I want you to know im in physical pain
_G.Aero.Controllers.UI.CameraCFrame = script.Parent.CameraTablePos.Value
_G.Aero.Controllers.UI.FileSelect.LeftPane.AnchorPoint = Vector2.new(1, 0)
-- network:invoke("lockCameraPosition",script.Parent.CameraTablePos.Value, 0.7)
if blur then
tween(blur,{"Size"},0,0.7)
end
-- tween(workspace.CurrentCamera, {"FieldOfView"}, 30, 0.5)
wait(0.7)
cameraTarget = script.Parent.CameraTablePos.Value
generateCoolButtons()
else
teleporting = true
if script.Parent.customize.Visible and characterTable and characterTable.accessories then
game:GetService("TeleportService"):SetTeleportSetting("playerAccessories",game:GetService("HttpService"):JSONEncode(characterTable.accessories))
customizeTable = characterTable.accessories
end
--[[
game:GetService("TeleportService"):SetTeleportSetting("arrivingTeleportId",game.PlaceId)
game:GetService("TeleportService"):SetTeleportSetting("lastTimeStamp",real.Data.lastTimeStamp.Value)
game:GetService("TeleportService"):SetTeleportSetting("dataSlot",slot)
network:invoke("teleportPlayerTo",real.Data.lastLocation.Value) ]]
script.Parent.Enabled = false
tween(workspace.CurrentCamera, {"FieldOfView"}, {120}, 7)
network:invoke("localPrepareForTeleport", real.Data.lastLocation.Value)
local realm
if (slot == 13 or slot == 14 or slot == 15) and isAdmin then
realm = "mirror"
end
network:invokeServer("enterGame", real.Data.lastLocation.Value, nil, slot, customizeTable, realm)
end
else
script.Parent.main.Frame.PlayButton.Visible = false
end
debounce = false
end
script.Parent.main.Frame.PlayButton.Activated:Connect(playButtonActivated)
script.Parent.main.play.Activated:Connect(playButtonActivated)
script.Parent.customize.play.Activated:Connect(playButtonActivated)
if input.mode.Value:lower() == "xbox" or game:GetService("UserInputService").GamepadEnabled then
game.GuiService.GuiNavigationEnabled = true
game.GuiService.AutoSelectGuiEnabled = true
game.GuiService.SelectedObject = script.Parent.landing.play
end
local allowedSlots = 4
if isAdmin then
allowedSlots = 20
elseif isLegend then
allowedSlots = 10
end
local player = game.Players.LocalPlayer
script.Parent.main.Frame.DataSlots.CanvasSize = UDim2.new(0,0,0,10 + allowedSlots * (160))
local frameDataPair = {}
local function loadDataFrameLogic(Frame, data, overrideCustomize, slot)
if Frame.character.ViewportFrame:FindFirstChild("entity") then
Frame.character.ViewportFrame.entity:Destroy()
end
if Frame.character.ViewportFrame:FindFirstChild("entity2") then
Frame.character.ViewportFrame.entity2:Destroy()
end
if data then
frameDataPair[Frame] = data
if (slot == 13 or slot == 14 or slot == 15) and isAdmin then
Frame.Loading.Visible = false
Frame.PlayerData.Level.Visible = false
Frame.PlayerData.Location.Visible = false
Frame.PlayerData.Class.Text = "Tester Slot"
Frame.Data.lastLocation.Value = 3372071669
Frame.PlayerData.Visible = true
Frame.BGHolder.BG.Image = "https://www.roblox.com/Thumbs/Asset.ashx?width=768&height=432&assetId="..Frame.Data.lastLocation.Value
Frame.BGHolder.BG.Visible = true
elseif data.newb then
Frame.Loading.Visible = false
Frame.PlayerData.Level.Visible = false
Frame.PlayerData.Location.Visible = false
Frame.PlayerData.Class.Text = "New Adventure"
Frame.Data.lastLocation.Value = 4561988219
Frame.PlayerData.Visible = true
Frame.BGHolder.BG.Image = "https://www.roblox.com/Thumbs/Asset.ashx?width=768&height=432&assetId="..Frame.Data.lastLocation.Value
Frame.BGHolder.BG.Visible = true
local customizeTag = Instance.new("BoolValue")
customizeTag.Name = "customize"
customizeTag.Parent = Frame
else
local lastLocation = data.lastLocation or 2064647391
Frame.Data.lastLocation.Value = lastLocation
Frame.Loading.Visible = false
Frame.PlayerData.Level.Text = "lv. " .. data.level
Frame.PlayerData.Class.Text = data.class or "???"
Frame.PlayerData.Location.Text = "-"
Frame.BGHolder.BG.Image = "https://www.roblox.com/Thumbs/Asset.ashx?width=768&height=432&assetId="..lastLocation
Frame.BGHolder.BG.Visible = true
spawn(function()
local info = game.MarketplaceService:GetProductInfo(lastLocation, Enum.InfoType.Asset)
if info then
Frame.PlayerData.Location.Text = info.Name
end
end)
Frame.Data.lastLocation.Value = lastLocation
Frame.PlayerData.Visible = true
if data.accessories then
local camera = Frame.character.ViewportFrame.CurrentCamera
if camera == nil then
camera = Instance.new("Camera")
camera.Parent = Frame.character.ViewportFrame
Frame.character.ViewportFrame.CurrentCamera = camera
end
local client = player
local character = player.Character
local mask = Frame.character.ViewportFrame.characterMask
local characterAppearanceData = {}
characterAppearanceData.equipment = data.equipment or {}
characterAppearanceData.accessories = data.accessories or {}
if configuration.getConfigurationValue("doUseProperAnimationForLoadingPlace", player) then
spawn(function()
local characterRender = network:invoke("createRenderCharacterContainerFromCharacterAppearanceData",mask, characterAppearanceData or {}, client)
characterRender.Parent = workspace.CurrentCamera
local animationController = characterRender.entity:WaitForChild("AnimationController")
local currentEquipment = network:invoke("getCurrentlyEquippedForRenderCharacter", characterRender.entity)
local weaponType do
if currentEquipment[1] then
weaponType = currentEquipment[1].baseData.equipmentType
end
end
local track = network:invoke("getMovementAnimationForCharacter", animationController, "idling", weaponType, nil)
if track then
if typeof(track) == "Instance" then
track:Play()
elseif typeof(track) == "table" then
for ii, obj in pairs(track) do
obj:Play()
end
end
spawn(function()
while true do
wait(0.1)
if typeof(track) == "Instance" then
if track.Length > 0 then
break
end
elseif typeof(track) == "table" then
local isGood = true
for ii, obj in pairs(track) do
if track.Length == 0 then
isGood = false
end
end
if isGood then
break
end
end
end
if characterRender then
local entity = characterRender.entity
entity.Parent = script.Parent.ViewportFrame
characterRender:Destroy()
local focus = CFrame.new(entity.PrimaryPart.Position + entity.PrimaryPart.CFrame.lookVector * 6.3, entity.PrimaryPart.Position) * CFrame.new(-3,0,0)
camera.CFrame = CFrame.new(focus.p + Vector3.new(0,1.5,0), entity.PrimaryPart.Position + Vector3.new(0,0.5,0))
end
end)
end
end)
else
spawn(function()
local characterRender = network:invoke("createRenderCharacterContainerFromCharacterAppearanceData",mask, characterAppearanceData or {}, client)
characterRender.Parent = workspace.CurrentCamera
local animationController = characterRender.entity:WaitForChild("AnimationController")
local track = animationController:LoadAnimation(mask.idle)
track.Looped = true
track.Priority = Enum.AnimationPriority.Idle
track:Play()
wait()
if characterRender then
local entity = characterRender.entity
entity.Parent = Frame.character.ViewportFrame
characterRender:destroy()
local focus = CFrame.new(entity.PrimaryPart.Position + entity.PrimaryPart.CFrame.lookVector * 6.3, entity.PrimaryPart.Position) * CFrame.new(3,0,0)
camera.CFrame = CFrame.new(focus.p + Vector3.new(0,1.5,0), entity.PrimaryPart.Position + Vector3.new(0,0.5,0))
end
end)
end
elseif not overrideCustomize then
local customizeTag = Instance.new("BoolValue")
customizeTag.Name = "customize"
customizeTag.Parent = Frame
end
end
else
Frame.Loading.Sample.TextLabel.Text = "Failed!"
wait(1)
Frame.Loading.Sample.TextLabel.Text = "Load"
end
end
local petContainer, petPreviewMaskCopy
function activateFrame(Frame)
local i = tonumber(Frame.Name)
-- print("Click")
if debounce then
return false
end
if petContainer then
petContainer:Destroy()
petContainer = nil
end
debounce = true
-- print("Clack")
-- if Frame.PlayerData.Visible then
if frameDataPair[Frame] then
local data = frameDataPair[Frame]
script.Parent.DataSlot.Value = i
if mainCharacter then
mainCharacter:Destroy()
mainCharacter = nil
end
if data.accessories and not data.newb and not (isAdmin and (i == 13 or i == 14 or i == 15)) then
local characterAppearanceData = {}
characterAppearanceData.equipment = data.equipment
characterAppearanceData.accessories = data.accessories
mainCharacter = network:invoke("createRenderCharacterContainerFromCharacterAppearanceData", workspace:WaitForChild("characterPreviewMask"), characterAppearanceData)
mainCharacter.Parent = workspace
local class = string.lower(data.class)
if class == "berserker" or class == "paladin" or class == "knight" then
class = "warrior"
elseif class == "sorcerer" or class == "cleric" or class == "warlock" then
class = "mage"
elseif class == "ranger" or class == "trickster" or class == "assassin" then
class = "hunter"
end
-- local banner = workspace.banners:FindFirstChild(string.lower(class) .. "Banner")
-- if banner then
-- banner = banner:Clone()
-- banner.Parent = mainCharacter
-- banner:SetPrimaryPartCFrame(
-- workspace:WaitForChild("characterPreviewMask").PrimaryPart.CFrame * CFrame.new(-6, -2.3, 4) * CFrame.Angles(0, math.pi / 2, 0)
-- )
-- end
local petData do
for i, equipmentSlotData in pairs(data.equipment) do
warn("scanning for pet", equipmentSlotData.position == 10)
if equipmentSlotData.position == 10 then
petData = equipmentSlotData
break
end
end
end
if petData then
if petContainer then
petContainer:Destroy()
petContainer = nil
end
workspace.petPreviewMask.entityId.Value = petData.id
petContainer = network:invoke("assembleEntityByManifest", workspace.petPreviewMask)
network:invoke("setRenderDataByNameTag", workspace.petPreviewMask, "disableNameTagUI", true)
network:invoke("setRenderDataByNameTag", workspace.petPreviewMask, "disableHealthBarUI", true)
end
local animationController = mainCharacter.entity:WaitForChild("AnimationController")
local currentEquipment = network:invoke("getCurrentlyEquippedForRenderCharacter", mainCharacter.entity)
local weaponType do
if currentEquipment[1] then
weaponType = currentEquipment[1].baseData.equipmentType
end
end
local track = network:invoke("getMovementAnimationForCharacter", animationController, "idling", weaponType, nil)
if track then
if typeof(track) == "Instance" then
track:Play()
elseif typeof(track) == "table" then
for ii, obj in pairs(track) do
obj:Play()
end
end
end
end
elseif not loading then
--print("hola")
loading = true
spawn(function()
while loading do
Frame.Loading.Sample.TextLabel.Text = "."
wait(0.3)
if not loading then break end
Frame.Loading.Sample.TextLabel.Text = ".."
wait(0.3)
if not loading then break end
Frame.Loading.Sample.TextLabel.Text = "..."
wait(0.3)
end
end)
local data = network:invokeServer("loadPlayerData", i)
loading = false
loadDataFrameLogic(Frame, data, false, i)
for e,otherFrame in pairs(script.Parent.main.Frame.DataSlots:GetChildren()) do
if isAdmin and (i == 13 or i == 14 or i == 15) then
loadDataFrameLogic(otherFrame, {}, true, tonumber(otherFrame.Name))
end
if globalData and globalData.saveSlotData then
local globalData = data.globalData
local quickData = globalData.saveSlotData["-slot"..otherFrame.Name]
if quickData and not frameDataPair[otherFrame] then
loadDataFrameLogic(otherFrame, quickData, true, tonumber(otherFrame.Name))
end
end
end
end
debounce = false
end
for i=1,allowedSlots do
local Frame = script.Parent.main.Frame.DataSlots.Sample:Clone()
Frame.Name = tostring(i)
Frame.Parent = script.Parent.main.Frame.DataSlots
Frame.Slot.Text = "slot " .. tostring(i)
Frame.PlayerData.Visible = false
Frame.Loading.Visible = true
Frame.LayoutOrder = i
local loading = false
if Frame.character.ViewportFrame:FindFirstChild("entity") then
Frame.character.ViewportFrame.entity:Destroy()
end
if Frame.character.ViewportFrame:FindFirstChild("entity2") then
Frame.character.ViewportFrame.entity2:Destroy()
end
Frame.MouseButton1Click:Connect(function()
activateFrame(Frame)
end)
if i == 1 then
spawn(function()
-- ugly ugly hack
activateFrame(Frame)
activateFrame(Frame)
end)
end
Frame.Visible = true
end
reset()
script.Parent.main.Frame.DataSlots.Sample:Destroy()
for e,otherFrame in pairs(script.Parent.main.Frame.DataSlots:GetChildren()) do
local i = tonumber(otherFrame.Name)
if isAdmin and (i == 13 or i == 14 or i == 15) then
loadDataFrameLogic(otherFrame, {}, true, tonumber(otherFrame.Name))
end
if globalData then
if globalData.saveSlotData then
local quickData = globalData.saveSlotData["-slot"..otherFrame.Name]
if quickData and not frameDataPair[otherFrame] then
loadDataFrameLogic(otherFrame, quickData, true, tonumber(otherFrame.Name))
if otherFrame.Name=="1" then
-- activateFrame(otherFrame)
end
end
end
end
end
if globalData then
if globalData.ethyr then
script.Parent.main.ethyr.amount.Text = tostring(globalData.ethyr) .. " Ethyr"
else
script.Parent.main.ethyr.amount.Text = "0 Ethyr"
end
script.Parent.main.ethyr.buy.Activated:Connect(function()
script.Parent.main.products.Visible = not script.Parent.main.products.Visible
end)
for i,product in pairs(script.Parent.main.products:GetChildren()) do
if product:FindFirstChild("productId") and product:FindFirstChild("buy") then
product.buy.Activated:connect(function()
game.MarketplaceService:PromptProductPurchase(game.Players.LocalPlayer, product.productId.Value)
end)
end
end
end
--script.Parent.Enabled = true
network:connect("globalDataUpdated", "OnClientEvent", function(globalData)
if globalData.ethyr then
script.Parent.main.ethyr.amount.Text = tostring(globalData.ethyr) .. " Ethyr"
else
script.Parent.main.ethyr.amount.Text = "0 Ethyr"
end
end)
game.Players.LocalPlayer:WaitForChild("DataLoaded")
--script.Parent.Enabled = false
if input.mode.Value == "xbox" or game:GetService("UserInputService").GamepadEnabled then
game.GuiService.GuiNavigationEnabled = true
game.GuiService.AutoSelectGuiEnabled = true
game.GuiService.SelectedObject = script.Parent.landing.play
end
--https://www.roblox.com/Thumbs/Asset.ashx?width=768&height=432&assetId=2015602902
end
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/modules/proxy.lua
================================================
-- scrambler proxy for commands that need to be accessed from non-central sources
local proxy = {}
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
function proxy.openShop(...)
network:invoke("openShop", ...)
end
return proxy
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/modules/safeScript.lua
================================================
local module = {}
module.isActive = false
module.interactPrompt = "Open" -- prompt text
local player = game.Players.LocalPlayer
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local utilities = modules.load("utilities")
local tween = modules.load("tween")
local safe = script.Parent.Parent
local controller = safe.AnimationController
local animation = safe.Open
local track = controller:LoadAnimation(animation)
local loaded
function module.init()
if not loaded then
game.ContentProvider:PreloadAsync({track})
loaded = true
end
track:Stop()
track:Play()
track:AdjustSpeed(1.5)
delay(2.75, function()
if track.IsPlaying then
track:AdjustSpeed(0)
end
end)
end
function module.close()
track:AdjustSpeed(1.5)
game.CollectionService:RemoveTag(script.Parent, "interact")
delay(1.5, function()
game.CollectionService:AddTag(script.Parent, "interact")
end)
end
return module
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/modules/seatScript.lua
================================================
local module = {}
module.interactPrompt = "SIT" -- prompt text
module.leavePrompt = "GET UP" -- end interaction text
module.leaveMask = true -- display LEAVE as a screen UI instead of a billboard
module.isActive = false
local player = game.Players.LocalPlayer
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
function module.init()
if not player or not player.Character or not player.Character.PrimaryPart then return end
player.Character.PrimaryPart.CFrame = script.Parent.CFrame + Vector3.new(0, 0.5, 0)
player.Character.PrimaryPart.Anchored = true
network:invoke("setCharacterMovementState", "isSitting", true, script.Parent)
end
function module.close()
if not player then return end
player.Character.PrimaryPart.Anchored = false
network:invoke("setCharacterMovementState", "isSitting", false, script.Parent)
end
return module
--
================================================
FILE: src/StarterPlayer/StarterPlayerScripts/modules/warpPad.lua
================================================
local module = {}
module.isActive = false
module.interactPrompt = "WARP"
-- prompt text
module.instant = true
local player = game.Players.LocalPlayer
local modules = require(game.ReplicatedStorage:WaitForChild("modules"))
local network = modules.load("network")
local function getTargetDoor()
return script.Parent.Parent.Target
end
function module.init()
local target = getTargetDoor()
if target and player.Character and player.Character.PrimaryPart then
if game.ReplicatedStorage.assets.sounds:FindFirstChild("blink") then
game.ReplicatedStorage.assets.sounds.blink:Play()
end
--player.Character:SetPrimaryPartCFrame(player.Character.PrimaryPart.CFrame - player.Character.PrimaryPart.Position + target.Position)
network:fireServer("playerRequest_activateEscapeRope", script.Parent)
end
end
function module.close()
end
return module
--
================================================
FILE: tools/serializer.lua
================================================
-- Command line code to serialize assets for rojo syncing
local feed = "return {\n"
local defaults = {
["Volume"] = 0.5,
["EmitterSize"] = 10,
["MaxDistance"] = 10000,
["Looped"] = false,
["PlaybackSpeed"] = 1,
}
for _, sound in pairs(game.ReplicatedStorage.assets.sounds:GetChildren()) do
feed = feed .. "\t[\"" .. sound.Name .. "\"] = {\n"
for property, default in pairs(defaults) do
local value = sound[property]
if value ~= default then
if typeof(value) == "number" then
value = (math.floor(value * 10)) / 10
end
feed = feed .. "\t\t" .. property .. " = " .. tostring(value) .. ",\n"
end
end
feed = feed .. "\t\tSoundId = \"" .. sound.SoundId .. "\",\n"
feed = feed .. "\t},\n"
end
feed = feed .. "}"
workspace.stuff.Source = feed