├── .dockerignore ├── .env ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── general-bug-report.md │ └── todo.md └── workflows │ └── NamigatorBindings.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── database ├── __init__.py ├── dbc │ ├── DbcDatabaseManager.py │ ├── DbcModels.py │ └── __init__.py ├── realm │ ├── RealmDatabaseManager.py │ ├── RealmModels.py │ └── __init__.py └── world │ ├── WorldDatabaseManager.py │ ├── WorldModels.py │ └── __init__.py ├── docker-compose.yml ├── docker-database-update.sh ├── etc ├── config │ └── config.yml.dist ├── databases │ ├── dbc │ │ ├── dbc.sql │ │ └── updates │ │ │ ├── .nomedia │ │ │ └── updates.sql │ ├── realm │ │ ├── realm.sql │ │ └── updates │ │ │ ├── .nomedia │ │ │ └── updates.sql │ └── world │ │ ├── custom │ │ ├── scarlet_monastery_teleporter.sql │ │ └── shaman_totems.sql │ │ ├── updates │ │ ├── .nomedia │ │ └── updates.sql │ │ └── world.sql ├── docker │ ├── inotify │ │ ├── Dockerfile │ │ └── init.sh │ ├── main │ │ └── Dockerfile │ ├── realm │ │ └── Dockerfile │ ├── sql │ │ ├── Dockerfile │ │ └── build_sql_patches.sh │ └── world │ │ └── Dockerfile ├── maps │ └── .nomedia └── navs │ └── .nomedia ├── game ├── __init__.py ├── login │ ├── LoginManager.py │ ├── LoginSessionStateHandler.py │ └── __init__.py ├── realm │ ├── AccountManager.py │ ├── RealmManager.py │ └── __init__.py ├── update │ ├── UpdateManager.py │ ├── UpdateSessionStateHandler.py │ └── __init__.py └── world │ ├── WorldLoader.py │ ├── WorldManager.py │ ├── WorldSessionStateHandler.py │ ├── __init__.py │ ├── managers │ ├── CommandManager.py │ ├── __init__.py │ ├── abstractions │ │ ├── Vector.py │ │ └── __init__.py │ ├── maps │ │ ├── Cell.py │ │ ├── GridManager.py │ │ ├── InstancesManager.py │ │ ├── Map.py │ │ ├── MapEventManager.py │ │ ├── MapManager.py │ │ ├── MapTile.py │ │ ├── __init__.py │ │ └── helpers │ │ │ ├── AreaInformation.py │ │ │ ├── CellUtils.py │ │ │ ├── Constants.py │ │ │ ├── InstanceToken.py │ │ │ ├── LiquidInformation.py │ │ │ ├── MapUtils.py │ │ │ ├── Mapbuild.py │ │ │ ├── Namigator.py │ │ │ ├── PendingTeleportDataHolder.py │ │ │ ├── ResurrectionRequestDataHolder.py │ │ │ └── __init__.py │ └── objects │ │ ├── GuidManager.py │ │ ├── ObjectManager.py │ │ ├── __init__.py │ │ ├── ai │ │ ├── AIFactory.py │ │ ├── BasicCreatureAI.py │ │ ├── CreatureAI.py │ │ ├── CritterAI.py │ │ ├── GameObjectAI.py │ │ ├── GuardAI.py │ │ ├── NullCreatureAI.py │ │ ├── PetAI.py │ │ ├── TotemAI.py │ │ └── __init__.py │ │ ├── corpse │ │ ├── CorpseManager.py │ │ └── __init__.py │ │ ├── dynamic │ │ ├── DynamicObjectManager.py │ │ └── __init__.py │ │ ├── farsight │ │ ├── Camera.py │ │ ├── FarSightManager.py │ │ └── __init__.py │ │ ├── gameobjects │ │ ├── GameObjectBuilder.py │ │ ├── GameObjectLootManager.py │ │ ├── GameObjectManager.py │ │ ├── GameObjectSpawn.py │ │ ├── __init__.py │ │ └── managers │ │ │ ├── ButtonManager.py │ │ │ ├── CameraManager.py │ │ │ ├── ChairManager.py │ │ │ ├── ChestMananger.py │ │ │ ├── DoorManager.py │ │ │ ├── DuelArbiterManager.py │ │ │ ├── FishingNodeManager.py │ │ │ ├── GooberManager.py │ │ │ ├── MiningNodeManager.py │ │ │ ├── QuestGiverManager.py │ │ │ ├── RitualManager.py │ │ │ ├── SpellFocusManager.py │ │ │ ├── TransportManager.py │ │ │ ├── TrapManager.py │ │ │ └── __init__.py │ │ ├── item │ │ ├── ContainerManager.py │ │ ├── ContainerSlots.py │ │ ├── EnchantmentHolder.py │ │ ├── ItemLootManager.py │ │ ├── ItemManager.py │ │ ├── Stats.py │ │ └── __init__.py │ │ ├── locks │ │ ├── LockHolder.py │ │ ├── LockManager.py │ │ └── __init__.py │ │ ├── loot │ │ ├── LootHolder.py │ │ ├── LootManager.py │ │ ├── LootSelection.py │ │ └── __init__.py │ │ ├── pools │ │ ├── PoolChancedEntry.py │ │ ├── PoolManager.py │ │ ├── PoolObject.py │ │ └── __init__.py │ │ ├── script │ │ ├── AIEventHandler.py │ │ ├── ConditionChecker.py │ │ ├── Script.py │ │ ├── ScriptAIEvent.py │ │ ├── ScriptCommand.py │ │ ├── ScriptHandler.py │ │ ├── ScriptHelpers.py │ │ ├── ScriptManager.py │ │ ├── ScriptedEvent.py │ │ ├── ScriptedEventTarget.py │ │ └── __init__.py │ │ ├── spell │ │ ├── CastingSpell.py │ │ ├── CooldownEntry.py │ │ ├── EffectTargets.py │ │ ├── EquipmentProcManager.py │ │ ├── ExtendedSpellData.py │ │ ├── SpellEffect.py │ │ ├── SpellEffectDummyHandler.py │ │ ├── SpellEffectHandler.py │ │ ├── SpellManager.py │ │ ├── __init__.py │ │ └── aura │ │ │ ├── AppliedAura.py │ │ │ ├── AreaAuraHolder.py │ │ │ ├── AuraEffectDummyHandler.py │ │ │ ├── AuraEffectHandler.py │ │ │ ├── AuraManager.py │ │ │ └── __init__.py │ │ ├── timers │ │ ├── MirrorTimer.py │ │ ├── MirrorTimersManager.py │ │ └── __init__.py │ │ └── units │ │ ├── ChatManager.py │ │ ├── DamageInfoHolder.py │ │ ├── UnitManager.py │ │ ├── __init__.py │ │ ├── creature │ │ ├── CreatureBuilder.py │ │ ├── CreatureLootManager.py │ │ ├── CreatureManager.py │ │ ├── CreaturePickPocketLootManager.py │ │ ├── CreatureSpawn.py │ │ ├── CreatureSpellsEntry.py │ │ ├── ThreatManager.py │ │ ├── __init__.py │ │ ├── groups │ │ │ ├── CreatureGroupManager.py │ │ │ ├── CreatureGroupMember.py │ │ │ └── __init__.py │ │ ├── items │ │ │ ├── VirtualItemInfoHolder.py │ │ │ ├── VirtualItemUtils.py │ │ │ └── __init__.py │ │ ├── utils │ │ │ ├── CreatureUtils.py │ │ │ ├── TrainerUtils.py │ │ │ ├── VendorUtils.py │ │ │ └── __init__.py │ │ └── vendors │ │ │ ├── VendorData.py │ │ │ └── __init__.py │ │ ├── movement │ │ ├── MovementInfo.py │ │ ├── MovementManager.py │ │ ├── __init__.py │ │ ├── behaviors │ │ │ ├── BaseMovement.py │ │ │ ├── ChaseMovement.py │ │ │ ├── ConfusedMovement.py │ │ │ ├── DistractedMovement.py │ │ │ ├── EvadeMovement.py │ │ │ ├── FearMovement.py │ │ │ ├── FlightMovement.py │ │ │ ├── GroupMovement.py │ │ │ ├── PetMovement.py │ │ │ ├── WanderingMovement.py │ │ │ └── WaypointMovement.py │ │ └── helpers │ │ │ ├── CommandMoveInfo.py │ │ │ ├── MovementWaypoint.py │ │ │ ├── PendingWaypoint.py │ │ │ ├── PetRangeMove.py │ │ │ ├── Spline.py │ │ │ ├── SplineBuilder.py │ │ │ ├── SplineEvent.py │ │ │ └── __init__.py │ │ ├── pet │ │ ├── ActivePet.py │ │ ├── PetData.py │ │ ├── PetManager.py │ │ └── __init__.py │ │ └── player │ │ ├── ChannelManager.py │ │ ├── EnchantmentManager.py │ │ ├── FriendsManager.py │ │ ├── GroupManager.py │ │ ├── InventoryManager.py │ │ ├── PlayerManager.py │ │ ├── ReputationManager.py │ │ ├── SkillManager.py │ │ ├── StatManager.py │ │ ├── TalentManager.py │ │ ├── __init__.py │ │ ├── guild │ │ ├── GuildManager.py │ │ ├── GuildPendingInvite.py │ │ ├── PetitionManager.py │ │ └── __init__.py │ │ ├── quest │ │ ├── ActiveQuest.py │ │ ├── QuestHelpers.py │ │ ├── QuestManager.py │ │ ├── QuestMenu.py │ │ └── __init__.py │ │ ├── taxi │ │ ├── TaxiManager.py │ │ ├── TaxiResumeInformation.py │ │ └── __init__.py │ │ └── trade │ │ ├── ChatAddonManager.py │ │ ├── ProposedEnchantment.py │ │ ├── TradeData.py │ │ ├── TradeManager.py │ │ └── __init__.py │ └── opcode_handling │ ├── Definitions.py │ ├── HandlerValidator.py │ ├── __init__.py │ └── handlers │ ├── NullHandler.py │ ├── __init__.py │ ├── channel │ ├── ChannelAnnounceHandler.py │ ├── ChannelBanHandler.py │ ├── ChannelInviteHandler.py │ ├── ChannelJoinHandler.py │ ├── ChannelKickHandler.py │ ├── ChannelLeaveHandler.py │ ├── ChannelListHandler.py │ ├── ChannelModeratorHandler.py │ ├── ChannelMuteHandler.py │ ├── ChannelOwnerHandler.py │ ├── ChannelPasswordHandler.py │ └── __init__.py │ ├── combat │ ├── AttackSwingHandler.py │ └── __init__.py │ ├── friends │ ├── FriendAddHandler.py │ ├── FriendDeleteHandler.py │ ├── FriendDeleteIgnoreHandler.py │ ├── FriendIgnoreHandler.py │ ├── FriendsListHandler.py │ └── __init__.py │ ├── gameobject │ ├── GameObjectQueryHandler.py │ ├── GameobjUseHandler.py │ └── __init__.py │ ├── group │ ├── GroupDisbandHandler.py │ ├── GroupInviteAcceptHandler.py │ ├── GroupInviteDeclineHandler.py │ ├── GroupInviteHandler.py │ ├── GroupLootMethodHandler.py │ ├── GroupSetLeaderHandler.py │ ├── GroupUnInviteGuidHandler.py │ ├── GroupUnInviteHandler.py │ ├── MinimapPingHandler.py │ └── __init__.py │ ├── guild │ ├── GuildCreateHandler.py │ ├── GuildDemoteHandler.py │ ├── GuildDisbandHandler.py │ ├── GuildInfoHandler.py │ ├── GuildInviteAcceptHandler.py │ ├── GuildInviteDeclineHandler.py │ ├── GuildInviteHandler.py │ ├── GuildLeaderHandler.py │ ├── GuildLeaveHandler.py │ ├── GuildMOTDHandler.py │ ├── GuildPromoteHandler.py │ ├── GuildQueryHandler.py │ ├── GuildRemoveMemberHandler.py │ ├── GuildRosterHandler.py │ ├── GuildSaveEmblemHandler.py │ └── __init__.py │ ├── interface │ ├── AuthSessionHandler.py │ ├── CharCreateHandler.py │ ├── CharDeleteHandler.py │ ├── CharEnumHandler.py │ └── __init__.py │ ├── inventory │ ├── AutoequipItemHandler.py │ ├── AutostoreBagItemHandler.py │ ├── DestroyItemHandler.py │ ├── ItemQueryMultipleHandler.py │ ├── ItemQuerySingleHandler.py │ ├── OpenItemHandler.py │ ├── PageTextQueryHandler.py │ ├── ReadItemHandler.py │ ├── SplitItemHandler.py │ ├── SwapInvItemHandler.py │ ├── SwapItemHandler.py │ ├── WrapItemHandler.py │ └── __init__.py │ ├── loot │ ├── AutostoreLootItemHandler.py │ ├── LootMoneyHandler.py │ ├── LootReleaseHandler.py │ ├── LootRequestHandler.py │ └── __init__.py │ ├── npc │ ├── ActivateTaxiHandler.py │ ├── BankerActivateHandler.py │ ├── BinderActivateHandler.py │ ├── BuyBankSlotHandler.py │ ├── BuyItemHandler.py │ ├── BuyItemInSlotHandler.py │ ├── CreatureQueryHandler.py │ ├── ListInventoryHandler.py │ ├── PetitionBuyHandler.py │ ├── PetitionOfferHandler.py │ ├── PetitionQueryHandler.py │ ├── PetitionShowSignaturesHandler.py │ ├── PetitionShowlistHandler.py │ ├── PetitionSignHandler.py │ ├── PetitionTurnInHandler.py │ ├── SellItemHandler.py │ ├── TabardVendorActivateHandler.py │ ├── TaxiNodeStatusQueryHandler.py │ ├── TaxiQueryNodesHandler.py │ ├── TrainerBuySpellHandler.py │ ├── TrainerListHandler.py │ └── __init__.py │ ├── pet │ ├── PetAbandonHandler.py │ ├── PetActionHandler.py │ ├── PetNameQueryHandler.py │ ├── PetRenameHandler.py │ ├── PetSetActionHandler.py │ └── __init__.py │ ├── player │ ├── BootMeHandler.py │ ├── DebugAIStateHandler.py │ ├── DuelAcceptHandler.py │ ├── DuelCanceledHandler.py │ ├── GetDeathBindPointHandler.py │ ├── InspectHandler.py │ ├── LogoutCancelHandler.py │ ├── LogoutRequestHandler.py │ ├── MountSpecialAnimHandler.py │ ├── MovementHandler.py │ ├── NameQueryHandler.py │ ├── NewSpellSlotHandler.py │ ├── PingHandler.py │ ├── PlayedTimeHandler.py │ ├── PlayerLoginHandler.py │ ├── PlayerLogoutHandler.py │ ├── PvPPortHandler.py │ ├── RandomRollHandler.py │ ├── RepopRequestHandler.py │ ├── ResurrectResponseHandler.py │ ├── SetActionButtonHandler.py │ ├── SetDeathBindPointHandler.py │ ├── SetWeaponModeHandler.py │ ├── StandStateChangeHandler.py │ ├── __init__.py │ └── cheats │ │ ├── BeastMasterHandler.py │ │ ├── CheatSetMoneyHandler.py │ │ ├── CooldownCheatHandler.py │ │ ├── CreateItemHandler.py │ │ ├── CreateMonsterHandler.py │ │ ├── DestroyMonsterHandler.py │ │ ├── GMSummonHandler.py │ │ ├── GodModeHandler.py │ │ ├── LearnSpellCheatHandler.py │ │ ├── LevelCheatHandler.py │ │ ├── LevelUpCheatHandler.py │ │ ├── MakeMonsterAttackMeHandler.py │ │ ├── PetLevelCheatHandler.py │ │ ├── RechargeHandler.py │ │ ├── SpeedCheatHandler.py │ │ ├── TaxiClearAllNodesHandler.py │ │ ├── TaxiEnableAllNodesHandlers.py │ │ ├── TeleportToPlayerHandler.py │ │ ├── TriggerCinematicCheatHandler.py │ │ └── __init__.py │ ├── quest │ ├── QuestConfirmAcceptHandler.py │ ├── QuestGiverAcceptQuestHandler.py │ ├── QuestGiverChooseRewardHandler.py │ ├── QuestGiverCompleteQuestHandler.py │ ├── QuestGiverHelloHandler.py │ ├── QuestGiverQueryQuestHandler.py │ ├── QuestGiverRemoveQuestHandler.py │ ├── QuestGiverRequestReward.py │ ├── QuestGiverStatusHandler.py │ ├── QuestQueryHandler.py │ └── __init__.py │ ├── social │ ├── BugHandler.py │ ├── ChatHandler.py │ ├── LookingForGroupHandler.py │ ├── PlayerMacroHandler.py │ ├── TextEmoteHandler.py │ ├── WhoHandler.py │ └── __init__.py │ ├── spell │ ├── CancelAuraHandler.py │ ├── CancelCastHandler.py │ ├── CancelChannellingHandler.py │ ├── CastSpellHandler.py │ ├── UseItemHandler.py │ └── __init__.py │ ├── trade │ ├── AcceptTradeHandler.py │ ├── BeginTradeHandler.py │ ├── CancelTradeHandler.py │ ├── ClearTradeItemHandler.py │ ├── InitiateTradeHandler.py │ ├── SetTradeGoldHandler.py │ ├── SetTradeItemHandler.py │ ├── UnacceptTradeHandler.py │ └── __init__.py │ ├── unit │ ├── SetSelectionHandler.py │ ├── SetTargetHandler.py │ └── __init__.py │ └── world │ ├── AreaTriggerHandler.py │ ├── TimeQueryHandler.py │ ├── WorldTeleportHandler.py │ ├── ZoneUpdateHandler.py │ └── __init__.py ├── main.py ├── namigator └── README.txt ├── network ├── __init__.py ├── packet │ ├── PacketReader.py │ ├── PacketWriter.py │ ├── __init__.py │ └── update │ │ ├── UpdateBuilder.py │ │ ├── UpdateData.py │ │ ├── UpdateManager.py │ │ ├── UpdateMask.py │ │ ├── UpdatePacketFactory.py │ │ └── __init__.py └── sockets │ ├── SocketBuilder.py │ └── __init__.py ├── requirements.txt ├── tools ├── ClassTrainersSkillsGenerator.py ├── CreatureCacheParser.py ├── ItemCacheParser.py ├── __init__.py └── extractors │ ├── Extractor.py │ ├── MapExtractor.py │ ├── NavExtractor.py │ ├── __init__.py │ ├── definitions │ ├── Adt.py │ ├── Wdt.py │ ├── __init__.py │ ├── chunks │ │ ├── CLID.py │ │ ├── MCLQ.py │ │ ├── MCVT.py │ │ ├── MDDF.py │ │ ├── MDNM.py │ │ ├── MHDR.py │ │ ├── MLIQ.py │ │ ├── MODF.py │ │ ├── MOHD.py │ │ ├── MONM.py │ │ ├── MOPY.py │ │ ├── MOVI.py │ │ ├── MOVT.py │ │ ├── MPHD.py │ │ ├── TileHeader.py │ │ ├── TileInformation.py │ │ └── __init__.py │ ├── enums │ │ ├── LiquidFlags.py │ │ └── __init__.py │ ├── objects │ │ ├── CAaBox.py │ │ ├── Doodad.py │ │ ├── Index3.py │ │ ├── Vector3.py │ │ ├── Vertex.py │ │ ├── Wmo.py │ │ ├── WmoGroupFile.py │ │ └── __init__.py │ └── reader │ │ ├── StreamReader.py │ │ └── __init__.py │ ├── helpers │ ├── Constants.py │ ├── DataHolders.py │ ├── HeightField.py │ ├── LiquidAdtWriter.py │ ├── WmoLiquidParser.py │ ├── WmoLiquidWriter.py │ └── __init__.py │ ├── pydbclib │ ├── DbcReader.py │ ├── __init__.py │ ├── helpers │ │ └── VanillaAreaHelper.py │ └── structs │ │ ├── AreaTable.py │ │ ├── DbcHeader.py │ │ └── Map.py │ └── pympqlib │ ├── MpqArchive.py │ ├── MpqEntry.py │ ├── MpqFlags.py │ ├── MpqHash.py │ ├── MpqHeader.py │ ├── MpqReader.py │ └── __init__.py └── utils ├── ByteUtils.py ├── ChatLogManager.py ├── ConfigManager.py ├── Float16.py ├── Formulas.py ├── GitUtils.py ├── GuidUtils.py ├── Logger.py ├── Matrix.py ├── ObjectQueryUtils.py ├── PathManager.py ├── Srp6.py ├── SysUtils.py ├── TextUtils.py ├── __init__.py └── constants ├── AuthCodes.py ├── CharCodes.py ├── ConditionCodes.py ├── CustomCodes.py ├── DuelCodes.py ├── EnvVars.py ├── GroupCodes.py ├── ItemCodes.py ├── MiscCodes.py ├── MiscFlags.py ├── OpCodes.py ├── PetCodes.py ├── ScriptCodes.py ├── SpellCodes.py ├── UnitCodes.py ├── UpdateFields.py └── __init__.py /.dockerignore: -------------------------------------------------------------------------------- 1 | # Exclude heavy folders to avoid being sent on the build context. 2 | /etc/maps 3 | .git 4 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PROJECT='alpha-core' 2 | OS_TAG='python-3.12.7_slim-bookworm' 3 | DB_TAG='mariadb-11.3.2' 4 | INO_TAG='inotify-1.0.1' 5 | PHPAD_TAG='phpmyadmin:5.2.1' 6 | TIME_ZONE='Europe/London' 7 | MYSQL_HOST=sql 8 | MYSQL_USERNAME=root 9 | MYSQL_PASSWORD=pwd 10 | CONSOLE_MODE=False 11 | FORWARD_ADDRESS_OVERRIDE=127.0.0.1 12 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: grender 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/general-bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: General bug report 3 | about: Create a report to help us improve alpha-core. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Steps to reproduce** 14 | 1. ... 15 | 2. ... 16 | 3. ... 17 | 18 | **Expected behavior** 19 | A clear and concise description of what you expected to happen. 20 | 21 | **Screenshots** 22 | If needed, add screenshots to help explain your problem. 23 | 24 | **Sources** 25 | If needed, provide valid sources (original screenshots or videos, archived allakhazam...) that show what's the expected behavior or state. 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/todo.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: TODO 3 | about: Intended for developers. Explain what's left to implement so others can take 4 | this task if they want. 5 | title: 'TODO: ' 6 | labels: todo 7 | assignees: '' 8 | 9 | --- 10 | 11 | 12 | 13 | **Describe what needs to be implemented or enhanced** 14 | A clear and concise description of what needs to be implemented, enhanced, reworked, refactored... 15 | 16 | **Expected end result** 17 | A clear and concise description of what the end result should be. 18 | 19 | **Guidance** 20 | Provide any useful information, hints or guidance in general that can be useful for another developer to start working on this task. 21 | 1. ... 22 | 2. ... 23 | 3. ... 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **.pyc 2 | 3 | # IDE configuration files 4 | .idea 5 | .vscode 6 | 7 | # Project files 8 | etc/config/config.yml 9 | etc/logs 10 | 11 | # Extractor 12 | etc/mdx 13 | 14 | # virtualenv folder 15 | venv 16 | 17 | # Client data 18 | **.map 19 | **.mmap 20 | **.mmtile 21 | **.vmap 22 | **.vmtree 23 | **.vmtile 24 | **.dbc 25 | **.nav 26 | **.bvh 27 | **.idx 28 | **.mdx 29 | 30 | # Compiled libs 31 | **.pyd 32 | **.so 33 | 34 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "contrib/namigator"] 2 | path = contrib/namigator 3 | url = https://github.com/namreeb/namigator.git 4 | -------------------------------------------------------------------------------- /database/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/database/__init__.py -------------------------------------------------------------------------------- /database/dbc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/database/dbc/__init__.py -------------------------------------------------------------------------------- /database/realm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/database/realm/__init__.py -------------------------------------------------------------------------------- /database/world/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/database/world/__init__.py -------------------------------------------------------------------------------- /docker-database-update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function init_database() { 4 | docker cp etc/databases/${1}/updates/updates.sql alpha-core-sql:/tmp/updates.sql 5 | docker exec -it alpha-core-sql sh -c "mariadb -u root -ppwd alpha_${1} < /tmp/updates.sql" 6 | docker exec -it alpha-core-sql sh -c "rm /tmp/updates.sql" 7 | echo "Done with ${1}" 8 | } 9 | 10 | type="default" 11 | databases=('dbc' 'realm' 'world') 12 | 13 | for database in "${databases[@]}"; do 14 | echo "Updated" "$database" "$type" 15 | init_database $database 16 | done 17 | 18 | echo "Done" 19 | echo "Please restart server" 20 | -------------------------------------------------------------------------------- /etc/databases/dbc/updates/.nomedia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/etc/databases/dbc/updates/.nomedia -------------------------------------------------------------------------------- /etc/databases/realm/updates/.nomedia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/etc/databases/realm/updates/.nomedia -------------------------------------------------------------------------------- /etc/databases/world/custom/scarlet_monastery_teleporter.sql: -------------------------------------------------------------------------------- 1 | -- Scarlet Monastery Game Object Teleporter 2 | 3 | -- GO template. 4 | DELETE FROM `gameobject_template` WHERE `entry`=3000492; 5 | INSERT INTO `gameobject_template` VALUES (3000492 , 1, 327, "[CUSTOM] Scarlet Monastery Teleporter", 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ""); 6 | 7 | -- GO spawn entry. 8 | DELETE FROM `spawns_gameobjects` WHERE `spawn_entry`=3000492; 9 | INSERT INTO `spawns_gameobjects` VALUES (3998714, 3000492, 0, 2830.448, -697.662, 140, 3.53, 0, 0, 0, 0, 900, 900, 100, 1, 0, 0, 0); 10 | 11 | -- Teleport, reset GO active state. 12 | DELETE FROM `gameobject_scripts` WHERE `id`=3998714; 13 | INSERT INTO `gameobject_scripts` (`id`, `delay`, `priority`, `command`, `datalong`, `datalong2`, `datalong3`, `datalong4`, `target_param1`, `target_param2`, `target_type`, `data_flags`, `dataint`, `dataint2`, `dataint3`, `dataint4`, `x`, `y`, `z`, `o`, `condition_id`, `comments`) VALUES 14 | (3998714, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2851.201, -711.075, 144.4, 5.15, 0, 'Teleport To SM'), 15 | (3998714, 0, 0, 80, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'Set GO Ready.'); -------------------------------------------------------------------------------- /etc/databases/world/updates/.nomedia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/etc/databases/world/updates/.nomedia -------------------------------------------------------------------------------- /etc/docker/inotify/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM pstauffer/inotify 2 | 3 | WORKDIR /tmp 4 | 5 | ADD ./init.sh /init.sh 6 | RUN chmod 750 /init.sh 7 | 8 | CMD ["/init.sh"] 9 | -------------------------------------------------------------------------------- /etc/docker/inotify/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Script options (exit script on command fail). 5 | # 6 | set -e 7 | 8 | CURL_OPTIONS_DEFAULT= 9 | SIGNAL_DEFAULT="restart" 10 | INOTIFY_EVENTS_DEFAULT="create,delete,modify,move" 11 | INOTIFY_OPTONS_DEFAULT='--monitor --recursive --exclude=\.pyc|\.sql|\.sh' 12 | 13 | # 14 | # Display settings on standard out. 15 | # 16 | echo "inotify settings" 17 | echo "================" 18 | echo 19 | echo " Container: ${CONTAINER}" 20 | echo " Volumes: ${VOLUMES}" 21 | echo " Curl_Options: ${CURL_OPTIONS:=${CURL_OPTIONS_DEFAULT}}" 22 | echo " Signal: ${SIGNAL:=${SIGNAL_DEFAULT}}" 23 | echo " Inotify_Events: ${INOTIFY_EVENTS:=${INOTIFY_EVENTS_DEFAULT}}" 24 | echo " Inotify_Options: ${INOTIFY_OPTONS:=${INOTIFY_OPTONS_DEFAULT}}" 25 | echo 26 | 27 | # 28 | # Inotify part. 29 | # 30 | echo "[Starting inotifywait...]" 31 | inotifywait -e ${INOTIFY_EVENTS} ${INOTIFY_OPTONS} "${VOLUMES}" | \ 32 | while read -r notifies; 33 | do 34 | echo "$notifies" 35 | echo "notify received, sent signal ${SIGNAL} to container ${CONTAINER}" 36 | curl ${CURL_OPTIONS} -X POST --silent --unix-socket /tmp/docker.sock http://docker/containers/${CONTAINER}/${SIGNAL} & > /dev/stdout 2> /dev/stderr 37 | done 38 | -------------------------------------------------------------------------------- /etc/docker/main/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12.7-slim-bookworm 2 | 3 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 4 | 5 | WORKDIR /var/wow 6 | COPY requirements.txt . 7 | RUN pip3 install -r requirements.txt 8 | 9 | ENTRYPOINT ["python3", "main.py"] 10 | -------------------------------------------------------------------------------- /etc/docker/realm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12.7-slim-bookworm 2 | 3 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 4 | 5 | WORKDIR /var/wow 6 | COPY requirements.txt . 7 | RUN pip3 install -r requirements.txt 8 | 9 | ENTRYPOINT ["python3", "main.py", "-l", "realm"] 10 | 11 | -------------------------------------------------------------------------------- /etc/docker/sql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mariadb:11.3.2 2 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 3 | 4 | COPY etc/docker/sql/build_sql_patches.sh /docker-entrypoint-initdb.d/ 5 | RUN chmod -x /docker-entrypoint-initdb.d/build_sql_patches.sh 6 | -------------------------------------------------------------------------------- /etc/docker/sql/build_sql_patches.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function init_database() { 4 | SQL_DUMP='CREATE DATABASE `alpha_'${1}'` /*!40100 COLLATE utf8mb4_general_ci */;'$'\n' 5 | SQL_DUMP=${SQL_DUMP}'USE `alpha_'${1}'`;'$'\n' 6 | SQL_DUMP=${SQL_DUMP}$(cat /etc/databases/${1}/${1}.sql)$'\n' 7 | SQL_DUMP=${SQL_DUMP}$(cat /etc/databases/${1}/updates/*.sql) 8 | 9 | echo "${SQL_DUMP}" | docker_process_sql 10 | } 11 | 12 | init_database 'dbc' 13 | init_database 'realm' 14 | init_database 'world' 15 | -------------------------------------------------------------------------------- /etc/docker/world/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12.7-slim-bookworm 2 | 3 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 4 | 5 | WORKDIR /var/wow 6 | COPY requirements.txt . 7 | RUN pip3 install -r requirements.txt 8 | 9 | ENTRYPOINT ["python3", "main.py", "-l", "world"] 10 | -------------------------------------------------------------------------------- /etc/maps/.nomedia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/etc/maps/.nomedia -------------------------------------------------------------------------------- /etc/navs/.nomedia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/etc/navs/.nomedia -------------------------------------------------------------------------------- /game/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/__init__.py -------------------------------------------------------------------------------- /game/login/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/login/__init__.py -------------------------------------------------------------------------------- /game/realm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/realm/__init__.py -------------------------------------------------------------------------------- /game/update/UpdateSessionStateHandler.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | MAX_PACKET_BYTES = 4096 4 | 5 | 6 | class UpdateSessionStateHandler: 7 | def __init__(self, client_socket, client_address): 8 | self.client_socket = client_socket 9 | self.client_address = client_address 10 | 11 | # TODO: UpdateServer seems to use some kind of RSYNC protocol for file patching. 12 | def handle(self): 13 | self.disconnect() 14 | 15 | def disconnect(self): 16 | try: 17 | self.client_socket.shutdown(socket.SHUT_RDWR) 18 | self.client_socket.close() 19 | except OSError: 20 | pass 21 | -------------------------------------------------------------------------------- /game/update/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/update/__init__.py -------------------------------------------------------------------------------- /game/world/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/__init__.py -------------------------------------------------------------------------------- /game/world/managers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/__init__.py -------------------------------------------------------------------------------- /game/world/managers/abstractions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/abstractions/__init__.py -------------------------------------------------------------------------------- /game/world/managers/maps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/maps/__init__.py -------------------------------------------------------------------------------- /game/world/managers/maps/helpers/AreaInformation.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class AreaInformation(object): 6 | zone_id: int 7 | area_number: int 8 | flags: int 9 | level: int 10 | explore_bit: int 11 | -------------------------------------------------------------------------------- /game/world/managers/maps/helpers/CellUtils.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from utils.ConfigManager import config 4 | 5 | TOLERANCE = 0.00001 6 | CELL_SIZE = config.Server.Settings.cell_size 7 | VIEW_DISTANCE = config.Server.Settings.view_distance 8 | 9 | 10 | class CellUtils: 11 | 12 | @staticmethod 13 | def generate_coord_data(x, y): 14 | mod_x = x / CELL_SIZE 15 | mod_y = y / CELL_SIZE 16 | 17 | max_x = math.ceil(mod_x) * CELL_SIZE - TOLERANCE 18 | max_y = math.ceil(mod_y) * CELL_SIZE - TOLERANCE 19 | min_x = max_x - CELL_SIZE + TOLERANCE 20 | min_y = max_y - CELL_SIZE + TOLERANCE 21 | 22 | return min_x, min_y, max_x, max_y 23 | 24 | @staticmethod 25 | def get_cell_key(x, y, map_, instance_id): 26 | min_x, min_y, max_x, max_y = CellUtils.generate_coord_data(x, y) 27 | key = f'{round(min_x, 5)}:{round(min_y, 5)}:{round(max_x, 5)}:{round(max_y, 5)}:{map_}:{instance_id}' 28 | return key 29 | 30 | @staticmethod 31 | def get_cell_key_for_object(world_object): 32 | x = world_object.location.x 33 | y = world_object.location.y 34 | map_id = world_object.map_id 35 | instance_id = world_object.instance_id 36 | return CellUtils.get_cell_key(x, y, map_id, instance_id) 37 | -------------------------------------------------------------------------------- /game/world/managers/maps/helpers/Constants.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | RESOLUTION_ZMAP = 256 4 | RESOLUTION_LIQUIDS = 128 5 | RESOLUTION_AREA_INFO = 16 6 | ADT_SIZE = 533.3333 7 | BLOCK_SIZE = 64 8 | 9 | 10 | class MapType(IntEnum): 11 | INSTANCE = 0 12 | COMMON = 1 13 | -------------------------------------------------------------------------------- /game/world/managers/maps/helpers/InstanceToken.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class InstanceToken: 6 | id: int 7 | map_id: int 8 | -------------------------------------------------------------------------------- /game/world/managers/maps/helpers/LiquidInformation.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from utils.Float16 import Float16 3 | 4 | 5 | @dataclass 6 | class LiquidInformation(object): 7 | def __init__(self, liquid_type, height, float_16=False): 8 | self.liquid_type = liquid_type 9 | self._height = height 10 | self.float_16 = float_16 11 | 12 | def get_height(self): 13 | if not self.float_16: 14 | return self._height 15 | return Float16.decompress(self._height) 16 | -------------------------------------------------------------------------------- /game/world/managers/maps/helpers/MapUtils.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.maps.helpers.Constants import RESOLUTION_ZMAP, ADT_SIZE 2 | 3 | 4 | class MapUtils: 5 | 6 | @staticmethod 7 | def calculate_tile(x, y, resolution=RESOLUTION_ZMAP - 1): 8 | # Check both axis within boundaries. 9 | x = MapUtils._validate_map_coord(x) 10 | y = MapUtils._validate_map_coord(y) 11 | adt_x = int(32.0 - (x / ADT_SIZE)) 12 | adt_y = int(32.0 - (y / ADT_SIZE)) 13 | cell_x = int(round(resolution * (32.0 - (x / ADT_SIZE) - adt_x))) 14 | cell_y = int(round(resolution * (32.0 - (y / ADT_SIZE) - adt_y))) 15 | return adt_x, adt_y, cell_x, cell_y 16 | 17 | @staticmethod 18 | def get_tile(x, y): 19 | adt_x = int(32.0 - MapUtils._validate_map_coord(x) / ADT_SIZE) 20 | adt_y = int(32.0 - MapUtils._validate_map_coord(y) / ADT_SIZE) 21 | return [adt_x, adt_y] 22 | 23 | @staticmethod 24 | def _validate_map_coord(coord): 25 | if coord > 32.0 * ADT_SIZE: 26 | return 32.0 * ADT_SIZE 27 | elif coord < -32.0 * ADT_SIZE: 28 | return -32.0 * ADT_SIZE 29 | return coord 30 | 31 | @staticmethod 32 | def is_valid_position(x, y): 33 | if x > 32.0 * ADT_SIZE or y > 32.0 * ADT_SIZE: 34 | return False 35 | elif x < -32.0 * ADT_SIZE or y < -32.0 * ADT_SIZE: 36 | return False 37 | return True 38 | -------------------------------------------------------------------------------- /game/world/managers/maps/helpers/Mapbuild.py: -------------------------------------------------------------------------------- 1 | # Interface implemented in C++, to avoid IDE errors basically (not actually mandatory). 2 | class Mapbuild: 3 | 4 | # Builds all gameobjects. Must be called before build_map. 5 | def build_bvh(self, data_path, output_path, workers): 6 | pass 7 | 8 | # Builds a specific map. `build_bvh` must be called before this function. 9 | def build_map(self, data_path, output_path, map_name, threads, go_csv): 10 | pass 11 | 12 | # Build a specific ADT. 13 | def build_adt(self, data_path, output_path, map_name, x, y, go_csv): 14 | pass 15 | 16 | # Checks if map files exist. If `True` the map will not need to be built. 17 | def map_files_exist(self, output_path, map_name): 18 | pass 19 | 20 | # Checks if gameobjects exist. If `True` the gameobjects will not need to be built. 21 | def bvh_files_exist(self, output_path): 22 | pass 23 | -------------------------------------------------------------------------------- /game/world/managers/maps/helpers/Namigator.py: -------------------------------------------------------------------------------- 1 | # Interface implemented in C++, to avoid IDE errors basically (not actually mandatory). 2 | class Namigator: 3 | 4 | def query_heights(self, x, y): 5 | pass 6 | 7 | def query_z(self, x, y, z, stop_x, stop_y): 8 | pass 9 | 10 | def line_of_sight(self, start_x, start_y, start_z, end_x, end_y, end_z, doodads): 11 | pass 12 | 13 | def find_path(self, start_x, start_y, start_z, end_x, end_y, end_z): 14 | pass 15 | 16 | def has_adts(self): 17 | pass 18 | 19 | def adt_loaded(self, adt_x, adt_y): 20 | pass 21 | 22 | def load_all_adts(self): 23 | pass 24 | 25 | def load_adt(self, adt_x, adt_y): 26 | pass 27 | 28 | def unload_adt(self, adt_x, adt_y): 29 | pass 30 | 31 | def find_random_point_around_circle(self, start_x, start_y, start_z, radius): 32 | pass 33 | 34 | def find_point_in_between_vectors(self, distance, start_x, start_y, start_z, end_x, end_y, end_z): 35 | pass 36 | -------------------------------------------------------------------------------- /game/world/managers/maps/helpers/PendingTeleportDataHolder.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from game.world.managers.abstractions.Vector import Vector 3 | 4 | 5 | @dataclass 6 | class PendingTeleportDataHolder: 7 | recovery_percentage: float 8 | origin_location: Vector 9 | origin_map: int 10 | destination_location: Vector 11 | destination_map: int 12 | 13 | def is_long_teleport(self): 14 | return self.origin_map != self.destination_map 15 | -------------------------------------------------------------------------------- /game/world/managers/maps/helpers/ResurrectionRequestDataHolder.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from game.world.managers.abstractions.Vector import Vector 3 | 4 | 5 | @dataclass 6 | class ResurrectionRequestDataHolder: 7 | resuscitator_guid: int 8 | recovery_percentage: float 9 | resurrect_location: Vector 10 | resurrect_map: int 11 | -------------------------------------------------------------------------------- /game/world/managers/maps/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/maps/helpers/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/GuidManager.py: -------------------------------------------------------------------------------- 1 | class GuidManager: 2 | def __init__(self): 3 | self.current_guid = 0 4 | 5 | def get_new_guid(self): 6 | self.current_guid += 1 7 | return self.current_guid 8 | -------------------------------------------------------------------------------- /game/world/managers/objects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/ai/CritterAI.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.ai.CreatureAI import CreatureAI 2 | from utils.constants.CustomCodes import Permits 3 | 4 | 5 | class CritterAI(CreatureAI): 6 | def __init__(self, creature): 7 | super().__init__(creature) 8 | self.combat_timer = 0 9 | 10 | # override 11 | def update_ai(self, elapsed, now): 12 | pass 13 | 14 | # override 15 | def permissible(self, creature): 16 | if creature.is_critter(): 17 | return Permits.PERMIT_BASE_SPECIAL 18 | return Permits.PERMIT_BASE_NO 19 | 20 | # override 21 | def damage_taken(self, attacker, damage): 22 | pass 23 | 24 | # override 25 | def spell_hit(self, caster, casting_spell): 26 | pass 27 | -------------------------------------------------------------------------------- /game/world/managers/objects/ai/GameObjectAI.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.ai.CreatureAI import CreatureAI 2 | 3 | 4 | class GameObjectAI(CreatureAI): 5 | def __init__(self, creature): 6 | super().__init__(creature) 7 | 8 | # override 9 | def update_ai(self, elapsed, now): 10 | pass 11 | 12 | # override 13 | def permissible(self, creature): 14 | pass 15 | 16 | # override 17 | def set_data(self, _id, value): 18 | pass 19 | 20 | # override 21 | def on_use(self, user): 22 | pass 23 | 24 | # override 25 | def just_summoned(self, world_object): 26 | pass 27 | 28 | # override 29 | def summoned_creature_just_died(self, creature): 30 | pass 31 | 32 | # override 33 | def summoned_movement_inform(self, creature, motion_type, point_id): 34 | pass 35 | 36 | # override 37 | def on_remove_from_world(self): 38 | pass 39 | -------------------------------------------------------------------------------- /game/world/managers/objects/ai/GuardAI.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.ai.CreatureAI import CreatureAI 2 | from utils.constants.CustomCodes import Permits 3 | 4 | 5 | class GuardAI(CreatureAI): 6 | def __init__(self, creature): 7 | super().__init__(creature) 8 | 9 | # override 10 | def update_ai(self, elapsed, now): 11 | super().update_ai(elapsed, now) 12 | if not self.creature or not self.creature.combat_target: 13 | return 14 | 15 | if self.has_spell_list(): 16 | self.update_spell_list(elapsed) 17 | 18 | self.do_melee_attack_if_ready() 19 | 20 | # override 21 | def permissible(self, creature): 22 | if creature.is_guard(): 23 | return Permits.PERMIT_BASE_SPECIAL 24 | return Permits.PERMIT_BASE_NO 25 | 26 | # override 27 | def move_in_line_of_sight(self, unit, ai_event=False): 28 | super().move_in_line_of_sight(unit, ai_event=ai_event) 29 | if ai_event or not self.is_ready_for_new_attack(): 30 | return 31 | self.creature.object_ai.attacked_by(unit) 32 | 33 | # override 34 | def enter_combat(self, source=None): 35 | pass 36 | 37 | # Returns whether the Unit is currently attacking other players or friendly units. 38 | def is_attacking_friendly_or_player(self, unit): 39 | pass 40 | -------------------------------------------------------------------------------- /game/world/managers/objects/ai/NullCreatureAI.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.ai.CreatureAI import CreatureAI 2 | from utils.constants.CustomCodes import Permits 3 | 4 | 5 | class NullCreatureAI(CreatureAI): 6 | def __init__(self, creature): 7 | super().__init__(creature) 8 | 9 | # override 10 | def update_ai(self, elapsed, now): 11 | pass 12 | 13 | # override 14 | def move_in_line_of_sight(self, unit, ai_event=False): 15 | pass 16 | 17 | # override 18 | def attack_start(self, victim, chase=True): 19 | pass 20 | 21 | # override 22 | def attacked_by(self, attacker): 23 | pass 24 | 25 | # override 26 | def permissible(self, creature): 27 | return Permits.PERMIT_BASE_IDLE 28 | -------------------------------------------------------------------------------- /game/world/managers/objects/ai/TotemAI.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.ai.CreatureAI import CreatureAI 2 | from utils.constants.CustomCodes import Permits 3 | 4 | 5 | class TotemAI(CreatureAI): 6 | def __init__(self, creature): 7 | super().__init__(creature) 8 | 9 | # override 10 | def update_ai(self, elapsed, now): 11 | super().update_ai(elapsed, now) 12 | if not self.creature: 13 | return 14 | 15 | owner = self.creature.get_charmer_or_summoner() 16 | if not owner: 17 | return 18 | 19 | self.creature.threat_manager.holders = owner.threat_manager.holders.copy() 20 | 21 | if self.has_spell_list(): 22 | self.update_spell_list(elapsed) 23 | 24 | # override 25 | def permissible(self, creature): 26 | if creature.is_totem(): 27 | return Permits.PERMIT_BASE_SPECIAL 28 | return Permits.PERMIT_BASE_NO 29 | 30 | # override 31 | def just_despawned(self): 32 | self.just_died() # Handle same as dead for now. 33 | super().just_despawned() 34 | 35 | # override 36 | def move_in_line_of_sight(self, unit, ai_event=False): 37 | pass 38 | 39 | # override 40 | def handle_return_movement(self): 41 | pass 42 | 43 | # override 44 | def attack_start(self, victim, chase=True): 45 | pass 46 | -------------------------------------------------------------------------------- /game/world/managers/objects/ai/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/ai/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/corpse/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/corpse/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/dynamic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/dynamic/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/farsight/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/farsight/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/gameobjects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/gameobjects/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/gameobjects/managers/CameraManager.py: -------------------------------------------------------------------------------- 1 | from struct import pack 2 | 3 | from database.dbc.DbcDatabaseManager import DbcDatabaseManager 4 | from game.world.managers.objects.gameobjects.GameObjectManager import GameObjectManager 5 | from network.packet.PacketWriter import PacketWriter 6 | from utils.constants.OpCodes import OpCode 7 | 8 | 9 | class CameraManager(GameObjectManager): 10 | 11 | def __init__(self, **kwargs): 12 | super().__init__(**kwargs) 13 | self.lock = 0 14 | self.cinematic_id = 0 15 | self.event_id = 0 16 | 17 | # override 18 | def initialize_from_gameobject_template(self, gobject_template): 19 | super().initialize_from_gameobject_template(gobject_template) 20 | self.lock = self.get_data_field(0, int) 21 | self.cinematic_id = self.get_data_field(1, int) 22 | self.event_id = self.get_data_field(2, int) 23 | 24 | # override 25 | def use(self, player=None, target=None, from_script=False): 26 | if self.cinematic_id and player: 27 | if DbcDatabaseManager.cinematic_sequences_get_by_id(self.cinematic_id): 28 | packet = PacketWriter.get_packet(OpCode.SMSG_TRIGGER_CINEMATIC, pack(' 0 15 | self.min_delay = event.event_param1 / 1000 # Seconds. 16 | self.max_delay = event.event_param2 / 1000 # Seconds. 17 | self.min_repeat = event.event_param3 / 1000 # Seconds. 18 | self.max_repeat = event.event_param4 / 1000 # Seconds. 19 | 20 | def pick_scripts(self): 21 | if self.event_flags & EventFlags.RANDOM_ACTION: 22 | return [choice(self._scripts)] 23 | else: 24 | return list(self._scripts) 25 | 26 | def can_repeat(self): 27 | return self.event_flags & EventFlags.REPEATABLE and self.get_repeat_seconds() 28 | 29 | def get_repeat_seconds(self): 30 | return uniform(self.min_repeat, self.max_repeat) 31 | 32 | def get_delay_seconds(self): 33 | return uniform(self.min_delay, self.max_delay) 34 | 35 | def get_event_info(self): 36 | return f'Event id: {self.id}, Comment: {self.comment}' 37 | -------------------------------------------------------------------------------- /game/world/managers/objects/script/ScriptHelpers.py: -------------------------------------------------------------------------------- 1 | class ScriptHelpers: 2 | 3 | @staticmethod 4 | def get_filtered_dataint(script_command): 5 | return list(filter((0).__ne__, [script_command.dataint, script_command.dataint2, 6 | script_command.dataint3, script_command.dataint4])) 7 | 8 | @staticmethod 9 | def get_filtered_datalong(script_command): 10 | return list(filter((0).__ne__, [script_command.datalong, script_command.datalong2, 11 | script_command.datalong3, script_command.datalong4])) 12 | 13 | @staticmethod 14 | def get_filtered_event_scripts(event): 15 | return list(filter((0).__ne__, [event.action1_script, event.action2_script, event.action3_script])) 16 | -------------------------------------------------------------------------------- /game/world/managers/objects/script/ScriptedEventTarget.py: -------------------------------------------------------------------------------- 1 | class ScriptedEventTarget: 2 | def __init__(self, target, failure_condition, failure_script, success_condition, success_script): 3 | self.target = target 4 | self.failure_condition = failure_condition 5 | self.failure_script = failure_script 6 | self.success_condition = success_condition 7 | self.success_script = success_script 8 | -------------------------------------------------------------------------------- /game/world/managers/objects/script/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/script/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/spell/SpellEffectDummyHandler.py: -------------------------------------------------------------------------------- 1 | from utils.constants.MiscCodes import Emotes 2 | 3 | 4 | class SpellEffectDummyHandler: 5 | @staticmethod 6 | def handle_force_target_salute(casting_spell, effect, caster, target): 7 | if not target.is_unit(by_mask=True): 8 | return 9 | 10 | target.play_emote(Emotes.SALUTE) 11 | 12 | @staticmethod 13 | def handle_force_target_bow(casting_spell, effect, caster, target): 14 | if not target.is_unit(by_mask=True): 15 | return 16 | 17 | target.play_emote(Emotes.BOW) 18 | 19 | 20 | DUMMY_SPELL_EFFECTS = { 21 | 6245: SpellEffectDummyHandler.handle_force_target_salute, # Force Target - Salute 22 | 6655: SpellEffectDummyHandler.handle_force_target_bow # Force Target - Bow 23 | } 24 | -------------------------------------------------------------------------------- /game/world/managers/objects/spell/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/spell/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/spell/aura/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/spell/aura/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/timers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/timers/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/units/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/units/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/units/creature/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/units/creature/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/units/creature/groups/CreatureGroupMember.py: -------------------------------------------------------------------------------- 1 | from database.world.WorldModels import CreatureGroup 2 | from utils.Logger import Logger 3 | 4 | 5 | class CreatureGroupMember(object): 6 | def __init__(self, creature_mgr, creature_group: CreatureGroup, dist: int = 0, angle: int = 0, flags: int = 0): 7 | self.creature = creature_mgr 8 | self.distance_leader = dist if dist else creature_group.dist 9 | self.angle = angle if angle else creature_group.angle 10 | self.flags = flags if flags else creature_group.flags 11 | Logger.debug(f'Group member {creature_mgr.get_name()}, Distance {self.distance_leader}, Angle {self.angle},' 12 | f' Flags {self.flags}') 13 | -------------------------------------------------------------------------------- /game/world/managers/objects/units/creature/groups/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/units/creature/groups/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/units/creature/items/VirtualItemInfoHolder.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class VirtualItemInfoHolder: 6 | display_id: int = 0 7 | info_packed: int = 0 # ClassID, SubClassID, Material, InventoryType. 8 | info_packed_2: int = 0 # Sheath, Padding, Padding, Padding. 9 | -------------------------------------------------------------------------------- /game/world/managers/objects/units/creature/items/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/units/creature/items/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/units/creature/utils/CreatureUtils.py: -------------------------------------------------------------------------------- 1 | from random import choice 2 | 3 | 4 | class CreatureUtils: 5 | @staticmethod 6 | def generate_creature_display_id(creature_template): 7 | display_id_list = list(filter((0).__ne__, [creature_template.display_id1, 8 | creature_template.display_id2, 9 | creature_template.display_id3, 10 | creature_template.display_id4])) 11 | return choice(display_id_list) if len(display_id_list) > 0 else 4 # 4 = Shane Cube. 12 | -------------------------------------------------------------------------------- /game/world/managers/objects/units/creature/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/units/creature/utils/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/units/creature/vendors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/units/creature/vendors/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/units/movement/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/units/movement/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/units/movement/behaviors/DistractedMovement.py: -------------------------------------------------------------------------------- 1 | import time 2 | from utils.constants.MiscCodes import MoveType 3 | from game.world.managers.objects.units.movement.behaviors.BaseMovement import BaseMovement 4 | from utils.constants.UnitCodes import UnitStates 5 | 6 | 7 | # Distracted works alongside stunned, meaning splines are not updated, therefor we set state upon initialization 8 | # and remove upon behavior removal. 9 | class DistractedMovement(BaseMovement): 10 | def __init__(self, wait_time_secs, angle, spline_callback): 11 | super().__init__(move_type=MoveType.DISTRACTED, spline_callback=spline_callback) 12 | self.unit = None 13 | self.expected_timestamp = time.time() + wait_time_secs 14 | self.angle = angle 15 | 16 | # override 17 | def initialize(self, unit): 18 | super().initialize(unit) 19 | self.unit.set_unit_state(UnitStates.DISTRACTED, active=True) 20 | self.unit.movement_manager.face_angle(self.angle) 21 | return True 22 | 23 | # override 24 | def on_removed(self): 25 | self.unit.set_unit_state(UnitStates.DISTRACTED, active=False) 26 | if self.unit.is_unit() and not self.unit.has_wander_type(): 27 | self.unit.movement_manager.face_angle(self.unit.spawn_position.o) 28 | 29 | # override 30 | def can_remove(self): 31 | return not self.unit.is_alive or self.unit.in_combat or time.time() >= self.expected_timestamp 32 | -------------------------------------------------------------------------------- /game/world/managers/objects/units/movement/helpers/CommandMoveInfo.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from utils.constants.ScriptCodes import WaypointPathOrigin 3 | 4 | 5 | @dataclass 6 | class CommandMoveInfo: 7 | wp_source: WaypointPathOrigin 8 | start_point: int 9 | initial_delay: int 10 | repeat: bool 11 | overwrite_guid: int 12 | overwrite_entry: int 13 | -------------------------------------------------------------------------------- /game/world/managers/objects/units/movement/helpers/MovementWaypoint.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.abstractions.Vector import Vector 2 | 3 | 4 | class MovementWaypoint: 5 | def __init__(self, id_, x, y, z, orientation, wait_time_seconds, script_id=0): 6 | self.id = id_ 7 | self.script_id = script_id 8 | self.location = Vector(x, y, z) 9 | self.orientation = orientation 10 | self.wait_time_seconds = wait_time_seconds 11 | -------------------------------------------------------------------------------- /game/world/managers/objects/units/movement/helpers/PendingWaypoint.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.abstractions.Vector import Vector 2 | 3 | 4 | class PendingWaypoint: 5 | def __init__(self, spline, id_, expected_timestamp, location): 6 | self.spline = spline 7 | self.id_: int = id_ 8 | self.expected_timestamp: int = expected_timestamp 9 | self.location: Vector = location 10 | 11 | def is_complete(self, total_elapsed): 12 | return total_elapsed >= self.expected_timestamp 13 | -------------------------------------------------------------------------------- /game/world/managers/objects/units/movement/helpers/PetRangeMove.py: -------------------------------------------------------------------------------- 1 | class PetRangeMove: 2 | def __init__(self, target, range_, delay): 3 | self.target = target 4 | self.range_ = range_ 5 | self.delay = delay # Wait after reaching location. 6 | self.location = None 7 | -------------------------------------------------------------------------------- /game/world/managers/objects/units/movement/helpers/SplineEvent.py: -------------------------------------------------------------------------------- 1 | # TODO: Move this to a more generic event system out of MovementManager. 2 | class SplineEvent: 3 | def __init__(self, unit, start_seconds): 4 | self.source = unit 5 | self.start_seconds: int = start_seconds 6 | self.elapsed = 0 7 | self.triggered = False 8 | 9 | def update(self, elapsed): 10 | self.elapsed += elapsed 11 | if self.elapsed >= self.start_seconds: 12 | self.execute() 13 | 14 | def execute(self): 15 | self.triggered = True 16 | 17 | 18 | class SplineRestoreOrientationEvent(SplineEvent): 19 | def __init__(self, source, start_seconds): 20 | super().__init__(source, start_seconds) 21 | 22 | def execute(self): 23 | if not self.source.in_combat and self.source.is_alive and not self.source.is_moving(): 24 | self.source.movement_manager.face_angle(self.source.spawn_position.o) 25 | super().execute() 26 | 27 | 28 | class SplineTargetedEmoteEvent(SplineEvent): 29 | def __init__(self, source, target, start_seconds, emote): 30 | super().__init__(source, start_seconds) 31 | self.target = target 32 | self.emote = emote 33 | 34 | def execute(self): 35 | if not self.source.in_combat and self.source.is_alive and not self.source.is_moving(): 36 | self.source.movement_manager.face_target(self.target) 37 | self.source.play_emote(self.emote) 38 | super().execute() 39 | -------------------------------------------------------------------------------- /game/world/managers/objects/units/movement/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/units/movement/helpers/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/units/pet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/units/pet/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/units/player/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/units/player/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/units/player/guild/GuildPendingInvite.py: -------------------------------------------------------------------------------- 1 | class GuildPendingInvite(object): 2 | def __init__(self, inviter, invitee): 3 | self.inviter = inviter 4 | self.invitee = invitee 5 | -------------------------------------------------------------------------------- /game/world/managers/objects/units/player/guild/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/units/player/guild/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/units/player/quest/QuestMenu.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | from database.world.WorldModels import QuestTemplate 3 | from utils.constants.MiscCodes import QuestGiverStatus, QuestState 4 | 5 | 6 | class QuestMenu: 7 | class QuestMenuItem(NamedTuple): 8 | quest: QuestTemplate 9 | quest_giver_state: QuestGiverStatus 10 | quest_state: QuestState 11 | 12 | def __init__(self): 13 | self.items = {} 14 | 15 | def add_menu_item(self, quest, quest_giver_status, quest_state): 16 | self.items[quest.entry] = QuestMenu.QuestMenuItem( 17 | quest=quest, 18 | quest_giver_state=quest_giver_status, 19 | quest_state=quest_state 20 | ) 21 | 22 | def clear_menu(self): 23 | self.items.clear() 24 | -------------------------------------------------------------------------------- /game/world/managers/objects/units/player/quest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/units/player/quest/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/units/player/taxi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/units/player/taxi/__init__.py -------------------------------------------------------------------------------- /game/world/managers/objects/units/player/trade/ProposedEnchantment.py: -------------------------------------------------------------------------------- 1 | from utils.constants.ItemCodes import EnchantmentSlots 2 | 3 | 4 | class ProposedEnchantment: 5 | def __init__(self, trade_slot=-1, spell_id=0, enchant_slot=0, enchant_entry=0, duration=0, charges=0): 6 | self.trade_slot: int = trade_slot 7 | self.spell_id: int = spell_id 8 | self.enchantment_slot: EnchantmentSlots = EnchantmentSlots(enchant_slot) 9 | self.enchantment_entry: int = enchant_entry 10 | self.duration: int = duration 11 | self.charges: int = charges 12 | 13 | def set_enchantment(self, trade_slot, spell_id, enchant_slot, enchant_entry, duration, charges): 14 | self.trade_slot = trade_slot 15 | self.spell_id = spell_id 16 | self.enchantment_slot = enchant_slot 17 | self.enchantment_entry = enchant_entry 18 | self.duration = duration 19 | self.charges = charges 20 | 21 | def flush(self): 22 | self.trade_slot = -1 23 | self.spell_id = -1 24 | self.enchantment_slot = 0 # Used to apply enchantment after trade. 25 | self.enchantment_entry = 0 # ItemEnchantment id. 26 | self.duration = 0 27 | self.charges = 0 28 | 29 | def is_valid(self): 30 | return self.trade_slot != -1 31 | -------------------------------------------------------------------------------- /game/world/managers/objects/units/player/trade/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/managers/objects/units/player/trade/__init__.py -------------------------------------------------------------------------------- /game/world/opcode_handling/HandlerValidator.py: -------------------------------------------------------------------------------- 1 | from utils.Logger import Logger 2 | from utils.constants.OpCodes import OpCode 3 | 4 | 5 | class HandlerValidator: 6 | 7 | @staticmethod 8 | def validate_session(world_session, opcode, disconnect=True): 9 | if not world_session: 10 | Logger.error(f'OpCode {OpCode(opcode).name}, Session was None.') 11 | return None, -1 if disconnect else 0 12 | 13 | if not world_session.player_mgr: 14 | Logger.error(f'OpCode {OpCode(opcode).name}, Session: {world_session.client_address} had None PlayerMgr' 15 | f' instance. Disconnecting.') 16 | return None, -1 if disconnect else 0 17 | 18 | return world_session.player_mgr, 0 19 | -------------------------------------------------------------------------------- /game/world/opcode_handling/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/opcode_handling/__init__.py -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/NullHandler.py: -------------------------------------------------------------------------------- 1 | class NullHandler(object): 2 | 3 | @staticmethod 4 | def handle(world_session, reader): 5 | # Just silently ignore. 6 | return 1 7 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/opcode_handling/handlers/__init__.py -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/channel/ChannelAnnounceHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.player.ChannelManager import ChannelManager 2 | from network.packet.PacketReader import * 3 | from utils.constants.MiscCodes import ChannelNotifications 4 | 5 | 6 | class ChannelAnnounceHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader): 10 | channel_name = PacketReader.read_string(reader.data, 0).strip().capitalize() 11 | 12 | channel = ChannelManager.get_channel(channel_name, world_session.player_mgr) 13 | # Check if channel exists. 14 | if not channel: 15 | packet = ChannelManager.build_notify_packet(channel_name, ChannelNotifications.NOT_MEMBER) 16 | ChannelManager.send_to_player(world_session.player_mgr, packet) 17 | return 0 18 | 19 | ChannelManager.toggle_announce(channel, world_session.player_mgr) 20 | 21 | return 0 22 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/channel/ChannelJoinHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.player.ChannelManager import ChannelManager 2 | from network.packet.PacketReader import * 3 | 4 | 5 | class ChannelJoinHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader): 9 | channel = PacketReader.read_string(reader.data, 0).strip().capitalize() 10 | offset = len(channel) + 1 11 | skip_pass = len(reader.data) == offset + 1 12 | password = '' if skip_pass else PacketReader.read_string(reader.data, offset, 0).strip() 13 | ChannelManager.join_channel(world_session.player_mgr, channel, password) 14 | 15 | return 0 16 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/channel/ChannelLeaveHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.player.ChannelManager import ChannelManager 2 | from network.packet.PacketReader import * 3 | from utils.constants.MiscCodes import ChannelNotifications 4 | 5 | 6 | class ChannelLeaveHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader): 10 | channel_name = PacketReader.read_string(reader.data, 0).capitalize() 11 | 12 | channel = ChannelManager.get_channel(channel_name, world_session.player_mgr) 13 | # Check if channel exists. 14 | if not channel: 15 | packet = ChannelManager.build_notify_packet(channel_name, ChannelNotifications.NOT_MEMBER) 16 | ChannelManager.send_to_player(world_session.player_mgr, packet) 17 | return 0 18 | 19 | ChannelManager.leave_channel(world_session.player_mgr, channel) 20 | 21 | return 0 22 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/channel/ChannelListHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.player.ChannelManager import ChannelManager 2 | from network.packet.PacketReader import * 3 | from utils.constants.MiscCodes import ChannelNotifications 4 | 5 | 6 | class ChannelListHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader): 10 | channel_name = PacketReader.read_string(reader.data, 0).strip().capitalize() 11 | 12 | channel = ChannelManager.get_channel(channel_name, world_session.player_mgr) 13 | # Check if channel exists. 14 | if not channel: 15 | packet = ChannelManager.build_notify_packet(channel_name, ChannelNotifications.NOT_MEMBER) 16 | ChannelManager.send_to_player(world_session.player_mgr, packet) 17 | return 0 18 | 19 | ChannelManager.send_channel_members_list(channel, world_session.player_mgr) 20 | 21 | return 0 22 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/channel/ChannelPasswordHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.player.ChannelManager import ChannelManager 2 | from network.packet.PacketReader import * 3 | from utils.constants.MiscCodes import ChannelNotifications 4 | 5 | 6 | class ChannelPasswordHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader): 10 | channel_name = PacketReader.read_string(reader.data, 0).strip().capitalize() 11 | offset = len(channel_name) + 1 12 | skip_pass = len(reader.data) == offset + 1 13 | password = '' if skip_pass else PacketReader.read_string(reader.data, offset, 0).strip() 14 | 15 | channel = ChannelManager.get_channel(channel_name, world_session.player_mgr) 16 | # Check if channel exists. 17 | if not channel: 18 | packet = ChannelManager.build_notify_packet(channel_name, ChannelNotifications.NOT_MEMBER) 19 | ChannelManager.send_to_player(world_session.player_mgr, packet) 20 | return 0 21 | 22 | ChannelManager.set_password(channel, world_session.player_mgr, password) 23 | 24 | return 0 25 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/channel/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/opcode_handling/handlers/channel/__init__.py -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/combat/AttackSwingHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | 4 | class AttackSwingHandler(object): 5 | 6 | @staticmethod 7 | def handle(world_session, reader): 8 | if len(reader.data) >= 8: # Avoid handling empty attack swing packet. 9 | enemy_guid = unpack('= 8: # Avoid handling empty friend delete packet. 9 | guid = unpack('= 8: # Avoid handling empty friend delete ignore packet. 9 | guid = unpack('= 12: # Avoid handling empty gameobject query packet. 11 | entry, guid = unpack(' 0: 13 | player_mgr = world_session.player_mgr 14 | gobject_mgr = player_mgr.get_map().get_surrounding_gameobject_by_guid(player_mgr, guid) 15 | if gobject_mgr: 16 | player_mgr.enqueue_packet(gobject_mgr.get_query_details_packet()) 17 | else: # Fallback just in case. 18 | go_template = WorldDatabaseManager.GameobjectTemplateHolder.gameobject_get_by_entry(entry) 19 | if go_template: 20 | player_mgr.enqueue_packet(ObjectQueryUtils.get_query_details_data(template=go_template)) 21 | return 0 22 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/gameobject/GameobjUseHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | from utils.constants.MiscCodes import GameObjectTypes 3 | 4 | 5 | class GameobjUseHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader): 9 | if len(reader.data) >= 8: # Avoid handling empty gameobj use packet. 10 | guid = unpack(' 0: 12 | gobject = world_session.player_mgr.get_map().get_surrounding_gameobject_by_guid( 13 | world_session.player_mgr, guid) 14 | if gobject: 15 | if gobject.gobject_template.type != GameObjectTypes.TYPE_GENERIC: 16 | gobject.use(world_session.player_mgr, target=gobject) 17 | 18 | return 0 19 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/gameobject/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/opcode_handling/handlers/gameobject/__init__.py -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/group/GroupDisbandHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.player.GroupManager import GroupManager 2 | from utils.constants.GroupCodes import PartyOperations, PartyResults 3 | 4 | 5 | class GroupDisbandHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader): 9 | if not world_session.player_mgr.group_manager: 10 | GroupManager.send_group_operation_result(world_session.player_mgr, PartyOperations.PARTY_OP_LEAVE, '', 11 | PartyResults.ERR_NOT_IN_GROUP) 12 | else: 13 | world_session.player_mgr.group_manager.leave_party(world_session.player_mgr.guid) 14 | 15 | return 0 16 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/group/GroupInviteAcceptHandler.py: -------------------------------------------------------------------------------- 1 | class GroupInviteAcceptHandler(object): 2 | 3 | @staticmethod 4 | def handle(world_session, reader): 5 | if not world_session.player_mgr.group_manager: 6 | return 0 7 | 8 | world_session.player_mgr.group_manager.remove_member_invite(world_session.player_mgr.guid) 9 | world_session.player_mgr.group_manager.try_add_member(world_session.player_mgr, False) 10 | 11 | return 0 12 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/group/GroupInviteDeclineHandler.py: -------------------------------------------------------------------------------- 1 | class GroupInviteDeclineHandler(object): 2 | 3 | @staticmethod 4 | def handle(world_session, reader): 5 | player = world_session.player_mgr 6 | if not player.group_manager: 7 | return 0 8 | 9 | if player.guid != player.group_manager.group.leader_guid: 10 | player.group_manager.send_invite_decline(player.get_name()) 11 | 12 | player.group_manager.remove_invitation(player.guid) 13 | 14 | return 0 15 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/group/MinimapPingHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | 4 | class MinimapPingHandler(object): 5 | 6 | @staticmethod 7 | def handle(world_session, reader): 8 | if len(reader.data) >= 8: # Avoid handling empty minimap ping packet. 9 | x, y = unpack('<2f', reader.data[:8]) 10 | 11 | if world_session.player_mgr and world_session.player_mgr.group_manager: 12 | world_session.player_mgr.group_manager.send_minimap_ping(world_session.player_mgr.guid, x, y) 13 | 14 | return 0 15 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/group/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/opcode_handling/handlers/group/__init__.py -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/guild/GuildCreateHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.player.guild.GuildManager import GuildManager 2 | from network.packet.PacketReader import * 3 | 4 | 5 | class GuildCreateHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader): 9 | if len(reader.data) > 1: # Avoid handling empty Guild Create packet. 10 | if not world_session.account_mgr.is_gm(): 11 | return 0 12 | guild_name = PacketReader.read_string(reader.data, 0).strip() 13 | player_mgr = world_session.player_mgr 14 | GuildManager.create_guild(player_mgr, guild_name) 15 | 16 | return 0 17 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/guild/GuildDisbandHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.player.guild.GuildManager import GuildManager 2 | from utils.constants.MiscCodes import GuildCommandResults, GuildTypeCommand, GuildRank 3 | 4 | 5 | class GuildDisbandHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader): 9 | player_mgr = world_session.player_mgr 10 | 11 | if not player_mgr.guild_manager: 12 | GuildManager.send_guild_command_result(player_mgr, GuildTypeCommand.GUILD_CREATE_S, '', 13 | GuildCommandResults.GUILD_PLAYER_NOT_IN_GUILD) 14 | elif player_mgr.guild_manager.get_rank(player_mgr.guid) != GuildRank.GUILDRANK_GUILD_MASTER: 15 | GuildManager.send_guild_command_result(player_mgr, GuildTypeCommand.GUILD_FOUNDER_S, '', 16 | GuildCommandResults.GUILD_PERMISSIONS) 17 | else: 18 | player_mgr.guild_manager.disband() 19 | 20 | return 0 21 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/guild/GuildInviteAcceptHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.player.guild.GuildManager import GuildManager 2 | from utils.constants.MiscCodes import GuildCommandResults, GuildTypeCommand 3 | 4 | 5 | class GuildInviteAcceptHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader): 9 | player_mgr = world_session.player_mgr 10 | 11 | if player_mgr.guid in GuildManager.PENDING_INVITES: 12 | inviter = GuildManager.PENDING_INVITES[player_mgr.guid].inviter 13 | GuildManager.PENDING_INVITES.pop(player_mgr.guid) 14 | if inviter and inviter.guild_manager: # Invited could have left right after sending the invite. 15 | inviter.guild_manager.add_new_member(player_mgr) 16 | else: 17 | GuildManager.send_guild_command_result(player_mgr, GuildTypeCommand.GUILD_INVITE_S, '', 18 | GuildCommandResults.GUILD_INTERNAL) 19 | 20 | return 0 21 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/guild/GuildInviteDeclineHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.player.guild.GuildManager import GuildManager 2 | from network.packet.PacketWriter import * 3 | from utils.constants.OpCodes import OpCode 4 | 5 | 6 | class GuildInviteDeclineHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader): 10 | player_mgr = world_session.player_mgr 11 | 12 | if player_mgr.guid in GuildManager.PENDING_INVITES: 13 | inviter = GuildManager.PENDING_INVITES[player_mgr.guid].inviter 14 | GuildManager.PENDING_INVITES.pop(player_mgr.guid) 15 | 16 | inviter_name_bytes = PacketWriter.string_to_bytes(inviter.get_name()) 17 | data = pack( 18 | f'<{len(inviter_name_bytes)}s', 19 | inviter_name_bytes 20 | ) 21 | 22 | inviter.enqueue_packet(PacketWriter.get_packet(OpCode.SMSG_GUILD_DECLINE, data)) 23 | 24 | return 0 25 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/guild/GuildLeaveHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.player.guild.GuildManager import GuildManager 2 | from utils.constants.MiscCodes import GuildCommandResults, GuildTypeCommand, GuildRank 3 | 4 | 5 | class GuildLeaveHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader): 9 | player = world_session.player_mgr 10 | 11 | if not player.guild_manager: 12 | GuildManager.send_guild_command_result(player, GuildTypeCommand.GUILD_INVITE_S, '', 13 | GuildCommandResults.GUILD_PLAYER_NOT_IN_GUILD) 14 | elif player.guild_manager.get_rank(player.guid) == GuildRank.GUILDRANK_GUILD_MASTER and player.guild_manager.has_members(): # GM should use disband. 15 | GuildManager.send_guild_command_result(player, GuildTypeCommand.GUILD_QUIT_S, '', 16 | GuildCommandResults.GUILD_LEADER_LEAVE) 17 | elif player.guild_manager.get_rank(player.guid) == GuildRank.GUILDRANK_GUILD_MASTER and not player.guild_manager.has_members(): 18 | player.guild_manager.disband() 19 | else: 20 | player.guild_manager.leave(player.guid) 21 | 22 | return 0 23 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/guild/GuildQueryHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.player.guild.GuildManager import GuildManager 2 | from network.packet.PacketReader import * 3 | 4 | 5 | class GuildQueryHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader): 9 | if len(reader.data) > 1: # Avoid handling empty Guild Query packet. 10 | # No ranks/permissions on 0.5.3 client. 11 | guild_id = unpack('<1I', reader.data[:4])[0] 12 | player = world_session.player_mgr 13 | 14 | for guild_manager in GuildManager.GUILDS.values(): 15 | if guild_manager.guild.guild_id == guild_id: 16 | if player: 17 | player.enqueue_packet(guild_manager.build_guild_query()) 18 | else: # This opcode is requested by char enum if there is no guild cache on client. 19 | world_session.enqueue_packet(guild_manager.build_guild_query()) 20 | 21 | return 0 22 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/guild/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/opcode_handling/handlers/guild/__init__.py -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/interface/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/opcode_handling/handlers/interface/__init__.py -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/inventory/DestroyItemHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 4 | from utils.constants.ItemCodes import InventoryError, InventorySlots 5 | 6 | 7 | class DestroyItemHandler(object): 8 | 9 | @staticmethod 10 | def handle(world_session, reader): 11 | # Validate world session. 12 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 13 | if not player_mgr: 14 | return res 15 | 16 | if len(reader.data) >= 3: # Avoid handling empty destroy item packet. 17 | bag, source_slot, count = unpack('<3B', reader.data[:3]) 18 | 19 | if bag == 0xFF: 20 | bag = InventorySlots.SLOT_INBACKPACK.value 21 | 22 | item = player_mgr.inventory.get_item(bag, source_slot) 23 | if not item: 24 | return 0 25 | 26 | if item.is_container() and not item.is_empty(): 27 | player_mgr.inventory.send_equip_error(InventoryError.BAG_NOT_EMPTY, item) 28 | return 0 29 | 30 | player_mgr.inventory.remove_item(bag, source_slot, clear_slot=True) 31 | 32 | return 0 33 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/inventory/ItemQueryMultipleHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | from database.world.WorldDatabaseManager import WorldDatabaseManager 3 | from game.world.managers.objects.item.ItemManager import ItemManager 4 | 5 | 6 | class ItemQueryMultipleHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader): 10 | if len(reader.data) >= 4: # Avoid handling empty multiple item query packet. 11 | requested_item_count = unpack('= 4: # Avoid handling empty item query packet. 13 | entry = unpack(' 0: 15 | item_template = WorldDatabaseManager.ItemTemplateHolder.item_template_get_by_entry(entry) 16 | if item_template: 17 | query_data = ObjectQueryUtils.get_query_details_data(template=item_template) 18 | query_packet = PacketWriter.get_packet(OpCode.SMSG_ITEM_QUERY_SINGLE_RESPONSE, query_data) 19 | world_session.enqueue_packet(query_packet) 20 | 21 | return 0 22 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/inventory/ReadItemHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | from network.packet.PacketWriter import * 4 | from utils.constants.ItemCodes import InventoryError, InventorySlots 5 | from utils.constants.OpCodes import OpCode 6 | 7 | 8 | class ReadItemHandler(object): 9 | 10 | @staticmethod 11 | def handle(world_session, reader): 12 | if len(reader.data) >= 2: # Avoid handling empty read item packet. 13 | bag, slot = unpack('<2B', reader.data[:2]) 14 | if bag == 0xFF: 15 | bag = InventorySlots.SLOT_INBACKPACK.value 16 | item = world_session.player_mgr.inventory.get_item(bag, slot) 17 | 18 | result = InventoryError.BAG_ITEM_NOT_FOUND 19 | if item: 20 | result = world_session.player_mgr.inventory.can_use_item(item.item_template) 21 | 22 | if result == InventoryError.BAG_OK: 23 | data = pack('<2Q', item.guid, item.guid) 24 | world_session.enqueue_packet(PacketWriter.get_packet(OpCode.SMSG_READ_ITEM_OK, data)) 25 | else: 26 | world_session.player_mgr.inventory.send_equip_error(result) 27 | data = pack('= 2: # Avoid handling empty swap inv item packet. 11 | source_slot, dest_slot = unpack('<2B', reader.data[:2]) 12 | bag = InventorySlots.SLOT_INBACKPACK.value 13 | inventory = world_session.player_mgr.inventory 14 | 15 | world_session.player_mgr.inventory.swap_item(bag, source_slot, bag, dest_slot) 16 | 17 | return 0 18 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/inventory/SwapItemHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | from utils.constants.ItemCodes import InventorySlots 3 | 4 | 5 | class SwapItemHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader): 9 | if len(reader.data) >= 4: # Avoid handling empty swap item packet. 10 | dest_bag, dest_slot, source_bag, source_slot = unpack('<4B', reader.data[:4]) 11 | inventory = world_session.player_mgr.inventory 12 | 13 | if dest_bag == 0xFF: 14 | dest_bag = InventorySlots.SLOT_INBACKPACK.value 15 | if source_bag == 0xFF: 16 | source_bag = InventorySlots.SLOT_INBACKPACK.value 17 | 18 | world_session.player_mgr.inventory.swap_item(source_bag, source_slot, dest_bag, dest_slot) 19 | 20 | return 0 21 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/inventory/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/opcode_handling/handlers/inventory/__init__.py -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/loot/LootReleaseHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | 4 | class LootReleaseHandler(object): 5 | 6 | @staticmethod 7 | def handle(world_session, reader): 8 | if len(reader.data) >= 8: # Avoid handling empty loot release packet. 9 | guid = unpack('= 8: # Avoid handling empty loot packet. 10 | loot_target_guid = unpack('= 8: # Avoid handling empty banker activate packet. 12 | guid = unpack('= 8: # Avoid handling empty binder activate packet. 16 | binder_guid = unpack(' Formulas.Distances.MAX_BIND_DISTANCE: 19 | return 0 20 | 21 | if binder.get_low_guid() == world_session.player_mgr.deathbind.creature_binder_guid: 22 | world_session.enqueue_packet(PacketWriter.get_packet(OpCode.SMSG_PLAYERBINDERROR)) 23 | else: 24 | binder.spell_manager.handle_cast_attempt(BIND_SPELL, world_session.player_mgr, 25 | SpellTargetMask.UNIT, validate=False) 26 | 27 | return 0 28 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/npc/BuyBankSlotHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | from database.dbc.DbcDatabaseManager import DbcDatabaseManager 4 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 5 | 6 | 7 | class BuyBankSlotHandler(object): 8 | 9 | @staticmethod 10 | def handle(world_session, reader): 11 | # Validate world session. 12 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 13 | if not player_mgr: 14 | return res 15 | 16 | if len(reader.data) >= 8: # Avoid handling empty buy bank slot packet. 17 | guid = unpack('= slot_cost: 24 | player_mgr.add_bank_slot(slot_cost) 25 | return 0 26 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/npc/BuyItemHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | from game.world.managers.objects.units.creature.utils.VendorUtils import VendorUtils 3 | 4 | 5 | class BuyItemHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader): 9 | if len(reader.data) >= 13: # Avoid handling empty buy item packet. 10 | vendor_guid, item, count, auto_equip = unpack(' 0: 13 | VendorUtils.handle_buy_item(world_session.player_mgr, vendor_guid, item, count) 14 | 15 | return 0 16 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/npc/BuyItemInSlotHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | from game.world.managers.objects.units.creature.utils.VendorUtils import VendorUtils 3 | 4 | 5 | class BuyItemInSlotHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader): 9 | if len(reader.data) >= 22: # Avoid handling empty buy item packet. 10 | vendor_guid, item, bag_guid, slot, count = unpack(' 0: 12 | VendorUtils.handle_buy_item(world_session.player_mgr, vendor_guid, item, count, bag_guid, slot) 13 | 14 | return 0 15 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/npc/CreatureQueryHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | from database.world.WorldDatabaseManager import WorldDatabaseManager 3 | from utils.ObjectQueryUtils import ObjectQueryUtils 4 | 5 | 6 | class CreatureQueryHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader): 10 | if len(reader.data) >= 12: # Avoid handling empty creature query packet. 11 | entry, guid = unpack('= 12: # Avoid handling empty petition query packet. 11 | petition_id, petition_item_guid = unpack('= 8: # Avoid handling empty petition show signatures packet. 11 | petition_item_guid = unpack('= 8: # Avoid handling empty petition showlist packet. 14 | guid = unpack(' 0: 16 | data = pack( 17 | '= 8: # Avoid handling empty petition sign packet. 12 | petition_guid = unpack(' 0: 15 | petition = PetitionManager.get_petition(petition_guid) 16 | petition_owner = WorldSessionStateHandler.find_player_by_guid(petition.owner_guid) 17 | if petition and petition_owner: 18 | PetitionManager.sign_petition(petition, world_session.player_mgr, petition_owner) 19 | 20 | return 0 21 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/npc/PetitionTurnInHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | from game.world.managers.objects.units.player.guild.PetitionManager import PetitionManager 4 | from utils.Logger import Logger 5 | 6 | 7 | class PetitionTurnInHandler(object): 8 | 9 | @staticmethod 10 | def handle(world_session, reader): 11 | if len(reader.data) >= 8: # Avoid handling empty petition turn in packet. 12 | petition_item_guid = unpack(' 0: 15 | petition = PetitionManager.get_petition(petition_item_guid) 16 | if not petition: 17 | Logger.error(f'Invalid guild petition turn in, guid {petition_item_guid}.') 18 | return 0 19 | PetitionManager.turn_in_petition(world_session.player_mgr, petition.owner_guid, petition) 20 | 21 | return 0 22 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/npc/TabardVendorActivateHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack, pack 2 | 3 | from network.packet.PacketWriter import PacketWriter 4 | from utils.constants.OpCodes import OpCode 5 | 6 | 7 | class TabardVendorActivateHandler(object): 8 | 9 | @staticmethod 10 | def handle(world_session, reader): 11 | if len(reader.data) >= 8: # Avoid handling empty tabard vendor activate packet. 12 | guid = unpack(' 0: 14 | data = pack('= 8: # Avoid handling empty taxi node status query packet. 11 | guid = unpack(' int: 8 | if len(reader.data) >= 8: # Avoid handling empty pet abandon packet. 9 | pet_guid = unpack(' int: 8 | if len(reader.data) >= 20: # Avoid handling empty pet action packet. 9 | pet_guid, action, target_guid = unpack(' int: 10 | if len(reader.data) >= 12: # Avoid handling empty pet name query packet. 11 | pet_id, pet_guid = unpack(' int: 10 | if len(reader.data) >= 8: # Avoid handling empty pet rename packet. 11 | pet_guid = unpack(' int: 8 | if len(reader.data) >= 16: # Avoid handling empty pet action packet. 9 | count = 2 if len(reader.data) == 24 else 1 # Client will append a 2nd pair of slot/action data when swapping. 10 | pet_guid = unpack(' int: 9 | # Validate world session. 10 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 11 | if not player_mgr: 12 | return res 13 | 14 | player_mgr.logout() 15 | return 0 16 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/DuelAcceptHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 2 | from network.packet.PacketReader import PacketReader 3 | from utils.Logger import Logger 4 | 5 | 6 | class DuelAcceptHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader: PacketReader) -> int: 10 | # Validate world session. 11 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 12 | if not player_mgr: 13 | return res 14 | 15 | duel_arbiter = player_mgr.get_duel_arbiter() 16 | if duel_arbiter: 17 | duel_arbiter.handle_duel_accept(player_mgr) 18 | else: 19 | Logger.warning(f'Unable to locate duel arbiter. {reader.opcode_str()}') 20 | 21 | return 0 22 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/DuelCanceledHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 2 | from network.packet.PacketReader import PacketReader 3 | from utils.Logger import Logger 4 | 5 | 6 | class DuelCanceledHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader: PacketReader) -> int: 10 | # Validate world session. 11 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 12 | if not player_mgr: 13 | return res 14 | 15 | # You can trigger cancel by using /yield without being in a duel. 16 | duel_arbiter = player_mgr.get_duel_arbiter() 17 | if duel_arbiter: 18 | duel_arbiter.handle_duel_canceled(player_mgr) 19 | else: 20 | Logger.warning(f'Unable to locate duel arbiter. {reader.opcode_str()}') 21 | 22 | return 0 23 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/GetDeathBindPointHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 2 | from network.packet.PacketReader import PacketReader 3 | from network.packet.PacketWriter import * 4 | from utils.constants.OpCodes import OpCode 5 | 6 | 7 | class GetDeathBindPointHandler(object): 8 | 9 | @staticmethod 10 | def handle(world_session, reader: PacketReader) -> int: 11 | # Validate world session. 12 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 13 | if not player_mgr: 14 | return res 15 | 16 | if player_mgr.deathbind: 17 | area_number = player_mgr.get_map().get_area_number_by_zone_id(player_mgr.deathbind.deathbind_zone) 18 | data = pack('<2I', player_mgr.map_id, area_number) 19 | packet = PacketWriter.get_packet(OpCode.SMSG_BINDZONEREPLY, data) 20 | player_mgr.enqueue_packet(packet) 21 | 22 | return 0 23 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/InspectHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | from game.world.managers.objects.units.player.PlayerManager import PlayerManager 4 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 5 | from network.packet.PacketReader import PacketReader 6 | from network.packet.PacketWriter import * 7 | from utils.constants.OpCodes import OpCode 8 | 9 | 10 | class InspectHandler(object): 11 | 12 | @staticmethod 13 | def handle(world_session, reader: PacketReader) -> int: 14 | # Validate world session. 15 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 16 | if not player_mgr: 17 | return res 18 | 19 | if len(reader.data) >= 8: # Avoid handling empty inspect packet. 20 | guid = unpack(' 0: 22 | inspected_player: PlayerManager = player_mgr.get_map().get_surrounding_player_by_guid(player_mgr, guid) 23 | if not inspected_player or not inspected_player.is_alive: 24 | return 0 25 | 26 | player_mgr.set_current_selection(guid) 27 | data = pack(' int: 12 | # Validate world session. 13 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 14 | if not player_mgr: 15 | return res 16 | 17 | player_mgr.set_stand_state(StandState.UNIT_STANDING) 18 | player_mgr.set_rooted(False) 19 | player_mgr.logout_timer = -1 20 | player_mgr.enqueue_packet(PacketWriter.get_packet(OpCode.SMSG_LOGOUT_CANCEL_ACK)) 21 | 22 | return 0 23 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/LogoutRequestHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 2 | from network.packet.PacketReader import PacketReader 3 | from network.packet.PacketWriter import * 4 | from utils.constants.MiscCodes import LogoutResponseCodes 5 | from utils.constants.OpCodes import OpCode 6 | from utils.constants.UnitCodes import StandState 7 | 8 | 9 | class LogoutRequestHandler(object): 10 | 11 | @staticmethod 12 | def handle(world_session, reader: PacketReader) -> int: 13 | # Validate world session. 14 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 15 | if not player_mgr: 16 | return res 17 | 18 | if player_mgr.in_combat: 19 | res = LogoutResponseCodes.LOGOUT_CANCEL 20 | else: 21 | res = LogoutResponseCodes.LOGOUT_PROCEED 22 | if not player_mgr.is_swimming(): 23 | player_mgr.set_stand_state(StandState.UNIT_SITTING) 24 | player_mgr.set_rooted(True) 25 | player_mgr.logout_timer = 20 26 | player_mgr.enqueue_packet(PacketWriter.get_packet(OpCode.SMSG_LOGOUT_RESPONSE, pack(' int: 10 | # Validate world session. 11 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 12 | if not player_mgr: 13 | return res 14 | 15 | # TODO Not working, wrong packet data, or animation not implemented client side? 16 | player_guid = unpack('= 8: # Avoid handling empty new spell slot packet. 17 | # Abilities index < 0 18 | # Spells index > 0 19 | # No zero, reserved for 'hidden' spells/abilities. 20 | spell, index = unpack(' int: 11 | if len(reader.data) >= 4: # Avoid handling empty ping packet. 12 | if world_session.player_mgr and world_session.player_mgr.online: 13 | world_session.player_mgr.last_ping = time.time() 14 | world_session.enqueue_packet(PacketWriter.get_packet(OpCode.SMSG_PONG, reader.data)) 15 | 16 | return 0 17 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/PlayedTimeHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 2 | from network.packet.PacketReader import * 3 | from network.packet.PacketWriter import * 4 | 5 | 6 | class PlayedTimeHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader: PacketReader) -> int: 10 | # Validate world session. 11 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 12 | if not player_mgr: 13 | return res 14 | 15 | # In seconds 16 | data = pack('<2I', 17 | int(player_mgr.player.totaltime), 18 | int(player_mgr.player.leveltime)) 19 | player_mgr.enqueue_packet(PacketWriter.get_packet(OpCode.SMSG_PLAYED_TIME, data)) 20 | 21 | return 0 22 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/PlayerLogoutHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 2 | from network.packet.PacketReader import * 3 | 4 | 5 | class PlayerLogoutHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader: PacketReader) -> int: 9 | # Validate world session. 10 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 11 | if not player_mgr: 12 | return res 13 | 14 | player_mgr.logout() 15 | 16 | return 0 17 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/PvPPortHandler.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from database.world.WorldDatabaseManager import WorldDatabaseManager 4 | from game.world.managers.abstractions.Vector import Vector 5 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 6 | from network.packet.PacketReader import PacketReader 7 | 8 | 9 | class PvPPortHandler(object): 10 | 11 | @staticmethod 12 | def handle(world_session, reader: PacketReader) -> int: 13 | # Validate world session. 14 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 15 | if not player_mgr: 16 | return res 17 | 18 | # Only two maps flagged as PvP exist in 0.5.3: PvPZone01 and PvPZone02. 19 | pvp_map = random.randint(1, 2) 20 | location = WorldDatabaseManager.worldport_get_by_name(f'PvPZone0{pvp_map}') 21 | 22 | if location: 23 | tel_location = Vector(location.x, location.y, location.z, location.o) 24 | player_mgr.teleport(location.map, tel_location) 25 | 26 | return 0 27 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/RandomRollHandler.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 4 | from network.packet.PacketReader import * 5 | from network.packet.PacketWriter import * 6 | 7 | 8 | class RandomRollHandler(object): 9 | 10 | @staticmethod 11 | def handle(world_session, reader: PacketReader) -> int: 12 | # Validate world session. 13 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 14 | if not player_mgr: 15 | return res 16 | 17 | if len(reader.data) >= 8: # Avoid handling empty random roll packet. 18 | minimum, maximum = unpack('<2I', reader.data[:8]) 19 | 20 | roll = randint(minimum, maximum) 21 | 22 | roll_packet = PacketWriter.get_packet(OpCode.MSG_RANDOM_ROLL, 23 | pack('<3IQ', minimum, maximum, roll, player_mgr.guid)) 24 | 25 | if player_mgr.group_manager and player_mgr.group_manager.is_party_formed(): 26 | player_mgr.group_manager.send_packet_to_members(roll_packet, use_ignore=True) 27 | else: 28 | player_mgr.enqueue_packet(roll_packet) 29 | 30 | return 0 31 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/RepopRequestHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 2 | from network.packet.PacketReader import * 3 | 4 | 5 | class RepopRequestHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader: PacketReader) -> int: 9 | # Validate world session. 10 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 11 | if not player_mgr: 12 | return res 13 | 14 | # Ignore if player is update locked or already alive. 15 | if player_mgr.update_lock or player_mgr.is_alive: 16 | return 0 17 | 18 | player_mgr.resurrect(release_spirit=True) 19 | 20 | return 0 21 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/ResurrectResponseHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 2 | from network.packet.PacketReader import * 3 | 4 | 5 | class ResurrectResponseHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader: PacketReader) -> int: 9 | # Validate world session. 10 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 11 | if not player_mgr: 12 | return res 13 | 14 | # Ignore if player is update locked or already alive. 15 | if player_mgr.update_lock or player_mgr.is_alive: 16 | return 0 17 | 18 | if len(reader.data) >= 9: # Avoid handling empty resurrect response packet. 19 | guid, status = unpack('= 5: # Avoid handling empty set action button packet. 17 | index, action = unpack(' int: 9 | # Validate world session. 10 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 11 | if not player_mgr: 12 | return res 13 | 14 | if player_mgr.deathbind: 15 | player_mgr.enqueue_packet(player_mgr.get_deathbind_packet()) 16 | 17 | return 0 18 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/SetWeaponModeHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 2 | from network.packet.PacketReader import * 3 | from struct import unpack 4 | 5 | 6 | class SetWeaponModeHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader: PacketReader) -> int: 10 | # Validate world session. 11 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 12 | if not player_mgr: 13 | return res 14 | 15 | if len(reader.data) >= 1: # Avoid handling empty set weapon mode packet. 16 | weapon_mode: int = unpack(' int: 12 | # Validate world session. 13 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=True) 14 | if not player_mgr: 15 | return res 16 | 17 | if len(reader.data) >= 4: # Avoid handling empty stand state packet. 18 | state: int = unpack(' int: 13 | # Validate world session. 14 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 15 | if not player_mgr: 16 | return res 17 | 18 | if not world_session.account_mgr.is_gm(): 19 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried to give himself Beastmaster.') 20 | return 0 21 | 22 | if len(reader.data) >= 1: # Avoid handling empty beast master packet. 23 | # Client sends `0` if you type `beastmaster off`, and `1` if you type `beastmaster`. 24 | player_mgr.beast_master = unpack('= 1 25 | # Set sanctuary state. 26 | player_mgr.set_sanctuary(player_mgr.beast_master, time_secs=1) 27 | 28 | ChatManager.send_system_message(world_session, f'Beastmaster ' 29 | f'{"enabled" if player_mgr.beast_master else "disabled"}') 30 | 31 | return 0 32 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/cheats/CheatSetMoneyHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 2 | from network.packet.PacketReader import PacketReader 3 | from utils.Logger import Logger 4 | from struct import unpack 5 | 6 | 7 | class CheatSetMoneyHandler(object): 8 | 9 | @staticmethod 10 | def handle(world_session, reader: PacketReader) -> int: 11 | # Validate world session. 12 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 13 | if not player_mgr: 14 | return res 15 | 16 | if not world_session.account_mgr.is_gm(): 17 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried to give himself money.') 18 | return 0 19 | 20 | if len(reader.data) >= 4: # Avoid handling empty cheat set money packet. 21 | new_money = unpack(' int: 12 | # Validate world session. 13 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 14 | if not player_mgr: 15 | return res 16 | 17 | if not world_session.account_mgr.is_gm(): 18 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried create item.') 19 | return 0 20 | 21 | if len(reader.data) >= 4: # Avoid handling empty create item packet. 22 | item_entry = unpack(' int: 12 | # Validate world session. 13 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 14 | if not player_mgr: 15 | return res 16 | 17 | if not world_session.account_mgr.is_gm(): 18 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried create monster.') 19 | return 0 20 | 21 | if len(reader.data) >= 4: # Avoid handling empty create monster packet. 22 | creature_entry = unpack(' int: 12 | # Validate world session. 13 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 14 | if not player_mgr: 15 | return res 16 | 17 | if not world_session.account_mgr.is_dev(): 18 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried destroy monster.') 19 | return 0 20 | 21 | if len(reader.data) >= 4: # Avoid handling empty destroy monster packet. 22 | creature_guid = unpack(' int: 11 | # Validate world session. 12 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 13 | if not player_mgr: 14 | return res 15 | 16 | if not player_mgr.session.account_mgr.is_gm(): 17 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried to summon a player.') 18 | return 0 19 | 20 | if len(reader.data) >= 1: # Avoid handling empty gm summon packet. 21 | player_name: str = PacketReader.read_string(reader.data, 0) 22 | CommandManager.summon(world_session, player_name) 23 | 24 | return 0 25 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/cheats/GodModeHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.ChatManager import ChatManager 2 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 3 | from network.packet.PacketReader import * 4 | from network.packet.PacketWriter import * 5 | from utils.Logger import Logger 6 | 7 | 8 | class GodModeHandler(object): 9 | 10 | @staticmethod 11 | def handle(world_session, reader: PacketReader) -> int: 12 | # Validate world session. 13 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 14 | if not player_mgr: 15 | return res 16 | 17 | if not world_session.account_mgr.is_gm(): 18 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried to set god mode.') 19 | return 0 20 | 21 | if len(reader.data) >= 1: # Avoid handling empty god mode packet. 22 | # Client sends `0` if you type `godmode`, and `1` if you type `godmode 1` (or a number greater than 1). 23 | player_mgr.is_god = unpack('= 1 24 | player_mgr.enqueue_packet(PacketWriter.get_packet(OpCode.SMSG_GODMODE, reader.data[:1])) 25 | ChatManager.send_system_message(world_session, f'Godmode ' 26 | f'{"enabled" if player_mgr.is_god else "disabled"}') 27 | 28 | return 0 29 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/cheats/LearnSpellCheatHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 2 | from utils.Logger import Logger 3 | from struct import unpack 4 | 5 | 6 | class LearnSpellCheatHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader): 10 | # Validate world session. 11 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 12 | if not player_mgr: 13 | return res 14 | 15 | if not world_session.account_mgr.is_gm(): 16 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried to learn spell.') 17 | return 0 18 | 19 | if len(reader.data) >= 4: # Avoid handling empty learn spell cheat packet. 20 | spell_id = unpack(' int: 11 | # Validate world session. 12 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 13 | if not player_mgr: 14 | return res 15 | 16 | if not world_session.account_mgr.is_gm(): 17 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried to modify level.') 18 | return 0 19 | 20 | if len(reader.data) >= 4: # Avoid empty packet level cheat packet. 21 | new_level = unpack(' int: 10 | # Validate world session. 11 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 12 | if not player_mgr: 13 | return res 14 | 15 | if not world_session.account_mgr.is_gm(): 16 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried to modify level.') 17 | return 0 18 | 19 | player_mgr.mod_level(player_mgr.level + 1) 20 | 21 | return 0 22 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/cheats/MakeMonsterAttackMeHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 3 | from network.packet.PacketReader import PacketReader 4 | 5 | 6 | class MakeMonsterAttackMeHandler(object): 7 | @staticmethod 8 | def handle(world_session, reader: PacketReader) -> int: 9 | # Validate world session. 10 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 11 | if not player_mgr: 12 | return res 13 | 14 | if not world_session.account_mgr.is_gm(): 15 | return 0 16 | 17 | if len(reader.data) >= 8: # Avoid handling empty make monster attack me packet. 18 | guid = unpack(' int: 11 | # Validate world session. 12 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 13 | if not player_mgr: 14 | return res 15 | 16 | if not world_session.account_mgr.is_gm(): 17 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried to modify their pet level.') 18 | return 0 19 | 20 | if len(reader.data) >= 4: # Avoid empty packet level cheat packet. 21 | new_level = unpack(' 100: 24 | return 0 25 | 26 | active_pet = world_session.player_mgr.pet_manager.get_active_controlled_pet() 27 | if not active_pet: 28 | return 0 29 | 30 | active_pet.set_level(new_level, replenish=True) 31 | 32 | return 0 33 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/cheats/RechargeHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 2 | from network.packet.PacketReader import PacketReader 3 | from utils.Logger import Logger 4 | 5 | 6 | class RechargeHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader: PacketReader) -> int: 10 | # Validate world session. 11 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 12 | if not player_mgr: 13 | return res 14 | 15 | if not world_session.account_mgr.is_gm(): 16 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried to recharge powers.') 17 | return 0 18 | 19 | player_mgr.recharge_power() 20 | 21 | return 0 22 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/cheats/SpeedCheatHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 2 | from network.packet.PacketReader import * 3 | from utils.Logger import Logger 4 | 5 | 6 | class SpeedCheatHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader: PacketReader) -> int: 10 | # Validate world session. 11 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 12 | if not player_mgr: 13 | return res 14 | 15 | if not world_session.account_mgr.is_gm(): 16 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried to modify speed.') 17 | return -1 18 | 19 | if len(reader.data) >= 52: # Avoid handling empty speed cheat packet. 20 | speed = unpack(' int: 10 | # Validate world session. 11 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 12 | if not player_mgr: 13 | return res 14 | 15 | if not world_session.account_mgr.is_gm(): 16 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried to clear all taxi nodes.') 17 | return 0 18 | 19 | player_mgr.taxi_manager.disable_all_taxi_nodes() 20 | 21 | return 0 22 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/cheats/TaxiEnableAllNodesHandlers.py: -------------------------------------------------------------------------------- 1 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 2 | from network.packet.PacketReader import * 3 | from utils.Logger import Logger 4 | 5 | 6 | class TaxiEnableAllNodesHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader: PacketReader) -> int: 10 | # Validate world session. 11 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 12 | if not player_mgr: 13 | return res 14 | 15 | if not player_mgr.session.account_mgr.is_gm(): 16 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried to enable all taxi nodes.') 17 | return 0 18 | 19 | player_mgr.taxi_manager.enable_all_taxi_nodes() 20 | 21 | return 0 22 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/cheats/TeleportToPlayerHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.CommandManager import CommandManager 2 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 3 | from network.packet.PacketReader import * 4 | from utils.Logger import Logger 5 | 6 | 7 | class TeleportToPlayerHandler(object): 8 | 9 | @staticmethod 10 | def handle(world_session, reader: PacketReader) -> int: 11 | # Validate world session. 12 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 13 | if not player_mgr: 14 | return res 15 | 16 | if not player_mgr.session.account_mgr.is_gm(): 17 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried to port to a player.') 18 | return 0 19 | 20 | if len(reader.data) >= 1: # Avoid handling empty teleport to player packet. 21 | player_name: str = PacketReader.read_string(reader.data, 0) 22 | result = CommandManager.goplayer(world_session, player_name) 23 | 24 | if result[0] == -1: 25 | CommandManager.tel(world_session, player_name) 26 | 27 | return 0 28 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/player/cheats/TriggerCinematicCheatHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | from database.dbc.DbcDatabaseManager import DbcDatabaseManager 4 | from game.world.opcode_handling.HandlerValidator import HandlerValidator 5 | from network.packet.PacketReader import PacketReader 6 | from network.packet.PacketWriter import * 7 | from utils.Logger import Logger 8 | from utils.constants.OpCodes import OpCode 9 | 10 | 11 | class TriggerCinematicCheatHandler(object): 12 | 13 | @staticmethod 14 | def handle(world_session, reader: PacketReader) -> int: 15 | # Validate world session. 16 | player_mgr, res = HandlerValidator.validate_session(world_session, reader.opcode, disconnect=False) 17 | if not player_mgr: 18 | return res 19 | 20 | if not world_session.account_mgr.is_gm(): 21 | Logger.anticheat(f'Player {player_mgr.get_name()} ({player_mgr.guid}) tried to force trigger a cinematic.') 22 | return 0 23 | 24 | if len(reader.data) >= 4: # Avoid handling empty trigger cinematic cheat packet. 25 | cinematic_id = unpack('= 4: # Avoid handling empty quest confirm accept packet. 18 | quest_id = unpack('= 1: # Avoid handling empty quest giver remove quest packet. 16 | slot = unpack('= 4: # Avoid handling empty quest query packet. 16 | quest_id = unpack('= 4: # Avoid handling empty bug packet. 10 | if not world_session.account_mgr or not world_session.player_mgr: 11 | return 0 12 | 13 | is_bug = unpack('= 4: # Avoid handling empty LFG set packet. 20 | is_lfg = bool(unpack('= 4: # Avoid handling empty player macro packet. 11 | category = unpack('= 4: # Avoid handling empty cancel aura packet. 8 | spell_id = unpack('= 4: # Avoid handling empty cancel cast packet. 9 | spell_id = unpack('= 4: # Avoid handling empty cancel channelling packet. 9 | spell_id = unpack('= 5: # Avoid handling empty use item packet. 9 | bag, slot, spell_count, target_mask = unpack('<3BH', reader.data[:5]) 10 | 11 | item = world_session.player_mgr.inventory.get_item(bag, slot) 12 | if not item: 13 | return 0 14 | 15 | target_bytes = reader.data[5:] 16 | target = CastSpellHandler.get_target_info(world_session, target_mask, target_bytes) 17 | 18 | world_session.player_mgr.spell_manager.handle_item_cast_attempt(item, target, target_mask) 19 | return 0 20 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/spell/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/opcode_handling/handlers/spell/__init__.py -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/trade/BeginTradeHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.player.trade.TradeManager import TradeManager 2 | from utils.constants.MiscCodes import TradeStatus 3 | 4 | 5 | class BeginTradeHandler(object): 6 | 7 | @staticmethod 8 | def handle(world_session, reader): 9 | if not world_session.player_mgr.trade_data: 10 | return 0 11 | 12 | TradeManager.send_trade_status(world_session.player_mgr, TradeStatus.TRADE_STATUS_INITIATED) 13 | TradeManager.send_trade_status(world_session.player_mgr.trade_data.other_player, 14 | TradeStatus.TRADE_STATUS_INITIATED) 15 | 16 | return 0 17 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/trade/CancelTradeHandler.py: -------------------------------------------------------------------------------- 1 | from game.world.managers.objects.units.player.trade.TradeManager import TradeManager 2 | 3 | 4 | class CancelTradeHandler(object): 5 | 6 | @staticmethod 7 | def handle(world_session, reader): 8 | if not world_session or not world_session.player_mgr or not world_session.player_mgr.trade_data: 9 | return 0 10 | 11 | TradeManager.cancel_trade(world_session.player_mgr) 12 | 13 | return 0 14 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/trade/ClearTradeItemHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | from game.world.managers.objects.units.player.trade.TradeManager import TradeManager 4 | 5 | 6 | class ClearTradeItemHandler(object): 7 | 8 | @staticmethod 9 | def handle(world_session, reader): 10 | if not world_session or not world_session.player_mgr or not world_session.player_mgr.trade_data: 11 | return 0 12 | 13 | if len(reader.data) >= 1: # Avoid handling empty clear trade item packet. 14 | trade_slot = unpack(' TradeManager.TRADE_SLOT_COUNT: 16 | return 0 17 | 18 | world_session.player_mgr.trade_data.clear_item(trade_slot) 19 | 20 | return 0 21 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/trade/SetTradeGoldHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | 4 | class SetTradeGoldHandler(object): 5 | 6 | @staticmethod 7 | def handle(world_session, reader): 8 | if not world_session.player_mgr.trade_data: 9 | return 0 10 | 11 | if len(reader.data) >= 4: # Avoid handling empty set trade gold packet. 12 | money = unpack(' world_session.player_mgr.coinage: 17 | money = world_session.player_mgr.coinage 18 | 19 | world_session.player_mgr.trade_data.set_money(money) 20 | 21 | return 0 22 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/trade/SetTradeItemHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | from game.world.managers.objects.units.player.trade.TradeManager import TradeManager 4 | from utils.constants.ItemCodes import InventorySlots 5 | from utils.constants.MiscCodes import TradeStatus 6 | 7 | 8 | class SetTradeItemHandler(object): 9 | 10 | @staticmethod 11 | def handle(world_session, reader): 12 | if not world_session.player_mgr.trade_data: 13 | return 0 14 | 15 | if len(reader.data) >= 3: # Avoid handling empty set trade item packet. 16 | trade_slot, bag, slot = unpack('<3B', reader.data[:3]) 17 | 18 | if bag == 0xFF: 19 | bag = InventorySlots.SLOT_INBACKPACK.value 20 | 21 | item = world_session.player_mgr.inventory.get_item(bag, slot) 22 | if not item: 23 | return 0 24 | 25 | if trade_slot > TradeManager.TRADE_SLOT_COUNT: 26 | TradeManager.send_trade_status(world_session.player_mgr, TradeStatus.TRADE_STATUS_CANCELLED) 27 | return 0 28 | 29 | world_session.player_mgr.trade_data.set_item(trade_slot, item) 30 | 31 | return 0 32 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/trade/UnacceptTradeHandler.py: -------------------------------------------------------------------------------- 1 | class UnacceptTradeHandler(object): 2 | 3 | @staticmethod 4 | def handle(world_session, reader): 5 | if not world_session.player_mgr.trade_data: 6 | return 0 7 | 8 | world_session.player_mgr.trade_data.set_accepted(False) 9 | 10 | return 0 11 | -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/trade/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/game/world/opcode_handling/handlers/trade/__init__.py -------------------------------------------------------------------------------- /game/world/opcode_handling/handlers/unit/SetSelectionHandler.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | 3 | 4 | class SetSelectionHandler(object): 5 | 6 | @staticmethod 7 | def handle(world_session, reader): 8 | if len(reader.data) >= 8: # Avoid handling empty set selection packet. 9 | guid = unpack('= 8: # Avoid handling empty set target packet. 9 | guid = unpack('= 21: # Avoid handling empty world teleport packet. 24 | pack_guid, map_, x, y, z, o = unpack('= 4: # Avoid handling empty zone update packet. 12 | zone = unpack(' 5: 9 | self.size = unpack('>H', data[:2])[0] - 4 # Big Endian 10 | self.opcode = unpack('H', len(data)) + data 26 | 27 | @staticmethod 28 | def get_packet(opcode, data=b''): 29 | if data is None: 30 | data = b'' 31 | 32 | data = pack('H', len(data)) + data 34 | 35 | if opcode == OpCode.SMSG_UPDATE_OBJECT and len(packet) > 100: 36 | compressed_packet_data = zlib.compress(packet[6:]) 37 | compressed_data = pack('h', Float16.compress(height))) 36 | -------------------------------------------------------------------------------- /tools/extractors/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/tools/extractors/helpers/__init__.py -------------------------------------------------------------------------------- /tools/extractors/pydbclib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/The-Alpha-Project/alpha-core/74f6e8077643f5828c16b257b5762d9ed18ce412/tools/extractors/pydbclib/__init__.py -------------------------------------------------------------------------------- /tools/extractors/pydbclib/structs/DbcHeader.py: -------------------------------------------------------------------------------- 1 | from struct import unpack 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass 6 | class DbcHeader: 7 | signature: str 8 | record_count: int 9 | field_count: int 10 | record_size: int 11 | string_block_size: int 12 | valid: bool 13 | 14 | @staticmethod 15 | def from_bytes(reader): 16 | signature = reader.read(4).decode('utf8') 17 | record_count, field_count, record_size, block_size = unpack('<4I', reader.read(16)) 18 | return DbcHeader(signature, record_count, field_count, record_size, block_size, signature == 'WDBC') 19 | -------------------------------------------------------------------------------- /tools/extractors/pydbclib/structs/Map.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dataclasses import dataclass 3 | 4 | 5 | @dataclass 6 | class Map: 7 | id: int 8 | directory: str 9 | pvp: int 10 | is_in_map: int 11 | name: str 12 | name_en_gb: str 13 | name_ko_kr: str 14 | name_fr_fr: str 15 | name_de_de: str 16 | name_en_cn: str 17 | name_zh_ch: str 18 | name_en_tw: str 19 | mask: int 20 | 21 | def exists(self, root_path): 22 | return os.path.exists(self.get_wdt_path(root_path)) 23 | 24 | def get_wdt_path(self, root_path): 25 | return os.path.join(os.path.join(root_path, self.directory), self.directory) + '.wdt.MPQ' 26 | 27 | @staticmethod 28 | def from_bytes(dbc_reader): 29 | id_ = dbc_reader.read_int32() 30 | directory = dbc_reader.read_string() 31 | pvp = dbc_reader.read_int32() 32 | is_in_map = dbc_reader.read_int32() 33 | name_en_us = dbc_reader.read_string() 34 | name_en_gb = dbc_reader.read_string() 35 | name_ko_kr = dbc_reader.read_string() 36 | name_fr_fr = dbc_reader.read_string() 37 | name_de_de = dbc_reader.read_string() 38 | name_en_cn = dbc_reader.read_string() 39 | name_zh_ch = dbc_reader.read_string() 40 | name_en_tw = dbc_reader.read_string() 41 | mask = dbc_reader.read_int32() 42 | 43 | return Map(id_, directory, pvp, is_in_map, name_en_us, name_en_gb, name_ko_kr, name_fr_fr, name_de_de, 44 | name_en_cn, name_zh_ch, name_en_tw, mask) 45 | -------------------------------------------------------------------------------- /tools/extractors/pympqlib/MpqFlags.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | 4 | class MpqFlags(IntEnum): 5 | CompressedPK = 0x100 6 | CompressedMulti = 0x200 7 | Compressed = 0xff00 8 | Encrypted = 0x10000 9 | BlockOffsetAdjustedKey = 0x020000 10 | SingleUnit = 0x1000000 11 | -------------------------------------------------------------------------------- /tools/extractors/pympqlib/MpqHash.py: -------------------------------------------------------------------------------- 1 | from io import BytesIO 2 | from struct import unpack 3 | 4 | 5 | class MpqHash: 6 | SIZE = 16 7 | 8 | def __init__(self): 9 | self.name_1 = 0 10 | self.name_2 = 0 11 | self.locale = 0 12 | self.block_index = 0 13 | 14 | @staticmethod 15 | def from_data(stream: BytesIO): 16 | mpq_hash = MpqHash() 17 | mpq_hash.name_1 = unpack('