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
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