forked from mirrors/cobblemon
Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2789e70ed | ||
|
|
22ea0296c0 | ||
|
|
24ae2f5324 | ||
|
|
c9d27d46b6 | ||
|
|
b68756378c | ||
|
|
1ff1880951 | ||
|
|
fea2d32de8 | ||
|
|
e3e6d4452c | ||
|
|
f687e8e658 | ||
|
|
a6d17659f8 | ||
|
|
997a6de23b | ||
|
|
c948b6d885 | ||
|
|
5508980384 | ||
|
|
e39dee01e5 | ||
|
|
a81412e176 | ||
|
|
09f9dd91aa | ||
|
|
2efc3c72ac | ||
|
|
a92fb03093 | ||
|
|
ad5847983f | ||
|
|
bb25300513 | ||
|
|
de33804eb4 | ||
|
|
de042f71dd | ||
|
|
5a1e85bbf4 | ||
|
|
3489e0df74 | ||
|
|
afe8eec343 | ||
|
|
8913d3df11 | ||
|
|
3a55965704 | ||
|
|
7112c74bf2 | ||
|
|
faa1906290 |
@@ -2,6 +2,7 @@
|
||||
## [1.7.0 (Month xth, 2025)](#1-7-0)
|
||||
|
||||
### Additions
|
||||
- Added `/spectateBattle <player>` command to spectate battles without having to manually walk up to the target.
|
||||
- Added an in-game configuration screen, allowing all settings from `main.json` to be edited directly in-game.
|
||||
- Added `/cobblemonconfig reload` command to reload `main.json` configuration. **Note:** Some settings require a server restart to take effect; use this command cautiously.
|
||||
- Added `blacklisted_items_to_hold` and `whitelisted_items_to_hold` tags to allow for controlling which items players can give to their Pokémon. If the whitelist is empty, it will consider all item as allowed (unless they are in the blacklist).
|
||||
@@ -10,6 +11,7 @@
|
||||
- Added cosmetic item functionality for Pokémon. Certain cosmetic items can be given to applicable Pokémon via the interact menu.
|
||||
- Added the various log blocks as cosmetic items for Timburr and Komala.
|
||||
- Added `visibility/hidden`, `visibility/hat` and `visibility/face` tags to control where and how certain items are rendered.
|
||||
- Added `/boxcount` command to change PC boxes amount
|
||||
|
||||
### Changes
|
||||
- Renamed `chargeGainedPerTick` config to `secondsToChargeHealingMachine`.
|
||||
|
||||
@@ -25,6 +25,7 @@ object CobblemonCommands {
|
||||
ChangeWalkSpeed.register(dispatcher)
|
||||
ChangeJointScaleCommand.register(dispatcher)
|
||||
TestCommand.register(dispatcher)
|
||||
ReloadShowdownCommand.register(dispatcher)
|
||||
ClickTextCommand.register(dispatcher)
|
||||
PokemonEditCommand.register(dispatcher)
|
||||
TeachCommand.register(dispatcher)
|
||||
@@ -59,12 +60,14 @@ object CobblemonCommands {
|
||||
RenameBoxCommand.register(dispatcher)
|
||||
ChangeBoxWallpaperCommand.register(dispatcher)
|
||||
RunMolangScriptCommand.register(dispatcher)
|
||||
SpectateBattleCommand.register(dispatcher)
|
||||
CobblemonConfigCommand.register(dispatcher)
|
||||
UnlockPCBoxWallpaperCommand.register(dispatcher)
|
||||
ChangePCBoxesCommand.register(dispatcher)
|
||||
|
||||
// Possibly lock down registration if and only if under dev environment or running in an environment
|
||||
// with a certain system environment variable set
|
||||
CobblemonInfoCommand.register(dispatcher)
|
||||
CalculateSeatPositionCommand.register(dispatcher)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ import com.cobblemon.mod.common.client.net.callback.party.OpenPartyCallbackHandl
|
||||
import com.cobblemon.mod.common.client.net.callback.partymove.OpenPartyMoveCallbackHandler
|
||||
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.data.UnlockReloadPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.debug.showdown.ShowdownRefreshInfoHandler
|
||||
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
|
||||
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.RunPosableMoLangHandler
|
||||
@@ -49,7 +48,14 @@ import com.cobblemon.mod.common.client.net.storage.party.SetPartyPokemonHandler
|
||||
import com.cobblemon.mod.common.client.net.storage.party.SetPartyReferenceHandler
|
||||
import com.cobblemon.mod.common.client.net.storage.pc.*
|
||||
import com.cobblemon.mod.common.client.net.toast.ToastPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.*
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeAcceptanceChangedHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeCancelledHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeCompletedHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeOfferExpiredHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeOfferNotificationHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeProcessStartedHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeStartedHandler
|
||||
import com.cobblemon.mod.common.client.net.trade.TradeUpdatedHandler
|
||||
import com.cobblemon.mod.common.net.PacketRegisterInfo
|
||||
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
|
||||
@@ -60,7 +66,6 @@ import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPac
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallbackPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.data.*
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.showdown.ShowdownRefreshInfoPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
|
||||
@@ -91,18 +96,42 @@ import com.cobblemon.mod.common.net.messages.client.storage.party.InitializePart
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.party.MoveClientPartyPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyReferencePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.*
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.ClosePCPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.InitializePCPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.MoveClientPCPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.OpenPCPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.RenamePCBoxPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCBoxPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.ChangePCBoxWallpaperPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.RequestPCBoxWallpapersPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.SetPCBoxWallpapersPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.UnlockPCBoxWallpaperPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.*
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeAcceptanceChangedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeCompletedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferExpiredPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferNotificationPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeProcessStartedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.trade.TradeUpdatedPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.*
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.*
|
||||
import com.cobblemon.mod.common.net.messages.server.BattleChallengePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.BattleChallengeResponsePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.BenchMovePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.RequestMoveSwapPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.RequestPlayerInteractionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.SendOutPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamLeavePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamRequestPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamResponsePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.RemoveSpectatorPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
|
||||
@@ -110,9 +139,6 @@ import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonS
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.party.PartySelectCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyMoveSelectCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyPokemonMoveSelectedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.showdown.RequestShowdownInfoPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.showdown.ShowdownActionPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.showdown.ShowdownInputPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.dialogue.EscapeDialoguePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.dialogue.InputToDialoguePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.npc.SaveNPCPacket
|
||||
@@ -132,11 +158,21 @@ import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokem
|
||||
import com.cobblemon.mod.common.net.messages.server.storage.party.ReleasePartyPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.storage.party.SwapPartyPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.storage.pc.*
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.*
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.AcceptTradeRequestPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.CancelTradePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.ChangeTradeAcceptancePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.OfferTradePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.PerformTradePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.trade.UpdateTradeOfferPacket
|
||||
import com.cobblemon.mod.common.net.serverhandling.ChallengeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.ChallengeResponseHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.RequestInteractionsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.*
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.RemoveSpectatorHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.TeamLeaveHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestResponseHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectedHandler
|
||||
@@ -144,9 +180,6 @@ import com.cobblemon.mod.common.net.serverhandling.callback.party.PartyPokemonSe
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartySelectCancelledHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.partymove.PartyMoveSelectCancelledHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.partymove.PartyPokemonMoveSelectedHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.debug.showdown.RequestShowdownInfoHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.debug.showdown.ShowdownActionHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.debug.showdown.ShowdownInputHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.dialogue.EscapeDialogueHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.dialogue.InputToDialogueHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.evolution.AcceptEvolutionHandler
|
||||
@@ -170,7 +203,12 @@ import com.cobblemon.mod.common.net.serverhandling.storage.party.MovePartyPokemo
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.ReleasePCPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.SwapPartyPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.pc.*
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.*
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.AcceptTradeRequestHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.CancelTradeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.ChangeTradeAcceptanceHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.OfferTradeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.PerformTradeHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.trade.UpdateTradeOfferHandler
|
||||
import com.cobblemon.mod.common.util.server
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
@@ -374,7 +412,6 @@ object CobblemonNetwork {
|
||||
|
||||
// Debug / cheats
|
||||
list.add(PacketRegisterInfo(CalculateSeatPositionsPacket.ID, CalculateSeatPositionsPacket::decode, CalculateSeatPositionsHandler))
|
||||
list.add(PacketRegisterInfo(ShowdownRefreshInfoPacket.ID, ShowdownRefreshInfoPacket::decode, ShowdownRefreshInfoHandler))
|
||||
|
||||
return list
|
||||
}
|
||||
@@ -474,11 +511,6 @@ object CobblemonNetwork {
|
||||
// Orientation packet(s)
|
||||
list.add(PacketRegisterInfo(C2SUpdateOrientationPacket.ID, C2SUpdateOrientationPacket::decode, OrientationPacketHandler))
|
||||
|
||||
// Debug / cheats
|
||||
list.add(PacketRegisterInfo(RequestShowdownInfoPacket.ID, RequestShowdownInfoPacket::decode, RequestShowdownInfoHandler))
|
||||
list.add(PacketRegisterInfo(ShowdownInputPacket.ID, ShowdownInputPacket::decode, ShowdownInputHandler))
|
||||
list.add(PacketRegisterInfo(ShowdownActionPacket.ID, ShowdownActionPacket::decode, ShowdownActionHandler))
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ object Abilities : DataRegistry {
|
||||
PotentialAbility.types.add(CommonAbilityType)
|
||||
PotentialAbility.types.add(HiddenAbilityType)
|
||||
this.abilityMap.clear()
|
||||
val abilitiesJson = ShowdownService.get().getAbilityIds()
|
||||
val abilitiesJson = ShowdownService.service.getAbilityIds()
|
||||
for (i in 0 until abilitiesJson.size()) {
|
||||
val id = abilitiesJson[i].asString
|
||||
val ability = AbilityTemplate(id)
|
||||
@@ -67,4 +67,4 @@ object Abilities : DataRegistry {
|
||||
abilities.forEach { ability -> this.register(ability) }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,10 @@ import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.Cobblemon.LOGGER
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.*
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.FleeableBattleActor
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
@@ -71,9 +74,7 @@ import net.minecraft.world.item.ItemStack
|
||||
open class PokemonBattle(
|
||||
val format: BattleFormat,
|
||||
val side1: BattleSide,
|
||||
val side2: BattleSide,
|
||||
val service: ShowdownService = ShowdownService.get(),
|
||||
val battleId: UUID = UUID.randomUUID()
|
||||
val side2: BattleSide
|
||||
) {
|
||||
/** Whether logging will be silenced for this battle. */
|
||||
var mute = true
|
||||
@@ -108,6 +109,8 @@ open class PokemonBattle(
|
||||
val players = playerUUIDs.mapNotNull { it.getPlayer() }
|
||||
val spectators = mutableSetOf<UUID>()
|
||||
|
||||
val battleId = UUID.randomUUID()
|
||||
|
||||
val showdownMessages = mutableListOf<String>()
|
||||
val battleLog = mutableListOf<String>()
|
||||
val chatLog = mutableListOf<Component>()
|
||||
@@ -222,7 +225,7 @@ open class PokemonBattle(
|
||||
|
||||
fun writeShowdownAction(vararg messages: String) {
|
||||
log(messages.joinToString("\n"))
|
||||
service.send(battleId, messages.toList().toTypedArray())
|
||||
ShowdownService.service.send(battleId, messages.toList().toTypedArray())
|
||||
}
|
||||
|
||||
fun turn(newTurnNumber: Int) {
|
||||
@@ -375,6 +378,7 @@ open class PokemonBattle(
|
||||
|
||||
fun dispatch(dispatcher: () -> DispatchResult) {
|
||||
dispatches.add(BattleDispatch { dispatcher() })
|
||||
|
||||
}
|
||||
|
||||
fun dispatchToFront(dispatcher: () -> DispatchResult) {
|
||||
@@ -498,7 +502,6 @@ open class PokemonBattle(
|
||||
}
|
||||
|
||||
fun checkForInputDispatch() {
|
||||
actors.filter { it is UnionBattleActor }.forEach { it.mustChoose = false }
|
||||
if (checkForfeit()) return // ignore actors that are still choosing, their choices don't matter anymore
|
||||
val readyToInput = (actors.any { !it.mustChoose && it.responses.isNotEmpty() } && actors.none { it.mustChoose })
|
||||
if (readyToInput && captureActions.isEmpty()) {
|
||||
@@ -555,4 +558,4 @@ open class PokemonBattle(
|
||||
queryStruct.addFunction("has_rule") { params -> DoubleValue(params.getString(0) in format.ruleSet) }
|
||||
return queryStruct
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -42,4 +42,4 @@ abstract class AIBattleActor(
|
||||
setActionResponses(request!!.iterate(this.activePokemon) { _, _, _ -> PassActionResponse })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -73,7 +73,7 @@ abstract class BattleActor(
|
||||
open fun isForPlayer(serverPlayerEntity: ServerPlayer) = serverPlayerEntity.uuid in getPlayerUUIDs()
|
||||
open fun isForPokemon(pokemonEntity: PokemonEntity) = activePokemon.any { it.battlePokemon?.effectedPokemon?.entity == pokemonEntity }
|
||||
|
||||
open fun turn() {
|
||||
fun turn() {
|
||||
val request = request ?: return
|
||||
responses.clear()
|
||||
if(activePokemon.any { it.isAlive() }) {
|
||||
@@ -90,7 +90,7 @@ abstract class BattleActor(
|
||||
}
|
||||
}
|
||||
|
||||
open fun upkeep() {
|
||||
fun upkeep() {
|
||||
val request = request ?: return
|
||||
val forceSwitchPokemon = request.forceSwitch.mapIndexedNotNull { index, b -> if (b) activePokemon[index] else null }
|
||||
if (forceSwitchPokemon.isEmpty()) {
|
||||
@@ -125,7 +125,7 @@ abstract class BattleActor(
|
||||
battle.checkForInputDispatch()
|
||||
}
|
||||
|
||||
open fun writeShowdownResponse() {
|
||||
fun writeShowdownResponse() {
|
||||
val showdownMessages = mutableListOf<String>()
|
||||
var index = 0
|
||||
request!!.iterate(activePokemon) { activeBattlePokemon, showdownMoveSet, forceSwitch ->
|
||||
@@ -159,4 +159,4 @@ abstract class BattleActor(
|
||||
|
||||
open fun win(otherWinners: List<BattleActor>, losers: List<BattleActor>) {}
|
||||
open fun lose(winners: List<BattleActor>, otherLosers: List<BattleActor>) {}
|
||||
}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
package com.cobblemon.mod.common.api.battles.model.actor
|
||||
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
|
||||
class UnionBattleActor(npc: NPCEntity, pokemonList: List<BattlePokemon>) : NPCBattleActor(npc, pokemonList.toMutableList(), 1) {
|
||||
|
||||
override fun writeShowdownResponse() {}
|
||||
|
||||
override fun onChoiceRequested() {
|
||||
this.mustChoose = false
|
||||
}
|
||||
|
||||
override fun turn() {
|
||||
super.turn()
|
||||
this.mustChoose = false
|
||||
}
|
||||
|
||||
override fun upkeep() {
|
||||
super.upkeep()
|
||||
this.mustChoose = false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,7 +40,7 @@ object Moves : DataRegistry {
|
||||
override fun reload(manager: ResourceManager) {
|
||||
this.allMoves.clear()
|
||||
this.idMapping.clear()
|
||||
val movesJson = ShowdownService.get().getMoves()
|
||||
val movesJson = ShowdownService.service.getMoves()
|
||||
for (i in 0 until movesJson.size()) {
|
||||
val jsMove = movesJson[i].asJsonObject
|
||||
val id = jsMove.get("id").asString
|
||||
@@ -116,4 +116,4 @@ object Moves : DataRegistry {
|
||||
this.idMapping[move.num] = move
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -128,8 +128,12 @@ object CobblemonPermissions {
|
||||
val RUN_MOLANG_SCRIPT = this.create("${COMMAND_PREFIX}runmolangscript", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
@JvmStatic
|
||||
val COBBLEMON_CONFIG_RELOAD = this.create("${COMMAND_PREFIX}cobblemonconfig.reload", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
|
||||
@JvmStatic
|
||||
val CALCULATE_SEAT_POSITIONS = this.create("${COMMAND_PREFIX}calculateseatpositions", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
@JvmStatic
|
||||
val CHANGE_BOX_COUNT = this.create("${COMMAND_PREFIX}boxcount", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
@JvmStatic
|
||||
val SPECTATE_BATTLE = this.create("${COMMAND_PREFIX}spectatebattle", PermissionLevel.ALL_COMMANDS)
|
||||
|
||||
fun all(): Iterable<Permission> = this.permissions
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
|
||||
import com.cobblemon.mod.common.util.adapters.*
|
||||
import com.cobblemon.mod.common.util.adapters.RideControllerAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.ifClient
|
||||
import com.google.common.collect.HashBasedTable
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
@@ -211,6 +212,11 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
this.speciesByDex.remove(old.resourceIdentifier.namespace, old.nationalPokedexNumber)
|
||||
}
|
||||
this.speciesByDex.put(species.resourceIdentifier.namespace, species.nationalPokedexNumber, species)
|
||||
ifClient {
|
||||
if (species.implemented) {
|
||||
this.implemented.add(species)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,4 +291,4 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
return "${species.resourceIdentifier.namespace}:${species.name}"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,12 @@ open class PCStore(
|
||||
pcChangeObservable.emit(Unit)
|
||||
}
|
||||
|
||||
fun removeListOfBoxes(boxList: List<PCBox>,lockNewSize: Boolean = false, overflowHandler: (Pokemon) -> Unit = ::relocateEvictedBoxPokemon){
|
||||
this.lockedSize = lockNewSize
|
||||
boxes.removeAll(boxList)
|
||||
boxList.flatMap { it.asIterable() }.forEach(overflowHandler)
|
||||
pcChangeObservable.emit(Unit)
|
||||
}
|
||||
|
||||
fun tryRestoreBackedUpPokemon() {
|
||||
var newPosition = getFirstAvailablePosition()
|
||||
|
||||
@@ -17,7 +17,6 @@ import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
@@ -60,9 +59,7 @@ object BattleBuilder {
|
||||
battleFormat: BattleFormat = BattleFormat.GEN_9_SINGLES,
|
||||
cloneParties: Boolean = false,
|
||||
healFirst: Boolean = false,
|
||||
partyAccessor: (ServerPlayer) -> PartyStore = { it.party() },
|
||||
service: ShowdownService = ShowdownService.get(),
|
||||
battleId: UUID = UUID.randomUUID()
|
||||
partyAccessor: (ServerPlayer) -> PartyStore = { it.party() }
|
||||
): BattleStartResult {
|
||||
|
||||
val adjustLevel = battleFormat.adjustLevel
|
||||
@@ -119,9 +116,7 @@ object BattleBuilder {
|
||||
BattleRegistry.startBattle(
|
||||
battleFormat = battleFormat,
|
||||
side1 = BattleSide(player1Actor),
|
||||
side2 = BattleSide(player2Actor),
|
||||
service = service,
|
||||
battleId = battleId
|
||||
side2 = BattleSide(player2Actor)
|
||||
).ifSuccessful {
|
||||
it.battlePartyStores.addAll(battlePartyStores)
|
||||
}
|
||||
@@ -137,9 +132,7 @@ object BattleBuilder {
|
||||
battleFormat: BattleFormat = BattleFormat.GEN_9_MULTI,
|
||||
cloneParties: Boolean = false,
|
||||
healFirst: Boolean = false,
|
||||
partyAccessor: (ServerPlayer) -> PartyStore = { it.party() },
|
||||
service: ShowdownService = ShowdownService.get(),
|
||||
battleId: UUID = UUID.randomUUID()
|
||||
partyAccessor: (ServerPlayer) -> PartyStore = { it.party() }
|
||||
): BattleStartResult {
|
||||
val adjustLevel = battleFormat.adjustLevel
|
||||
val teams = players.mapIndexed { index, it ->
|
||||
@@ -220,9 +213,7 @@ object BattleBuilder {
|
||||
BattleRegistry.startBattle(
|
||||
battleFormat = battleFormat,
|
||||
side1 = BattleSide(playerActors[0], playerActors[1]),
|
||||
side2 = BattleSide(playerActors[2], playerActors[3]),
|
||||
service = service,
|
||||
battleId = battleId
|
||||
side2 = BattleSide(playerActors[2], playerActors[3])
|
||||
).ifSuccessful {
|
||||
it.battlePartyStores.addAll(battlePartyStores)
|
||||
}
|
||||
@@ -254,9 +245,7 @@ object BattleBuilder {
|
||||
cloneParties: Boolean = false,
|
||||
healFirst: Boolean = false,
|
||||
fleeDistance: Float = Cobblemon.config.defaultFleeDistance,
|
||||
party: PartyStore = player.party(),
|
||||
service: ShowdownService = ShowdownService.get(),
|
||||
battleId: UUID = UUID.randomUUID()
|
||||
party: PartyStore = player.party()
|
||||
): BattleStartResult {
|
||||
val playerTeam = party.toBattleTeam(clone = cloneParties, healPokemon = healFirst, leadingPokemon = leadingPokemon).sortedBy { it.health <= 0 }
|
||||
val playerActor = PlayerBattleActor(player.uuid, playerTeam)
|
||||
@@ -297,9 +286,7 @@ object BattleBuilder {
|
||||
BattleRegistry.startBattle(
|
||||
battleFormat = battleFormat,
|
||||
side1 = BattleSide(playerActor),
|
||||
side2 = BattleSide(wildActor),
|
||||
service = service,
|
||||
battleId = battleId
|
||||
side2 = BattleSide(wildActor)
|
||||
).ifSuccessful {
|
||||
if (!cloneParties) {
|
||||
pokemonEntity.battleId = it.battleId
|
||||
@@ -329,9 +316,7 @@ object BattleBuilder {
|
||||
battleFormat: BattleFormat = BattleFormat.GEN_9_SINGLES,
|
||||
cloneParties: Boolean = false,
|
||||
healFirst: Boolean = false,
|
||||
party: PartyStore = player.party(),
|
||||
service: ShowdownService = ShowdownService.get(),
|
||||
battleId: UUID = UUID.randomUUID()
|
||||
party: PartyStore = player.party()
|
||||
): BattleStartResult {
|
||||
val playerTeam = party.toBattleTeam(clone = cloneParties, healPokemon = healFirst, leadingPokemon = leadingPokemon)
|
||||
val playerActor = PlayerBattleActor(player.uuid, playerTeam)
|
||||
@@ -378,9 +363,7 @@ object BattleBuilder {
|
||||
BattleRegistry.startBattle(
|
||||
battleFormat = battleFormat,
|
||||
side1 = BattleSide(playerActor),
|
||||
side2 = BattleSide(npcActor),
|
||||
service = service,
|
||||
battleId = battleId
|
||||
side2 = BattleSide(npcActor)
|
||||
).ifSuccessful { battle ->
|
||||
npcEntity.entityData.update(NPCEntity.BATTLE_IDS) { it + battle.battleId }
|
||||
}
|
||||
@@ -554,4 +537,4 @@ open class ErroredBattleStart(
|
||||
|
||||
val errors: Iterable<BattleStartError>
|
||||
get() = generalErrors + participantErrors.flatMap { it.value }
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ object BattleRegistry {
|
||||
return team.joinToString("]")
|
||||
}
|
||||
|
||||
private fun startShowdown(battle: PokemonBattle, sendMessages: Boolean = true) {
|
||||
private fun startShowdown(battle: PokemonBattle) {
|
||||
|
||||
// Build request message
|
||||
val messages = mutableListOf<String>()
|
||||
@@ -176,9 +176,7 @@ object BattleRegistry {
|
||||
}
|
||||
|
||||
// Compiles the request and sends it off
|
||||
if (sendMessages) {
|
||||
battle.service.startBattle(battle, messages.toTypedArray())
|
||||
}
|
||||
ShowdownService.service.startBattle(battle, messages.toTypedArray())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,7 +186,6 @@ object BattleRegistry {
|
||||
* @param side1 The first side for the battle.
|
||||
* @param side2 The second side for the battle.
|
||||
* @param canPreempt Whether the battle can be blocked by subscribers to [CobblemonEvents.BATTLE_STARTED_PRE].
|
||||
* @param service The showdown service to use for the battle.
|
||||
*
|
||||
* NOTE: this does NOT validate the [battleFormat] or sides before launching Showdown.
|
||||
* This creates the battle and hands it off to Showdown - YOU WILL GET ISSUES IF THE BATTLE IS INVALID!
|
||||
@@ -198,15 +195,12 @@ object BattleRegistry {
|
||||
battleFormat: BattleFormat,
|
||||
side1: BattleSide,
|
||||
side2: BattleSide,
|
||||
canPreempt: Boolean = true,
|
||||
service: ShowdownService = ShowdownService.get(),
|
||||
battleId: UUID = UUID.randomUUID(),
|
||||
sendMessages: Boolean = true
|
||||
canPreempt: Boolean = true
|
||||
): BattleStartResult {
|
||||
val battle = PokemonBattle(battleFormat, side1, side2, service, battleId)
|
||||
val battle = PokemonBattle(battleFormat, side1, side2)
|
||||
val start: () -> Unit = {
|
||||
battleMap[battle.battleId] = battle
|
||||
startShowdown(battle, sendMessages)
|
||||
startShowdown(battle)
|
||||
}
|
||||
|
||||
if (!canPreempt) start().also { return SuccessfulBattleStart(battle) }
|
||||
@@ -240,4 +234,4 @@ object BattleRegistry {
|
||||
fun tick() {
|
||||
battleMap.forEachValue(Long.MAX_VALUE) { it.tick() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,13 +23,13 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
|
||||
this.start()
|
||||
this.latch.await()
|
||||
for (action in whenReady) {
|
||||
action(ShowdownService.get())
|
||||
action(ShowdownService.service)
|
||||
}
|
||||
}
|
||||
|
||||
fun queue(action: (ShowdownService) -> Unit) {
|
||||
if (this.latch.count == 0L) {
|
||||
action(ShowdownService.get())
|
||||
action(ShowdownService.service)
|
||||
} else {
|
||||
this.whenReady.add(action)
|
||||
}
|
||||
@@ -37,8 +37,8 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
|
||||
|
||||
override fun run() {
|
||||
LOGGER.info("Starting showdown service...")
|
||||
ShowdownService.get().openConnection()
|
||||
ShowdownService.service.openConnection()
|
||||
LOGGER.info("Showdown has been started!")
|
||||
this.latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -49,4 +49,4 @@ class RequestInstruction(val battleActor: BattleActor, val message: BattleMessag
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package com.cobblemon.mod.common.battles.runner
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* Class responsible for logging debug messages for the showdown service.
|
||||
*
|
||||
* @author landonjw
|
||||
*/
|
||||
class ShowdownDebugLog {
|
||||
val log = mutableListOf<LogMessage>()
|
||||
|
||||
/**
|
||||
* Adds a log message to the debug log.
|
||||
*
|
||||
* @param source The source of the log message.
|
||||
* @param message The log message.
|
||||
*/
|
||||
fun add(source: String, message: String) {
|
||||
if (!Cobblemon.config.enableDebugKeys) return
|
||||
val now = Date()
|
||||
val timestamp = SimpleDateFormat("HH:mm:ss").format(now)
|
||||
log.add(LogMessage("[$source][${timestamp}]> $message", now))
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all log messages from the debug log.
|
||||
*/
|
||||
fun clear() {
|
||||
log.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves log messages after a specified date.
|
||||
*
|
||||
* @param after The date after which to retrieve log messages. If null, retrieves the last 250 messages.
|
||||
* @return A list of log messages.
|
||||
*/
|
||||
fun get(after: Date?): List<String> {
|
||||
if (after == null) return log.take(250).map { it.message }
|
||||
return log.filter { it.time.after(after) }.map { it.message }
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided messages are the start of the log.
|
||||
*
|
||||
* @param messages The messages to check.
|
||||
* @return True if the messages are the start of the log, false otherwise.
|
||||
*/
|
||||
fun isStartOfLog(messages: List<String>): Boolean {
|
||||
if (messages.isEmpty() && log.isEmpty()) return true
|
||||
return messages.size == log.size && messages.first() == log.first().message
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class representing a log message.
|
||||
*
|
||||
* @property message The log message.
|
||||
* @property time The time the log message was created.
|
||||
*/
|
||||
data class LogMessage(val message: String, val time: Date)
|
||||
}
|
||||
@@ -10,154 +10,32 @@ package com.cobblemon.mod.common.battles.runner
|
||||
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.battles.runner.graal.GraalShowdownService
|
||||
import com.cobblemon.mod.common.battles.runner.remote.UnionShowdownService
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.JsonArray
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service.
|
||||
*
|
||||
* All outgoing calls to showdown should be done through this layer to decouple the mod from a specific implementation.
|
||||
* To get an instance of the service, use the singleton provided via `ShowdownService.get()`.
|
||||
* To get an instance of the service, use the singleton provided via `ShowdownService.get()`
|
||||
*
|
||||
* @since February 27th, 2023
|
||||
* @author landonjw
|
||||
*/
|
||||
interface ShowdownService {
|
||||
/**
|
||||
* The unique identifier for this showdown service.
|
||||
*/
|
||||
val id: ResourceLocation
|
||||
|
||||
/**
|
||||
* The current status of the showdown service.
|
||||
*/
|
||||
val status: ShowdownStatus
|
||||
|
||||
/**
|
||||
* The log of messages from the showdown service.
|
||||
* This is accessed for use in [ShowdownDebugGUI]. It is not intended for general use
|
||||
* and will be empty in production environments.
|
||||
*/
|
||||
val log: ShowdownDebugLog
|
||||
|
||||
/**
|
||||
* Opens a connection to the showdown service.
|
||||
*/
|
||||
fun openConnection()
|
||||
|
||||
/**
|
||||
* Closes the connection to the showdown service.
|
||||
*/
|
||||
fun closeConnection()
|
||||
|
||||
/**
|
||||
* Restarts the connection to the showdown service.
|
||||
*/
|
||||
fun restartConnection()
|
||||
|
||||
/**
|
||||
* Starts a battle in the showdown service.
|
||||
*
|
||||
* @param battle The battle to start.
|
||||
* @param messages The initial messages for the battle.
|
||||
*/
|
||||
fun startBattle(battle: PokemonBattle, messages: Array<String>)
|
||||
|
||||
/**
|
||||
* Sends messages to a specific battle in the showdown service.
|
||||
*
|
||||
* @param battleId The unique identifier of the battle.
|
||||
* @param messages The messages to send.
|
||||
*/
|
||||
fun send(battleId: UUID, messages: Array<String>)
|
||||
|
||||
/**
|
||||
* Sends a message to the showdown service.
|
||||
*
|
||||
* @param message The message to send.
|
||||
*/
|
||||
fun send(message: String)
|
||||
|
||||
/**
|
||||
* Retrieves the ability IDs from the showdown service.
|
||||
*
|
||||
* @return A `JsonArray` of ability IDs.
|
||||
*/
|
||||
fun getAbilityIds(): JsonArray
|
||||
|
||||
/**
|
||||
* Retrieves the moves from the showdown service.
|
||||
*
|
||||
* @return A `JsonArray` of moves.
|
||||
*/
|
||||
fun getMoves(): JsonArray
|
||||
|
||||
/**
|
||||
* Retrieves the item IDs from the showdown service.
|
||||
*
|
||||
* @return A `JsonArray` of item IDs.
|
||||
*/
|
||||
fun getItemIds(): JsonArray
|
||||
|
||||
/**
|
||||
* Registers species in the showdown service.
|
||||
*/
|
||||
fun registerSpecies()
|
||||
|
||||
/**
|
||||
* Registers bag items in the showdown service.
|
||||
*/
|
||||
fun registerBagItems()
|
||||
|
||||
/**
|
||||
* Indicates that species have been initialized in the showdown service.
|
||||
*/
|
||||
fun indicateSpeciesInitialized() {}
|
||||
|
||||
|
||||
companion object {
|
||||
private val services = mutableMapOf<ResourceLocation, ShowdownService>(
|
||||
cobblemonResource("embedded") to GraalShowdownService(cobblemonResource("embedded")),
|
||||
cobblemonResource("union") to UnionShowdownService(cobblemonResource("union"))
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieves the showdown service for the specified path.
|
||||
*
|
||||
* @param path The resource location of the showdown service.
|
||||
* @return The showdown service.
|
||||
* @throws IllegalStateException if no showdown service is found for the specified path.
|
||||
*/
|
||||
fun get(path: ResourceLocation = cobblemonResource("embedded")): ShowdownService {
|
||||
return services[path] ?: error("No showdown service found for path $path")
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a showdown service exists for the specified path.
|
||||
*
|
||||
* @param path The resource location of the showdown service.
|
||||
* @return `true` if the showdown service exists, `false` otherwise.
|
||||
*/
|
||||
fun contains(path: ResourceLocation): Boolean {
|
||||
return services.containsKey(path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all available showdown services.
|
||||
*
|
||||
* @return A list of all showdown services.
|
||||
*/
|
||||
fun all(): List<ShowdownService> {
|
||||
return services.values.toList()
|
||||
}
|
||||
val service: ShowdownService by lazy { GraalShowdownService() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum representing the status of the showdown service.
|
||||
*/
|
||||
enum class ShowdownStatus {
|
||||
CONNECTED, DISCONNECTED, RESTARTING
|
||||
}
|
||||
}
|
||||
+4
-152
@@ -13,12 +13,9 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.battles.BagItems
|
||||
import com.cobblemon.mod.common.battles.ShowdownInterpreter
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownDebugLog
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownStatus
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URI
|
||||
@@ -41,20 +38,12 @@ import org.graalvm.polyglot.io.FileSystem
|
||||
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service via
|
||||
* GraalVM. This directly invokes JavaScript functions provided within the showdown service.
|
||||
*
|
||||
* @property id The unique identifier for this showdown service.
|
||||
* @property context The GraalVM context for executing JavaScript.
|
||||
* @property sendBattleMessageFunction The JavaScript function for sending battle messages.
|
||||
* @property unbundler The unbundler for extracting necessary showdown files.
|
||||
* @property gson The Gson instance for JSON operations.
|
||||
* @property log The debug log for the showdown service.
|
||||
* @property status The current status of the showdown service.
|
||||
*
|
||||
* @see {@code cobbled-exports/cobbled-index.js} within cobbledmon-showdown repository
|
||||
* @see <a href="https://www.graalvm.org/">
|
||||
* @since February 27, 2023
|
||||
* @author Hiroku, landonjw
|
||||
*/
|
||||
class GraalShowdownService(override val id: ResourceLocation) : ShowdownService {
|
||||
class GraalShowdownService : ShowdownService {
|
||||
|
||||
@Transient
|
||||
lateinit var context: Context
|
||||
@@ -65,35 +54,12 @@ class GraalShowdownService(override val id: ResourceLocation) : ShowdownService
|
||||
@Transient
|
||||
val gson = Gson()
|
||||
|
||||
override val log = ShowdownDebugLog()
|
||||
|
||||
override var status: ShowdownStatus = ShowdownStatus.DISCONNECTED
|
||||
private set
|
||||
|
||||
/**
|
||||
* Opens a connection to the showdown service.
|
||||
*/
|
||||
override fun openConnection() = openConnection(false)
|
||||
|
||||
/**
|
||||
* Opens a connection to the showdown service.
|
||||
*
|
||||
* @param silent If true, suppresses changing the status of the connection process.
|
||||
*/
|
||||
private fun openConnection(silent: Boolean) {
|
||||
log.clear()
|
||||
log.add(source = "Minecraft", message = "Opening showdown connection")
|
||||
override fun openConnection() {
|
||||
unbundler.attemptUnbundle()
|
||||
createContext()
|
||||
boot()
|
||||
if (!silent) {
|
||||
status = ShowdownStatus.CONNECTED
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the GraalVM context for executing JavaScript.
|
||||
*/
|
||||
private fun createContext() {
|
||||
val wd = Paths.get("./showdown")
|
||||
val access = HostAccess.newBuilder(HostAccess.EXPLICIT)
|
||||
@@ -150,126 +116,42 @@ class GraalShowdownService(override val id: ResourceLocation) : ShowdownService
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection to the showdown service.
|
||||
*/
|
||||
override fun closeConnection() = closeConnection(false)
|
||||
|
||||
/**
|
||||
* Closes the connection to the showdown service.
|
||||
*
|
||||
* @param silent If true, suppresses changing the status of the connection process.
|
||||
*/
|
||||
private fun closeConnection(silent: Boolean) {
|
||||
log.add(source = "Minecraft", message = "Closing showdown connection")
|
||||
override fun closeConnection() {
|
||||
context.close()
|
||||
if (!silent) {
|
||||
this.status = ShowdownStatus.DISCONNECTED
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts the connection to the showdown service.
|
||||
*/
|
||||
override fun restartConnection() {
|
||||
this.status = ShowdownStatus.RESTARTING
|
||||
try {
|
||||
closeConnection(true)
|
||||
openConnection(true)
|
||||
this.status = ShowdownStatus.CONNECTED
|
||||
} catch (e: Exception) {
|
||||
Cobblemon.LOGGER.error("Failed to restart showdown connection", e)
|
||||
this.status = ShowdownStatus.DISCONNECTED
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Boots the showdown service by evaluating the JavaScript entry point.
|
||||
*/
|
||||
private fun boot() {
|
||||
log.add(source = "Minecraft", message = "Booting showdown")
|
||||
context.eval("js", File("showdown/index.js").readText())
|
||||
sendBattleMessageFunction = context.getBindings("js").getMember("sendBattleMessage")
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a battle in the showdown service.
|
||||
*
|
||||
* @param battle The battle to start.
|
||||
* @param messages The initial messages for the battle.
|
||||
*/
|
||||
override fun startBattle(battle: PokemonBattle, messages: Array<String>) {
|
||||
val startBattleFunction = context.getBindings("js").getMember("startBattle")
|
||||
log.add(source = "Minecraft", message = "Starting battle ${battle.battleId}")
|
||||
messages.forEach { message ->
|
||||
log.add(source = "Minecraft", message = "(${battle.battleId}) $message")
|
||||
}
|
||||
startBattleFunction.execute(this, battle.battleId.toString(), messages)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends messages to a specific battle in the showdown service.
|
||||
*
|
||||
* @param battleId The unique identifier of the battle.
|
||||
* @param messages The messages to send.
|
||||
*/
|
||||
override fun send(battleId: UUID, messages: Array<String>) {
|
||||
sendToShowdown(battleId, messages)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the showdown service.
|
||||
*
|
||||
* @param message The message to send.
|
||||
*/
|
||||
override fun send(message: String) {
|
||||
log.add("Minecraft", message)
|
||||
val regex = Regex("^\\(([^()]*)\\)")
|
||||
val match = regex.find(message)
|
||||
val battleId = match?.groups?.get(1)?.value ?: return
|
||||
val endIndex = match.groups[1]!!.range.last + 1
|
||||
sendToShowdown(UUID.fromString(battleId), arrayOf(message.substring(endIndex)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ability IDs from the showdown service.
|
||||
*
|
||||
* @return A `JsonArray` of ability IDs.
|
||||
*/
|
||||
override fun getAbilityIds(): JsonArray {
|
||||
val getCobbledAbilityIdsFn = context.getBindings("js").getMember("getCobbledAbilityIds")
|
||||
val arrayResult = getCobbledAbilityIdsFn.execute().asString()
|
||||
log.add(source = "Minecraft", message = "Getting ability ids")
|
||||
return gson.fromJson(arrayResult, JsonArray::class.java)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the moves from the showdown service.
|
||||
*
|
||||
* @return A `JsonArray` of moves.
|
||||
*/
|
||||
override fun getMoves(): JsonArray {
|
||||
val getCobbledMovesFn = context.getBindings("js").getMember("getCobbledMoves")
|
||||
log.add(source = "Minecraft", message = "Getting moves")
|
||||
val arrayResult = getCobbledMovesFn.execute().asString()
|
||||
return gson.fromJson(arrayResult, JsonArray::class.java)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item IDs from the showdown service.
|
||||
*
|
||||
* @return A `JsonArray` of item IDs.
|
||||
*/
|
||||
override fun getItemIds(): JsonArray {
|
||||
val getCobbledItemIdsFn = context.getBindings("js").getMember("getCobbledItemIds")
|
||||
log.add(source = "Minecraft", message = "Getting item ids")
|
||||
val arrayResult = getCobbledItemIdsFn.execute().asString()
|
||||
return gson.fromJson(arrayResult, JsonArray::class.java)
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers species in the showdown service.
|
||||
*/
|
||||
override fun registerSpecies() {
|
||||
val receiveSpeciesDataFn = this.context.getBindings("js").getMember("receiveSpeciesData")
|
||||
val jsArray = this.context.eval("js", "new Array();")
|
||||
@@ -282,62 +164,32 @@ class GraalShowdownService(override val id: ResourceLocation) : ShowdownService
|
||||
}
|
||||
}
|
||||
}
|
||||
log.add(source = "Minecraft", message = "Registering species")
|
||||
receiveSpeciesDataFn.execute(jsArray)
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that species have been initialized to the showdown service.
|
||||
*/
|
||||
override fun indicateSpeciesInitialized() {
|
||||
val afterCobbledSpeciesInitFn = this.context.getBindings("js").getMember("afterCobbledSpeciesInit")
|
||||
log.add(source = "Minecraft", message = "Indicating species initialized")
|
||||
afterCobbledSpeciesInitFn.execute()
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers bag items in the showdown service.
|
||||
*/
|
||||
override fun registerBagItems() {
|
||||
val receiveBagItemDataFn = this.context.getBindings("js").getMember("receiveBagItemData")
|
||||
for ((itemId, js) in BagItems.bagItemsScripts) {
|
||||
log.add(source = "Minecraft", message = "Registering bag item $itemId")
|
||||
receiveBagItemDataFn.execute(itemId, js.replace("\n", " "))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends messages to the showdown service.
|
||||
*
|
||||
* @param battleId The unique identifier of the battle.
|
||||
* @param messages The messages to send.
|
||||
*/
|
||||
private fun sendToShowdown(battleId: UUID, messages: Array<String>) {
|
||||
messages.forEach { message ->
|
||||
log.add(source = "Minecraft", message = "($battleId) $message")
|
||||
}
|
||||
sendBattleMessageFunction.execute(battleId.toString(), messages)
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives messages from the showdown service.
|
||||
*
|
||||
* @param battleId The unique identifier of the battle.
|
||||
* @param message The message received.
|
||||
*/
|
||||
@Export
|
||||
fun sendFromShowdown(battleId: String, message: String) {
|
||||
log.add(source = "Showdown", message = "($battleId) $message")
|
||||
ShowdownInterpreter.interpretMessage(UUID.fromString(battleId), message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs messages from the showdown service.
|
||||
*
|
||||
* @param message The message to log.
|
||||
*/
|
||||
@Export
|
||||
fun log(message: String) {
|
||||
Cobblemon.LOGGER.info(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
-381
@@ -1,381 +0,0 @@
|
||||
package com.cobblemon.mod.common.battles.runner.remote
|
||||
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.UnionBattleActor
|
||||
import com.cobblemon.mod.common.api.npc.NPCClasses
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.storage.party.PartyStore
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.battles.*
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownDebugLog
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownStatus
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.HttpService
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.server
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonParser
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.entity.schedule.Activity
|
||||
import java.net.URI
|
||||
import java.net.http.HttpClient
|
||||
import java.net.http.WebSocket
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletionStage
|
||||
|
||||
/**
|
||||
* Implementation of the ShowdownService interface for use with the Union Block.
|
||||
*
|
||||
* This wraps around a WebSocket connection to a remote showdown service.
|
||||
*
|
||||
* @property id The unique identifier for this showdown service.
|
||||
* @property host The host address of the showdown service.
|
||||
* @property httpPort The port for HTTP communication with the showdown service.
|
||||
* @property socketPort The port for WebSocket communication with the showdown service.
|
||||
*
|
||||
* @author landonjw
|
||||
*/
|
||||
class UnionShowdownService(
|
||||
override val id: ResourceLocation,
|
||||
val host: String = "localhost",
|
||||
val httpPort: Int = 8003,
|
||||
val socketPort: Int = 8002
|
||||
) : ShowdownService {
|
||||
|
||||
override var status: ShowdownStatus = ShowdownStatus.DISCONNECTED
|
||||
private set
|
||||
|
||||
override val log = ShowdownDebugLog()
|
||||
|
||||
private lateinit var webSocket: WebSocket
|
||||
|
||||
/**
|
||||
* Opens a connection to the showdown service.
|
||||
*/
|
||||
override fun openConnection() = openConnection(false)
|
||||
|
||||
/**
|
||||
* Opens a connection to the showdown service.
|
||||
*
|
||||
* @param silent If true, suppresses changing the status of the connection process.
|
||||
*/
|
||||
private fun openConnection(silent: Boolean) {
|
||||
log.clear()
|
||||
log.add(source = "Minecraft", message = "Opening showdown connection")
|
||||
try {
|
||||
webSocket = HttpClient.newHttpClient()
|
||||
.newWebSocketBuilder()
|
||||
.buildAsync(URI.create("ws://$host:$socketPort"), WebSocketListener(this))
|
||||
.join()
|
||||
// TODO: See if this is needed
|
||||
registerSpecies()
|
||||
indicateSpeciesInitialized()
|
||||
registerBagItems()
|
||||
if (!silent) {
|
||||
this.status = ShowdownStatus.CONNECTED
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Cobblemon.LOGGER.warn("Failed to open showdown connection to $host:$socketPort", e)
|
||||
log.add(source = "Minecraft", message = "Failed to open showdown connection with error: ${e.message}")
|
||||
if (!silent) {
|
||||
this.status = ShowdownStatus.DISCONNECTED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection to the showdown service.
|
||||
*/
|
||||
override fun closeConnection() = closeConnection(false)
|
||||
|
||||
/**
|
||||
* Closes the connection to the showdown service.
|
||||
*
|
||||
* @param silent If true, suppresses changing the status of the disconnection process.
|
||||
*/
|
||||
private fun closeConnection(silent: Boolean) {
|
||||
log.add(source = "Minecraft", message = "Closing showdown connection")
|
||||
try {
|
||||
webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "Goodbye!").join()
|
||||
} catch (e: Exception) {
|
||||
Cobblemon.LOGGER.warn("Failed to close showdown connection to $host:$socketPort", e)
|
||||
log.add(source = "Minecraft", message = "Failed to close showdown connection to $host:$socketPort with error: ${e.message}")
|
||||
}
|
||||
finally {
|
||||
if (!silent) {
|
||||
this.status = ShowdownStatus.DISCONNECTED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts the connection to the showdown service.
|
||||
*/
|
||||
override fun restartConnection() {
|
||||
this.status = ShowdownStatus.RESTARTING
|
||||
closeConnection(true)
|
||||
openConnection(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a battle in the showdown service.
|
||||
*
|
||||
* @param battle The battle to start.
|
||||
* @param messages The initial messages for the battle.
|
||||
*/
|
||||
override fun startBattle(battle: PokemonBattle, messages: Array<String>) {
|
||||
send("[${battle.battleId}]>start-battle")
|
||||
send(battle.battleId, messages)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends messages to a specific battle in the showdown service.
|
||||
*
|
||||
* @param battleId The unique identifier of the battle.
|
||||
* @param messages The messages to send.
|
||||
*/
|
||||
override fun send(battleId: UUID, messages: Array<String>) {
|
||||
messages.forEach { send("[$battleId]$it") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the showdown service.
|
||||
*
|
||||
* @param message The message to send.
|
||||
*/
|
||||
override fun send(message: String) {
|
||||
webSocket.sendText(message, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ability IDs from the showdown service.
|
||||
*
|
||||
* @return A `JsonArray` of ability IDs.
|
||||
*/
|
||||
override fun getAbilityIds(): JsonArray {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the moves from the showdown service.
|
||||
*
|
||||
* @return A `JsonArray` of moves.
|
||||
*/
|
||||
override fun getMoves(): JsonArray {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item IDs from the showdown service.
|
||||
*
|
||||
* @return A `JsonArray` of item IDs.
|
||||
*/
|
||||
override fun getItemIds(): JsonArray {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers species in the showdown service.
|
||||
*/
|
||||
override fun registerSpecies() {
|
||||
HttpService.post<Unit>(
|
||||
url = "http://$host:$httpPort/Species",
|
||||
body = PokemonSpecies.species.map { PokemonSpecies.ShowdownSpecies(it, null) }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers bag items in the showdown service.
|
||||
*/
|
||||
override fun registerBagItems() {
|
||||
HttpService.post<Unit>(
|
||||
url = "http://$host:$httpPort/BagItems",
|
||||
body = BagItems.bagItemsScripts.map { BagItem(it.key, it.value) }.toList()
|
||||
)
|
||||
}
|
||||
|
||||
override fun indicateSpeciesInitialized() {
|
||||
HttpService.post<Unit>(url = "http://$host:$httpPort/InitializeDex")
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket listener for handling events from the showdown service.
|
||||
*
|
||||
* @property service The UnionShowdownService instance.
|
||||
*/
|
||||
class WebSocketListener(val service: UnionShowdownService) : WebSocket.Listener {
|
||||
val builder = StringBuilder()
|
||||
|
||||
override fun onOpen(webSocket: WebSocket) {
|
||||
service.log.add(source = "Showdown", message = "Connected to server at ${service.host}:${service.socketPort}")
|
||||
super.onOpen(webSocket)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a battle based on the received instruction.
|
||||
*
|
||||
* @param instruction The instruction to start the battle.
|
||||
*/
|
||||
fun startBattle(instruction: String) {
|
||||
val arguments = instruction.replace("|start-battle|", "").split("|")
|
||||
if (arguments.size != 7) return
|
||||
val battleId = UUID.fromString(arguments[0])
|
||||
val playerIdA = UUID.fromString(arguments[1])
|
||||
val playerNameA = arguments[2]
|
||||
val playerIdB = UUID.fromString(arguments[3])
|
||||
val playerNameB = arguments[4]
|
||||
val teamA = JsonParser.parseString(arguments[5]).asJsonObject
|
||||
val teamB = JsonParser.parseString(arguments[6]).asJsonObject
|
||||
val playerA = server()?.playerList?.getPlayer(playerIdA)
|
||||
val playerB = server()?.playerList?.getPlayer(playerIdB)
|
||||
|
||||
println(arguments[5])
|
||||
println(arguments[6])
|
||||
|
||||
if (playerA == null && playerB == null) {
|
||||
Cobblemon.LOGGER.warn("Tried to start battle with no players present on server")
|
||||
return
|
||||
}
|
||||
|
||||
val registryAccess = server()?.registryAccess() ?: return
|
||||
|
||||
val playerAParty = PartyStore(UUID.randomUUID()).loadFromJSON(teamA, registryAccess)
|
||||
val playerBParty = PartyStore(UUID.randomUUID()).loadFromJSON(teamB, registryAccess)
|
||||
val playerATeam = toShowdownBattleTeam(playerAParty.toList()).onEach {
|
||||
it.effectedPokemon.level = 100
|
||||
it.effectedPokemon.heal()
|
||||
}
|
||||
val playerBTeam = toShowdownBattleTeam(playerBParty.toList()).onEach {
|
||||
it.effectedPokemon.level = 100
|
||||
it.effectedPokemon.heal()
|
||||
}
|
||||
|
||||
if (playerA != null && playerB != null) {
|
||||
val playerActorA = PlayerBattleActor(playerIdA, playerATeam)
|
||||
val playerActorB = PlayerBattleActor(playerIdB, playerBTeam)
|
||||
BattleRegistry.startBattle(
|
||||
battleId = battleId,
|
||||
battleFormat = BattleFormat.GEN_9_SINGLES,
|
||||
side1 = BattleSide(playerActorA),
|
||||
side2 = BattleSide(playerActorB),
|
||||
sendMessages = false,
|
||||
service = service
|
||||
)
|
||||
}
|
||||
else if (playerA != null && playerB == null) {
|
||||
val playerActorA = PlayerBattleActor(playerIdA, playerATeam)
|
||||
val npc = NPCEntity(playerA.level())
|
||||
npc.moveTo(playerA.x + 5, playerA.y, playerA.z, playerA.yRot, (playerA.xRot + 180f) % 360f)
|
||||
npc.npc = NPCClasses.getByIdentifier(cobblemonResource("standard"))!!
|
||||
npc.loadTextureFromGameProfileName(playerNameB)
|
||||
npc.initialize(1)
|
||||
npc.config.setDirectly("yaw", DoubleValue((playerA.xRot + 90f) % 360f))
|
||||
npc.uuid = playerIdB
|
||||
npc.customName = playerNameB.text()
|
||||
npc.isInvulnerable = true
|
||||
npc.isMovable = false
|
||||
playerA.level().addFreshEntity(npc)
|
||||
val playerActorB = UnionBattleActor(npc, playerBTeam)
|
||||
BattleRegistry.startBattle(
|
||||
battleId = battleId,
|
||||
battleFormat = BattleFormat.GEN_9_SINGLES,
|
||||
side1 = BattleSide(playerActorA),
|
||||
side2 = BattleSide(playerActorB),
|
||||
sendMessages = false,
|
||||
service = service
|
||||
)
|
||||
}
|
||||
else {
|
||||
val npc = NPCEntity(playerB!!.level())
|
||||
npc.moveTo(playerB.x + 5, playerB.y, playerB.z, playerB.yRot, (playerB.xRot + 180f) % 360f)
|
||||
npc.npc = NPCClasses.getByIdentifier(cobblemonResource("standard"))!!
|
||||
npc.loadTextureFromGameProfileName(playerNameA)
|
||||
npc.initialize(1)
|
||||
npc.uuid = playerIdA
|
||||
npc.customName = playerNameA.text()
|
||||
npc.isInvulnerable = true
|
||||
npc.isMovable = false
|
||||
npc.config.setDirectly("yaw", DoubleValue((playerB.xRot + 90f) % 360f))
|
||||
playerB.level().addFreshEntity(npc)
|
||||
val playerActorA = UnionBattleActor(npc, playerATeam)
|
||||
val playerActorB = PlayerBattleActor(playerIdB, playerBTeam)
|
||||
BattleRegistry.startBattle(
|
||||
battleId = battleId,
|
||||
battleFormat = BattleFormat.GEN_9_SINGLES,
|
||||
side1 = BattleSide(playerActorA),
|
||||
side2 = BattleSide(playerActorB),
|
||||
sendMessages = false,
|
||||
service = service
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of Pokemon to a list of BattlePokemon for the showdown service.
|
||||
*
|
||||
* @param team The list of Pokemon to convert.
|
||||
* @return A list of BattlePokemon.
|
||||
*/
|
||||
fun toShowdownBattleTeam(team: List<Pokemon>): List<BattlePokemon> {
|
||||
return team.map { pokemon ->
|
||||
BattlePokemon(
|
||||
originalPokemon = pokemon,
|
||||
effectedPokemon = pokemon.clone(newUUID = false),
|
||||
postBattleEntityOperation = { entity -> entity.discard() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onText(webSocket: WebSocket, data: CharSequence, last: Boolean): CompletionStage<*>? {
|
||||
if (last) {
|
||||
builder.append(data)
|
||||
val message = builder.toString()
|
||||
service.log.add(source = "Showdown", message = message)
|
||||
|
||||
if (data.startsWith("|start-battle|")) {
|
||||
startBattle(data.toString())
|
||||
}
|
||||
else {
|
||||
val battleIdRegex = Regex("^\\[([^]]+)]")
|
||||
val match = battleIdRegex.find(message) ?: return null
|
||||
|
||||
val battleId = match.groupValues[1]
|
||||
val instruction = message.replace("[$battleId]", "")
|
||||
ShowdownInterpreter.interpretMessage(UUID.fromString(battleId), instruction)
|
||||
}
|
||||
|
||||
builder.clear()
|
||||
}
|
||||
else {
|
||||
builder.append(data)
|
||||
}
|
||||
return super.onText(webSocket, data, last)
|
||||
}
|
||||
|
||||
override fun onError(webSocket: WebSocket, error: Throwable) {
|
||||
service.log.add(source = "Showdown", message = "Error: ${error.message}")
|
||||
Cobblemon.LOGGER.warn("Error in showdown service ${service.id} websocket", error)
|
||||
super.onError(webSocket, error)
|
||||
}
|
||||
|
||||
override fun onClose(webSocket: WebSocket, statusCode: Int, reason: String): CompletionStage<*>? {
|
||||
service.log.add(source = "Showdown", message = "Closed connection with status $statusCode and reason $reason")
|
||||
return super.onClose(webSocket, statusCode, reason)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class representing a bag item. We use this for serialization to the showdown service.
|
||||
*
|
||||
* @property id The unique identifier of the bag item.
|
||||
* @property script The script associated with the bag item.
|
||||
*/
|
||||
private data class BagItem(val id: String, val script: String)
|
||||
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.battles.runner.socket
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.battles.BagItems
|
||||
import com.cobblemon.mod.common.battles.ShowdownInterpreter
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.pokemon.Species
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.io.OutputStreamWriter
|
||||
import java.net.InetAddress
|
||||
import java.net.Socket
|
||||
import java.nio.charset.Charset
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service via
|
||||
* a socket client.
|
||||
*
|
||||
* This is primarily used for debugging purposes, but could be extended in the future to provide
|
||||
* a means of connecting to a remote Showdown server. This does not provide any fault handling in the
|
||||
* event that the server goes down.
|
||||
*
|
||||
* When messages are sent to showdown, this will await a response from showdown.
|
||||
* The protocol for messages sent from showdown is <length of characters in payload><payload>,
|
||||
* and a payload length of 0 indicates that there is no response.
|
||||
*
|
||||
* @see {@code sim/cobbled-debug-server.ts} within cobblemon-showdown repository
|
||||
* @since February 27, 2023
|
||||
* @author landonjw
|
||||
*/
|
||||
class SocketShowdownService(val host: String = "localhost", val port: Int = 18468, val localPort: Int = 0) : ShowdownService {
|
||||
|
||||
private lateinit var socket: Socket
|
||||
private lateinit var writer: OutputStreamWriter
|
||||
private lateinit var reader: BufferedReader
|
||||
val gson = Gson()
|
||||
|
||||
override fun openConnection() {
|
||||
socket = Socket(InetAddress.getLocalHost(), port, InetAddress.getLocalHost(), localPort)
|
||||
writer = socket.getOutputStream().writer(charset = Charset.forName("ascii"))
|
||||
reader = BufferedReader(InputStreamReader(socket.getInputStream()))
|
||||
}
|
||||
|
||||
override fun closeConnection() {
|
||||
socket.close()
|
||||
}
|
||||
|
||||
override fun startBattle(battle: PokemonBattle, messages: Array<String>) {
|
||||
writer.write(">startbattle ${battle.battleId}\n")
|
||||
acknowledge { Cobblemon.LOGGER.error("Failed to start battle!") }
|
||||
send(battle.battleId, messages)
|
||||
}
|
||||
|
||||
override fun send(battleId: UUID, messages: Array<String>) {
|
||||
for (message in messages) {
|
||||
writer.write("$battleId~$message\n")
|
||||
writer.flush()
|
||||
readBattleInput().forEach { interpretMessage(battleId, it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun read(reader: BufferedReader, size: Int): String {
|
||||
val buffer = CharArray(size)
|
||||
while (true) {
|
||||
if(reader.read(buffer) == 0) continue
|
||||
return String(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readMessage(): String {
|
||||
val payloadSize = read(reader, 8).toInt()
|
||||
val payload = read(reader, payloadSize)
|
||||
return payload
|
||||
}
|
||||
|
||||
private fun readBattleInput(): List<String> {
|
||||
val lines = mutableListOf<String>()
|
||||
val numLines = read(reader, 8).toInt()
|
||||
if (numLines != 0) {
|
||||
for (i in 0 until numLines) {
|
||||
lines.add(readMessage())
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
private fun interpretMessage(battleId: UUID, message: String) {
|
||||
ShowdownInterpreter.interpretMessage(battleId, message)
|
||||
}
|
||||
|
||||
override fun getAbilityIds(): JsonArray {
|
||||
writer.write(">getCobbledAbilityIds")
|
||||
writer.flush()
|
||||
val response = readMessage()
|
||||
return gson.fromJson(response, JsonArray::class.java)
|
||||
}
|
||||
|
||||
override fun getMoves(): JsonArray {
|
||||
writer.write(">getCobbledMoves\n")
|
||||
writer.flush()
|
||||
val response = readMessage()
|
||||
return gson.fromJson(response, JsonArray::class.java)
|
||||
}
|
||||
|
||||
override fun getItemIds(): JsonArray {
|
||||
writer.write(">getCobbledItemIds")
|
||||
writer.flush()
|
||||
val response = readMessage()
|
||||
return gson.fromJson(response, JsonArray::class.java)
|
||||
}
|
||||
|
||||
private fun sendSpeciesData(species: Species, form: FormData?) {
|
||||
writer.write(">receiveSpeciesData ${gson.toJson(PokemonSpecies.ShowdownSpecies(species, form))}\n")
|
||||
acknowledge()
|
||||
}
|
||||
|
||||
private fun sendBagItem(itemId: String, js: String) {
|
||||
writer.write(">receiveBagItemData $itemId $js")
|
||||
acknowledge { Cobblemon.LOGGER.error("Failed to send bag item to Showdown: $itemId") }
|
||||
}
|
||||
|
||||
override fun registerSpecies() {
|
||||
writer.write(">resetSpeciesData\n")
|
||||
acknowledge()
|
||||
PokemonSpecies.species.forEach { species ->
|
||||
sendSpeciesData(species, null)
|
||||
species.forms.forEach { form ->
|
||||
if (form != species.standardForm) {
|
||||
sendSpeciesData(species, form)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun acknowledge(ifFails: () -> Unit = {}) {
|
||||
writer.flush()
|
||||
val ack = CharArray(3)
|
||||
reader.read(ack)
|
||||
if (String(ack) != "ACK") {
|
||||
ifFails()
|
||||
}
|
||||
}
|
||||
|
||||
override fun registerBagItems() {
|
||||
for ((itemId, js) in BagItems.bagItemsScripts) {
|
||||
sendBagItem(itemId, js.replace("\n", " "))
|
||||
}
|
||||
}
|
||||
|
||||
override fun indicateSpeciesInitialized() {
|
||||
writer.write(">afterCobbledSpeciesInit")
|
||||
acknowledge()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
package com.cobblemon.mod.common.client.gui.debug
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork
|
||||
import com.cobblemon.mod.common.api.text.text
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownStatus
|
||||
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
|
||||
import com.cobblemon.mod.common.client.gui.debug.ShowdownDebugGUI.Companion.activeShowdownService
|
||||
import com.cobblemon.mod.common.client.render.drawScaledText
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.showdown.ShowdownRefreshInfoPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.showdown.RequestShowdownInfoPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.showdown.ShowdownActionPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.showdown.ShowdownInputPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.mojang.blaze3d.platform.ClipboardManager
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.client.gui.components.Button
|
||||
import net.minecraft.client.gui.components.EditBox
|
||||
import net.minecraft.client.gui.components.ObjectSelectionList
|
||||
import net.minecraft.client.gui.components.ObjectSelectionList.Entry
|
||||
import net.minecraft.client.gui.screens.Screen
|
||||
import net.minecraft.locale.Language
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.util.FormattedCharSequence
|
||||
import java.awt.Color
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* GUI screen for debugging the Showdown service in the Cobblemon mod.
|
||||
* This screen allows users to view and interact with the Showdown service logs and status.
|
||||
*
|
||||
* @property status The current status of the showdown service.
|
||||
* @property service The resource location of the showdown service.
|
||||
* @property messages A list of messages from the active showdown service currently being refreshed.
|
||||
* @property availableServices A list of available showdown services. This allows the client to switch which server they are viewing.
|
||||
* @property requestProcessedAt The date and time when the request was processed.
|
||||
* @property includesStartOfLog Indicates if the messages include the start of the log.
|
||||
*
|
||||
* @author landonjw
|
||||
*/
|
||||
class ShowdownDebugGUI : Screen(lang("ui.debug.showdown")), CobblemonRenderable {
|
||||
|
||||
companion object {
|
||||
var activeShowdownService = cobblemonResource("embedded")
|
||||
private set
|
||||
}
|
||||
|
||||
private var showdownStatus: ShowdownStatus? = null
|
||||
val log = mutableListOf<String>()
|
||||
var availableServices = listOf(cobblemonResource("embedded"))
|
||||
var lastProcessedLogTime: Date? = null
|
||||
|
||||
val outputWindow = addRenderableWidget(ShowdownOutputList())
|
||||
val serviceButton = addRenderableWidget(
|
||||
Button.builder(Component.literal("Service: $activeShowdownService")) {
|
||||
cycleActiveService()
|
||||
}.bounds(10, 10, 200, 20).build()
|
||||
)
|
||||
|
||||
val startButton = addRenderableWidget(
|
||||
Button.builder(Component.literal("Start")) { button ->
|
||||
CobblemonNetwork.sendToServer(ShowdownActionPacket(
|
||||
service = activeShowdownService,
|
||||
action = ShowdownActionPacket.Action.START
|
||||
))
|
||||
}.bounds(10, 40, 50, 20).build()
|
||||
)
|
||||
|
||||
val stopButton = addRenderableWidget(
|
||||
Button.builder(Component.literal("Stop")) { button ->
|
||||
CobblemonNetwork.sendToServer(ShowdownActionPacket(
|
||||
service = activeShowdownService,
|
||||
action = ShowdownActionPacket.Action.STOP
|
||||
))
|
||||
}.bounds(70, 40, 50, 20).build()
|
||||
)
|
||||
|
||||
val restartButton = addRenderableWidget(
|
||||
Button.builder(Component.literal("Restart")) { button ->
|
||||
this.showdownStatus = ShowdownStatus.RESTARTING
|
||||
CobblemonNetwork.sendToServer(ShowdownActionPacket(
|
||||
service = activeShowdownService,
|
||||
action = ShowdownActionPacket.Action.RESTART
|
||||
))
|
||||
}.bounds(130, 40, 50, 20).build()
|
||||
)
|
||||
|
||||
val startNPCBattleButton = addRenderableWidget(
|
||||
Button.builder(Component.literal("Queue")) { button ->
|
||||
CobblemonNetwork.sendToServer(ShowdownActionPacket(
|
||||
service = activeShowdownService,
|
||||
action = ShowdownActionPacket.Action.QUEUE
|
||||
))
|
||||
}.bounds(10, 70, 100, 20).build()
|
||||
)
|
||||
|
||||
val clipboardManager = ClipboardManager()
|
||||
var timer = Timer()
|
||||
|
||||
init {
|
||||
addRenderableWidget(
|
||||
Button.builder(Component.literal("Copy to Clipboard")) Clipboard@{
|
||||
if (log.isEmpty()) return@Clipboard
|
||||
clipboardManager.setClipboard(Minecraft.getInstance().window.window, log.joinToString("\n"))
|
||||
}.bounds(170, Minecraft.getInstance().window.guiScaledHeight - 40, 100, 30).build()
|
||||
)
|
||||
|
||||
addRenderableWidget(ShowdownInputWidget(this))
|
||||
startTimer()
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycles through the available showdown services.
|
||||
*/
|
||||
fun cycleActiveService() {
|
||||
if (availableServices.size == 1) return
|
||||
val currentIndex = availableServices.indexOf(activeShowdownService)
|
||||
activeShowdownService = availableServices[(currentIndex + 1) % availableServices.size]
|
||||
serviceButton.message = Component.literal("Service: $activeShowdownService")
|
||||
outputWindow.reset()
|
||||
log.clear()
|
||||
lastProcessedLogTime = null
|
||||
startTimer()
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the timer for periodically refreshing the showdown info.
|
||||
*/
|
||||
fun startTimer() {
|
||||
timer.cancel()
|
||||
timer = Timer()
|
||||
timer.schedule(object : TimerTask() {
|
||||
override fun run() {
|
||||
CobblemonNetwork.sendToServer(RequestShowdownInfoPacket(activeShowdownService, lastProcessedLogTime))
|
||||
}
|
||||
}, 0, 500)
|
||||
}
|
||||
|
||||
override fun onClose() {
|
||||
timer.cancel()
|
||||
super.onClose()
|
||||
}
|
||||
|
||||
override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
|
||||
guiGraphics.fill(0, 0, width, height, Color(0, 0, 0, 100).rgb)
|
||||
super.render(guiGraphics, mouseX, mouseY, partialTick)
|
||||
if (showdownStatus != null) {
|
||||
guiGraphics.fill(
|
||||
190,
|
||||
45,
|
||||
200,
|
||||
55,
|
||||
when (showdownStatus!!) {
|
||||
ShowdownStatus.DISCONNECTED -> Color(255, 0, 0, 255).rgb
|
||||
ShowdownStatus.RESTARTING -> Color(255, 255, 0, 255).rgb
|
||||
ShowdownStatus.CONNECTED -> Color(0, 255, 0, 255).rgb
|
||||
}
|
||||
)
|
||||
drawScaledText(
|
||||
context = guiGraphics,
|
||||
text = Component.literal(showdownStatus!!.name.replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(
|
||||
Locale.getDefault()
|
||||
) else it.toString()
|
||||
}),
|
||||
x = 205,
|
||||
y = 47
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the showdown info with the data from the given packet.
|
||||
*
|
||||
* @param packet The packet containing the refreshed showdown info.
|
||||
*/
|
||||
fun refreshShowdownInfo(packet: ShowdownRefreshInfoPacket) {
|
||||
// Prevent false positives from old packets
|
||||
if (activeShowdownService != packet.service) return
|
||||
if (packet.includesStartOfLog) {
|
||||
log.clear()
|
||||
outputWindow.reset()
|
||||
}
|
||||
packet.messages.forEach {
|
||||
log.add(it)
|
||||
outputWindow.addNewEntry(it.text())
|
||||
}
|
||||
lastProcessedLogTime = packet.requestProcessedAt
|
||||
showdownStatus = packet.status
|
||||
when (showdownStatus) {
|
||||
ShowdownStatus.CONNECTED -> {
|
||||
startButton.active = false
|
||||
stopButton.active = true
|
||||
restartButton.active = true
|
||||
startNPCBattleButton.active = true
|
||||
}
|
||||
ShowdownStatus.DISCONNECTED -> {
|
||||
startButton.active = true
|
||||
stopButton.active = false
|
||||
restartButton.active = false
|
||||
startNPCBattleButton.active = false
|
||||
}
|
||||
ShowdownStatus.RESTARTING -> {
|
||||
startButton.active = false
|
||||
stopButton.active = false
|
||||
restartButton.active = false
|
||||
startNPCBattleButton.active = false
|
||||
}
|
||||
else -> {
|
||||
startButton.active = false
|
||||
stopButton.active = false
|
||||
restartButton.active = false
|
||||
startNPCBattleButton.active = false
|
||||
}
|
||||
}
|
||||
this.availableServices = packet.availableServices
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* List widget for displaying the output log of the Showdown service.
|
||||
*/
|
||||
class ShowdownOutputList : ObjectSelectionList<OutputEntry>(
|
||||
Minecraft.getInstance(),
|
||||
Minecraft.getInstance().window.guiScaledWidth - 280 - 10,
|
||||
Minecraft.getInstance().window.guiScaledHeight - 10 - 40,
|
||||
10,
|
||||
14
|
||||
) {
|
||||
/**
|
||||
* Adds a new entry to the output list.
|
||||
*
|
||||
* @param text The text to add as a new entry.
|
||||
*/
|
||||
fun addNewEntry(text: Component) {
|
||||
val line = text.copy()
|
||||
val textRenderer = Minecraft.getInstance().font
|
||||
val lines = Language.getInstance().getVisualOrder(textRenderer.splitter.splitLines(line, rowWidth, line.style))
|
||||
val fullyScrolledDown = maxScroll - scrollAmount < 10
|
||||
lines.forEach { addEntry(OutputEntry(it)) }
|
||||
if (fullyScrolledDown) {
|
||||
scrollAmount = maxScroll.toDouble()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSelected(): OutputEntry? = null
|
||||
|
||||
override fun getRowWidth(): Int {
|
||||
return Minecraft.getInstance().window.guiScaledWidth - 280 - 10 - 10
|
||||
}
|
||||
|
||||
override fun getScrollbarPosition(): Int {
|
||||
return this.x + rowWidth + 5
|
||||
}
|
||||
|
||||
override fun renderWidget(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
|
||||
setRectangle(
|
||||
Minecraft.getInstance().window.guiScaledWidth - 280 - 10,
|
||||
Minecraft.getInstance().window.guiScaledHeight - 10 - 10 - 40,
|
||||
280,
|
||||
10
|
||||
)
|
||||
this.x = 280
|
||||
super.renderWidget(guiGraphics, mouseX, mouseY, partialTick)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the output list by clearing all entries.
|
||||
*/
|
||||
fun reset() {
|
||||
super.clearEntries()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry for the output list, representing a single line of text.
|
||||
*
|
||||
* @param line The formatted line of text to display.
|
||||
*/
|
||||
class OutputEntry(val line: FormattedCharSequence) : Entry<OutputEntry>() {
|
||||
override fun getNarration() = "".text()
|
||||
override fun render(
|
||||
context: GuiGraphics,
|
||||
index: Int,
|
||||
rowTop: Int,
|
||||
rowLeft: Int,
|
||||
rowWidth: Int,
|
||||
rowHeight: Int,
|
||||
mouseX: Int,
|
||||
mouseY: Int,
|
||||
isHovered: Boolean,
|
||||
partialTicks: Float
|
||||
) {
|
||||
drawScaledText(
|
||||
context,
|
||||
line,
|
||||
rowLeft,
|
||||
rowTop - 2
|
||||
)
|
||||
}
|
||||
|
||||
override fun isFocused() = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Input widget for entering commands to the Showdown service.
|
||||
*/
|
||||
class ShowdownInputWidget(val gui: ShowdownDebugGUI) : EditBox(
|
||||
Minecraft.getInstance().font,
|
||||
280,
|
||||
Minecraft.getInstance().window.guiScaledHeight - 40,
|
||||
Minecraft.getInstance().window.guiScaledWidth - 280 - 10,
|
||||
30,
|
||||
Component.literal("Enter command")
|
||||
) {
|
||||
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
||||
val result = super.keyPressed(keyCode, scanCode, modifiers)
|
||||
if (keyCode == 257 && this.value.isNotEmpty()) {
|
||||
CobblemonNetwork.sendToServer(ShowdownInputPacket(
|
||||
service = activeShowdownService,
|
||||
input = this.value
|
||||
))
|
||||
deleteCharsToPos(0)
|
||||
CobblemonNetwork.sendToServer(RequestShowdownInfoPacket(activeShowdownService, gui.lastProcessedLogTime))
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
+2
-16
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.client.keybind.keybinds
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.client.gui.debug.ShowdownDebugGUI
|
||||
import com.cobblemon.mod.common.client.keybind.CobblemonKeyBinding
|
||||
import com.cobblemon.mod.common.client.keybind.KeybindCategories
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
|
||||
@@ -27,10 +26,8 @@ object DebugKeybindings {
|
||||
TranslateRightKeybinding(),
|
||||
TranslateUpKeybinding(),
|
||||
TranslateDownKeybinding(),
|
||||
PrintModelSettingsKeybinding(),
|
||||
ToggleShowdownDebugGUIKeybinding()
|
||||
PrintModelSettingsKeybinding()
|
||||
)
|
||||
|
||||
class ScaleUpKeybinding: CobblemonKeyBinding(
|
||||
"key.cobblemon.scaleportraitup",
|
||||
InputConstants.Type.KEYSYM,
|
||||
@@ -148,15 +145,4 @@ object DebugKeybindings {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleShowdownDebugGUIKeybinding : CobblemonKeyBinding(
|
||||
"key.cobblemon.toggleshowdowndebuggui",
|
||||
InputConstants.Type.KEYSYM,
|
||||
InputConstants.KEY_APOSTROPHE,
|
||||
KeybindCategories.COBBLEMON_DEBUG_CATEGORY
|
||||
) {
|
||||
override fun onPress() {
|
||||
Minecraft.getInstance().setScreen(ShowdownDebugGUI())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
package com.cobblemon.mod.common.client.net.debug.showdown
|
||||
|
||||
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.client.gui.debug.ShowdownDebugGUI
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.showdown.ShowdownRefreshInfoPacket
|
||||
import net.minecraft.client.Minecraft
|
||||
|
||||
/**
|
||||
* Handles the reception of a [ShowdownRefreshInfoPacket] on the client side.
|
||||
* This handler updates the Showdown Debug GUI with the latest showdown information.
|
||||
* @author landonjw
|
||||
*/
|
||||
object ShowdownRefreshInfoHandler : ClientNetworkPacketHandler<ShowdownRefreshInfoPacket> {
|
||||
/**
|
||||
* Handles the received [ShowdownRefreshInfoPacket] by refreshing the showdown information
|
||||
* in the [ShowdownDebugGUI] if it is currently open.
|
||||
*
|
||||
* @param packet The received packet containing the showdown information.
|
||||
* @param client The Minecraft client instance.
|
||||
*/
|
||||
override fun handle(packet: ShowdownRefreshInfoPacket, client: Minecraft) {
|
||||
if (client.screen is ShowdownDebugGUI) {
|
||||
(client.screen as ShowdownDebugGUI).refreshShowdownInfo(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.command
|
||||
|
||||
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
|
||||
import com.cobblemon.mod.common.api.storage.pc.PCBox
|
||||
import com.cobblemon.mod.common.api.storage.pc.PCStore
|
||||
import com.cobblemon.mod.common.api.text.*
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.mojang.brigadier.Command
|
||||
import com.mojang.brigadier.CommandDispatcher
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType
|
||||
import com.mojang.brigadier.context.CommandContext
|
||||
import net.minecraft.commands.CommandSourceStack
|
||||
import net.minecraft.commands.Commands.argument
|
||||
import net.minecraft.commands.Commands.literal
|
||||
import net.minecraft.commands.arguments.EntityArgument
|
||||
import net.minecraft.commands.arguments.selector.EntitySelector
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
object ChangePCBoxesCommand {
|
||||
private const val NAME = "boxcount"
|
||||
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
|
||||
dispatcher.register(literal(NAME)
|
||||
.permission(CobblemonPermissions.CHANGE_BOX_COUNT)
|
||||
.then(literal("query").then(
|
||||
argument("player", EntityArgument.player()).executes(::executeQuery))
|
||||
)
|
||||
.then(literal("add").then(
|
||||
argument("player", EntityArgument.player()).then(
|
||||
argument("amount", IntegerArgumentType.integer(1, 1000)).executes(::executeAdd)))
|
||||
)
|
||||
.then(literal("remove").then(
|
||||
argument("player", EntityArgument.player()).then(
|
||||
argument("amount", IntegerArgumentType.integer(1, 1000)).executes(::executeRemove)))
|
||||
)
|
||||
.then(literal("set").then(
|
||||
argument("player", EntityArgument.player()).then(
|
||||
argument("amount", IntegerArgumentType.integer(1, 1000)).executes(::executeSet)))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun executeQuery(context: CommandContext<CommandSourceStack>): Int {
|
||||
val player = context.getArgument("player", EntitySelector::class.java).findSinglePlayer(context.source)
|
||||
val playerPc = player.pc()
|
||||
context.source.sendSystemMessage(lang("command.boxcount", player.name, playerPc.boxes.size))
|
||||
|
||||
return Command.SINGLE_SUCCESS
|
||||
}
|
||||
|
||||
private fun executeAdd(context: CommandContext<CommandSourceStack>): Int {
|
||||
val player = context.getArgument("player", EntitySelector::class.java).findSinglePlayer(context.source)
|
||||
val playerPc = player.pc()
|
||||
val amount = context.getArgument("amount", Int::class.java)
|
||||
|
||||
playerPc.resize(playerPc.boxes.size + amount, true)
|
||||
playerPc.sendTo(player)
|
||||
context.source.sendSystemMessage(lang("command.changeboxcount", player.name, playerPc.boxes.size).green())
|
||||
|
||||
return Command.SINGLE_SUCCESS
|
||||
}
|
||||
|
||||
private fun executeRemove(context: CommandContext<CommandSourceStack>): Int {
|
||||
val player = context.getArgument("player", EntitySelector::class.java).findSinglePlayer(context.source)
|
||||
val playerPc = player.pc()
|
||||
val amount = context.getArgument("amount", Int::class.java)
|
||||
|
||||
if (amount < playerPc.boxes.size) {
|
||||
val emptyBoxes = playerPc.boxes.filter { it.getNonEmptySlots().isEmpty() }
|
||||
|
||||
if (emptyBoxes.size >= amount) {
|
||||
remove(context,player, playerPc, amount, emptyBoxes)
|
||||
return Command.SINGLE_SUCCESS
|
||||
}
|
||||
else {
|
||||
context.source.sendSystemMessage(lang("command.changeboxcount.removing_not_empty_box").red().append("\n").append(
|
||||
lang("command.changeboxcount.force_remove").gray().onClick(true){remove(context,player, playerPc, amount, emptyBoxes)}
|
||||
))
|
||||
return 0
|
||||
}
|
||||
}
|
||||
else {
|
||||
context.source.sendSystemMessage(lang("command.changeboxcount.removing_too_many_boxes", player.name, playerPc.boxes.size).red())
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeSet(context: CommandContext<CommandSourceStack>): Int {
|
||||
val player = context.getArgument("player", EntitySelector::class.java).findSinglePlayer(context.source)
|
||||
val playerPc = player.pc()
|
||||
val amount = context.getArgument("amount", Int::class.java)
|
||||
|
||||
if (amount < playerPc.boxes.size) {
|
||||
val emptyBoxes = playerPc.boxes.filter { it.getNonEmptySlots().isEmpty() }
|
||||
val deleteAmount = playerPc.boxes.size - amount
|
||||
|
||||
if (emptyBoxes.size <= deleteAmount) {
|
||||
context.source.sendSystemMessage(lang("command.changeboxcount.removing_not_empty_box").red().append("\n").append(
|
||||
lang("command.changeboxcount.force_remove").gray().onClick(true){ remove(context,player, playerPc, deleteAmount, emptyBoxes) }
|
||||
))
|
||||
return 0
|
||||
}
|
||||
else {
|
||||
remove(context,player, playerPc, deleteAmount, emptyBoxes)
|
||||
return Command.SINGLE_SUCCESS
|
||||
}
|
||||
}
|
||||
set(context,player, playerPc, amount)
|
||||
|
||||
return Command.SINGLE_SUCCESS
|
||||
}
|
||||
|
||||
private fun set(context: CommandContext<CommandSourceStack>,player: ServerPlayer, playerPc: PCStore, amount: Int){
|
||||
playerPc.resize(amount, true)
|
||||
playerPc.sendTo(player)
|
||||
context.source.sendSystemMessage(lang("command.changeboxcount", player.name, playerPc.boxes.size).green())
|
||||
}
|
||||
|
||||
private fun remove(context: CommandContext<CommandSourceStack>,player: ServerPlayer, playerPc: PCStore, amount: Int,emptyBoxes: List<PCBox>){
|
||||
if (amount <= emptyBoxes.size) {
|
||||
playerPc.removeListOfBoxes(emptyBoxes.takeLast(amount),true)
|
||||
}
|
||||
else {
|
||||
val slicedBoxes = emptyBoxes + playerPc.boxes.takeLast(amount - emptyBoxes.size)
|
||||
playerPc.removeListOfBoxes(slicedBoxes,true)
|
||||
}
|
||||
playerPc.initialize()
|
||||
playerPc.sendTo(player)
|
||||
context.source.sendSystemMessage(lang("command.changeboxcount", player.name, playerPc.boxes.size).green())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.command
|
||||
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.mojang.brigadier.Command
|
||||
import com.mojang.brigadier.CommandDispatcher
|
||||
import com.mojang.brigadier.context.CommandContext
|
||||
import net.minecraft.commands.CommandSourceStack
|
||||
import net.minecraft.commands.Commands
|
||||
import net.minecraft.network.chat.Component
|
||||
|
||||
object ReloadShowdownCommand {
|
||||
|
||||
fun register(dispatcher: CommandDispatcher<CommandSourceStack>) {
|
||||
val command = Commands.literal("reloadshowdown")
|
||||
.requires { it.hasPermission(4) }
|
||||
.executes(::execute)
|
||||
dispatcher.register(command)
|
||||
}
|
||||
|
||||
private fun execute(context: CommandContext<CommandSourceStack>): Int {
|
||||
try {
|
||||
ShowdownService.service.closeConnection()
|
||||
ShowdownService.service.openConnection()
|
||||
ShowdownService.service.registerSpecies()
|
||||
ShowdownService.service.registerBagItems()
|
||||
context.source.sendSystemMessage(Component.literal("Reloaded showdown"))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return Command.SINGLE_SUCCESS
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.command.argument.NPCClassArgumentType
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.alias
|
||||
import com.cobblemon.mod.common.util.commandLang
|
||||
import com.cobblemon.mod.common.util.permission
|
||||
import com.cobblemon.mod.common.util.toBlockPos
|
||||
import com.mojang.brigadier.Command
|
||||
@@ -83,4 +84,4 @@ object SpawnNPCCommand {
|
||||
}
|
||||
throw FAILED_SPAWN_EXCEPTION.create()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.command
|
||||
|
||||
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
|
||||
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
|
||||
import com.cobblemon.mod.common.util.permission
|
||||
import com.mojang.brigadier.Command
|
||||
import com.mojang.brigadier.CommandDispatcher
|
||||
import com.mojang.brigadier.context.CommandContext
|
||||
import net.minecraft.commands.CommandSourceStack
|
||||
import net.minecraft.commands.Commands.argument
|
||||
import net.minecraft.commands.Commands.literal
|
||||
import net.minecraft.commands.arguments.EntityArgument
|
||||
|
||||
object SpectateBattleCommand {
|
||||
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
|
||||
val command = literal("spectateBattle")
|
||||
.permission(CobblemonPermissions.SPECTATE_BATTLE)
|
||||
.then(argument("player", EntityArgument.player())
|
||||
.executes(::execute))
|
||||
|
||||
dispatcher.register(command)
|
||||
}
|
||||
|
||||
private fun execute(context: CommandContext<CommandSourceStack>): Int {
|
||||
val player = context.source.playerOrException
|
||||
|
||||
val target = EntityArgument.getPlayer(context, "player")
|
||||
|
||||
SpectateBattleHandler.spectateBattle(target, player)
|
||||
|
||||
return Command.SINGLE_SUCCESS
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,6 @@ import java.util.*
|
||||
|
||||
object CobblemonDataProvider : DataProvider {
|
||||
|
||||
// Both Forge n Fabric keep insertion order so if a registry depends on another simply register it after
|
||||
internal var canReload = true
|
||||
// Both Forge n Fabric keep insertion order so if a registry depends on another simply register it after
|
||||
private val registries = linkedSetOf<DataRegistry>()
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.cobblemon.mod.common.util.effectiveName
|
||||
import com.cobblemon.mod.common.util.update
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
open class NPCBattleActor(
|
||||
class NPCBattleActor(
|
||||
val npc: NPCEntity,
|
||||
pokemonList: List<BattlePokemon>,
|
||||
val skill: Int
|
||||
@@ -84,4 +84,4 @@ open class NPCBattleActor(
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
package com.cobblemon.mod.common.net.messages.client.debug.showdown
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownStatus
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import java.util.Date
|
||||
|
||||
|
||||
/**
|
||||
* Packet sent to refresh the showdown information with the current status, messages, and available services.
|
||||
* This is typically sent when a user is in the Showdown Debug UI after they send a [RequestShowdownInfoPacket].
|
||||
*
|
||||
* @property status The current status of the showdown service.
|
||||
* @property service The resource location of the showdown service.
|
||||
* @property messages A list of messages from the active showdown service currently being refreshed.
|
||||
* @property availableServices A list of available showdown services. This allows the client to switch which server they are viewing.
|
||||
* @property requestProcessedAt The date and time when the request was processed.
|
||||
* @property includesStartOfLog Indicates if the messages include the start of the log.
|
||||
*
|
||||
* @author landonjw
|
||||
*/
|
||||
class ShowdownRefreshInfoPacket(
|
||||
val status: ShowdownStatus,
|
||||
val service: ResourceLocation,
|
||||
val messages: List<String>,
|
||||
val availableServices: List<ResourceLocation>,
|
||||
val requestProcessedAt: Date,
|
||||
val includesStartOfLog: Boolean
|
||||
) : NetworkPacket<ShowdownRefreshInfoPacket> {
|
||||
companion object {
|
||||
val ID = cobblemonResource("refresh_showdown_info")
|
||||
|
||||
/**
|
||||
* Decodes a [ShowdownRefreshInfoPacket] from the given [RegistryFriendlyByteBuf].
|
||||
*
|
||||
* @param buffer The buffer to read from.
|
||||
* @return The decoded [ShowdownRefreshInfoPacket].
|
||||
*/
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = ShowdownRefreshInfoPacket(
|
||||
status = ShowdownStatus.valueOf(buffer.readString()),
|
||||
service = buffer.readResourceLocation(),
|
||||
messages = buffer.readList { buffer.readString() },
|
||||
availableServices = buffer.readList { buffer.readResourceLocation() },
|
||||
requestProcessedAt = buffer.readDate(),
|
||||
includesStartOfLog = buffer.readBoolean()
|
||||
)
|
||||
}
|
||||
|
||||
override val id = ID
|
||||
|
||||
/**
|
||||
* Encodes this [ShowdownRefreshInfoPacket] to the given [RegistryFriendlyByteBuf].
|
||||
*
|
||||
* @param buffer The buffer to write to.
|
||||
*/
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeString(status.name)
|
||||
buffer.writeResourceLocation(service)
|
||||
buffer.writeCollection(messages) { pb, value -> pb.writeString(value) }
|
||||
buffer.writeCollection(availableServices) { pb, value -> pb.writeResourceLocation(value) }
|
||||
buffer.writeDate(requestProcessedAt)
|
||||
buffer.writeBoolean(includesStartOfLog)
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
package com.cobblemon.mod.common.net.messages.server.debug.showdown
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Packet sent to request showdown information for a specific server.
|
||||
*
|
||||
* @property service The id of the showdown service to get showdown information for.
|
||||
* @author landonjw
|
||||
*/
|
||||
class RequestShowdownInfoPacket(val service: ResourceLocation, val lastRequest: Date?) : NetworkPacket<RequestShowdownInfoPacket> {
|
||||
override val id = ID
|
||||
|
||||
/**
|
||||
* Encodes this [RequestShowdownInfoPacket] to the given [RegistryFriendlyByteBuf].
|
||||
*
|
||||
* @param buffer The buffer to write to.
|
||||
*/
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeResourceLocation(service)
|
||||
buffer.writeNullable(lastRequest) { pb, value -> pb.writeDate(value) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("request_showdown_info")
|
||||
|
||||
/**
|
||||
* Decodes a [RequestShowdownInfoPacket] from the given [RegistryFriendlyByteBuf].
|
||||
*
|
||||
* @param buffer The buffer to read from.
|
||||
* @return The decoded [RequestShowdownInfoPacket].
|
||||
*/
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = RequestShowdownInfoPacket(
|
||||
service = buffer.readResourceLocation(),
|
||||
lastRequest = buffer.readNullable { pb -> pb.readDate() }
|
||||
)
|
||||
}
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
package com.cobblemon.mod.common.net.messages.server.debug.showdown
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
/**
|
||||
* A network packet for sending action commands to the Showdown service.
|
||||
*
|
||||
* @property service The resource location of the Showdown service.
|
||||
* @property action The action command to be sent to the Showdown service.
|
||||
* @author landonjw
|
||||
*/
|
||||
class ShowdownActionPacket(val service: ResourceLocation, val action: Action) : NetworkPacket<ShowdownActionPacket> {
|
||||
override val id = ID
|
||||
|
||||
/**
|
||||
* Encodes the packet data into the given buffer.
|
||||
*
|
||||
* @param buffer The buffer to encode the data into.
|
||||
*/
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeResourceLocation(service)
|
||||
buffer.writeEnum(action)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("showdown_action_request")
|
||||
|
||||
/**
|
||||
* Decodes the packet data from the given buffer.
|
||||
*
|
||||
* @param buffer The buffer to decode the data from.
|
||||
* @return The decoded ShowdownActionPacket.
|
||||
*/
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = ShowdownActionPacket(
|
||||
service = buffer.readResourceLocation(),
|
||||
action = buffer.readEnum(Action::class.java)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum representing possible actions for the Showdown service.
|
||||
*/
|
||||
enum class Action {
|
||||
START, STOP, RESTART, QUEUE
|
||||
}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
package com.cobblemon.mod.common.net.messages.server.debug.showdown
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
/**
|
||||
* A network packet for sending input commands to the Showdown service.
|
||||
*
|
||||
* @property service The resource location of the Showdown service.
|
||||
* @property input The input command to be sent to the Showdown service.
|
||||
* @author landonjw
|
||||
*/
|
||||
class ShowdownInputPacket(val service: ResourceLocation, val input: String) : NetworkPacket<ShowdownInputPacket> {
|
||||
override val id = ID
|
||||
|
||||
/**
|
||||
* Encodes the packet data into the given buffer.
|
||||
*
|
||||
* @param buffer The buffer to encode the data into.
|
||||
*/
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeResourceLocation(service)
|
||||
buffer.writeString(input)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("showdown_info_request")
|
||||
|
||||
/**
|
||||
* Decodes the packet data from the given buffer.
|
||||
*
|
||||
* @param buffer The buffer to decode the data from.
|
||||
* @return The decoded ShowdownInputPacket.
|
||||
*/
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) = ShowdownInputPacket(
|
||||
service = buffer.readResourceLocation(),
|
||||
input = buffer.readString()
|
||||
)
|
||||
}
|
||||
}
|
||||
+29
-6
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.net.serverhandling.battle
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.api.text.red
|
||||
import com.cobblemon.mod.common.api.text.yellow
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
|
||||
@@ -18,9 +19,7 @@ import com.cobblemon.mod.common.net.messages.client.battle.BattleInitializePacke
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.cobblemon.mod.common.util.traceFirstEntityCollision
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
@@ -48,9 +47,10 @@ object SpectateBattleHandler : ServerNetworkPacketHandler<SpectateBattlePacket>
|
||||
player.sendSystemMessage(lang("ui.interact.failed").yellow())
|
||||
return
|
||||
}
|
||||
battle.spectators.add(player.uuid)
|
||||
player.sendPacket(BattleInitializePacket(battle, null))
|
||||
player.sendPacket(BattleMessagePacket(battle.chatLog))
|
||||
|
||||
this.spectateBattle(targetedPlayerEntity, player)
|
||||
|
||||
// Handle music
|
||||
target?.battleTheme?.let { player.sendPacket(BattleMusicPacket(it)) }
|
||||
}
|
||||
else {
|
||||
@@ -58,4 +58,27 @@ object SpectateBattleHandler : ServerNetworkPacketHandler<SpectateBattlePacket>
|
||||
}
|
||||
}
|
||||
|
||||
fun spectateBattle(target: ServerPlayer, player: ServerPlayer) {
|
||||
if (player == target) {
|
||||
player.sendSystemMessage(lang("command.spectatebattle.self_spectate_disallowed").red())
|
||||
return
|
||||
}
|
||||
|
||||
if (!target.isInBattle()) {
|
||||
player.sendSystemMessage(lang("command.spectatebattle.player_not_in_battle").red())
|
||||
return
|
||||
}
|
||||
|
||||
if (player.isInBattle()) {
|
||||
player.sendSystemMessage(lang("command.spectatebattle.while_battling_disallowed").red())
|
||||
return
|
||||
}
|
||||
|
||||
val battle = BattleRegistry.getBattleByParticipatingPlayer(target) ?: return
|
||||
|
||||
battle.spectators.add(player.uuid)
|
||||
player.sendPacket(BattleInitializePacket(battle, null))
|
||||
player.sendPacket(BattleMessagePacket(battle.chatLog))
|
||||
}
|
||||
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
package com.cobblemon.mod.common.net.serverhandling.debug.showdown
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.net.messages.client.debug.showdown.ShowdownRefreshInfoPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.showdown.RequestShowdownInfoPacket
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* Handles the reception of a [RequestShowdownInfoPacket] on the server side.
|
||||
* This handler retrieves the showdown information from the specified service
|
||||
* and sends it back to the client.
|
||||
*
|
||||
* @author landonjw
|
||||
*/
|
||||
object RequestShowdownInfoHandler : ServerNetworkPacketHandler<RequestShowdownInfoPacket> {
|
||||
|
||||
/**
|
||||
* Handles the received [RequestShowdownInfoPacket] by retrieving the showdown information
|
||||
* from the specified service and sending it back to the client in a [ShowdownRefreshInfoPacket].
|
||||
*
|
||||
* @param packet The received packet requesting showdown information.
|
||||
* @param server The Minecraft server instance.
|
||||
* @param player The player who sent the request.
|
||||
*/
|
||||
override fun handle(packet: RequestShowdownInfoPacket, server: MinecraftServer, player: ServerPlayer) {
|
||||
if (!Cobblemon.config.enableDebugKeys) return
|
||||
if (!ShowdownService.contains(packet.service)) return
|
||||
|
||||
val service = ShowdownService.get(packet.service)
|
||||
val messages = service.log.get(after = packet.lastRequest)
|
||||
val availableServices = ShowdownService.all().map { it.id }
|
||||
val includesStartOfLog = if (packet.lastRequest == null) true else service.log.isStartOfLog(messages)
|
||||
player.sendPacket(ShowdownRefreshInfoPacket(service.status, packet.service, messages, availableServices, Date(), includesStartOfLog))
|
||||
}
|
||||
}
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
package com.cobblemon.mod.common.net.serverhandling.debug.showdown
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry.packTeam
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.showdown.ShowdownActionPacket
|
||||
import com.cobblemon.mod.common.util.party
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
* Handles network packets for Showdown actions on the server side.
|
||||
*
|
||||
* This handler processes incoming `ShowdownActionPacket` packets and performs the corresponding
|
||||
* actions (ie. restart it) on the specified Showdown service.
|
||||
*
|
||||
* @author landonjw
|
||||
*/
|
||||
object ShowdownActionHandler : ServerNetworkPacketHandler<ShowdownActionPacket> {
|
||||
|
||||
/**
|
||||
* Handles the received `ShowdownActionPacket`.
|
||||
*
|
||||
* @param packet The received packet containing the action to perform.
|
||||
* @param server The Minecraft server instance.
|
||||
* @param player The player who sent the packet.
|
||||
*/
|
||||
override fun handle(packet: ShowdownActionPacket, server: MinecraftServer, player: ServerPlayer) {
|
||||
if (!Cobblemon.config.enableDebugKeys) return
|
||||
if (!ShowdownService.contains(packet.service)) return
|
||||
|
||||
val service = ShowdownService.get(packet.service)
|
||||
when (packet.action) {
|
||||
ShowdownActionPacket.Action.START -> service.openConnection()
|
||||
ShowdownActionPacket.Action.STOP -> service.closeConnection()
|
||||
ShowdownActionPacket.Action.RESTART -> service.restartConnection()
|
||||
ShowdownActionPacket.Action.QUEUE -> {
|
||||
val cobblemonTeam = player.party().saveToJSON(JsonObject(), player.registryAccess())
|
||||
val showdownTeam = player.party().map { BattlePokemon(it, it.clone(false)) { entity -> entity.discard() } }
|
||||
showdownTeam.forEach {
|
||||
it.effectedPokemon.level = 100
|
||||
it.effectedPokemon.heal()
|
||||
}
|
||||
val showdownTeamStr = showdownTeam.packTeam()
|
||||
service.send(">queue-battle ${player.uuid} ${player.name.string} $cobblemonTeam@|@$showdownTeamStr")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
package com.cobblemon.mod.common.net.serverhandling.debug.showdown
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.net.messages.server.debug.showdown.ShowdownInputPacket
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
* Handles the processing of `ShowdownInputPacket` on the server side.
|
||||
* This handler sends a command to the given showdown service. This is used for debugging purposes.
|
||||
*
|
||||
* @author landonjw
|
||||
*/
|
||||
object ShowdownInputHandler : ServerNetworkPacketHandler<ShowdownInputPacket> {
|
||||
|
||||
/**
|
||||
* Handles the received `ShowdownInputPacket`.
|
||||
*
|
||||
* @param packet The received packet containing the input command and service information.
|
||||
* @param server The Minecraft server instance.
|
||||
* @param player The player who sent the packet.
|
||||
*/
|
||||
override fun handle(packet: ShowdownInputPacket, server: MinecraftServer, player: ServerPlayer) {
|
||||
if (!Cobblemon.config.enableDebugKeys) return
|
||||
if (!ShowdownService.contains(packet.service)) return
|
||||
|
||||
val service = ShowdownService.get(packet.service)
|
||||
service.send(packet.input)
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -32,7 +32,7 @@ abstract class BaseCobblemonHeldItemManager : HeldItemManager {
|
||||
|
||||
internal open fun load() {
|
||||
this.itemIds.clear()
|
||||
val itemsJson = ShowdownService.get().getItemIds()
|
||||
val itemsJson = ShowdownService.service.getItemIds()
|
||||
val showdownIds = hashSetOf<String>()
|
||||
for (i in 0 until itemsJson.size()) {
|
||||
showdownIds += itemsJson[i].asString
|
||||
@@ -95,4 +95,4 @@ abstract class BaseCobblemonHeldItemManager : HeldItemManager {
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-14
@@ -235,23 +235,23 @@ class BirdAirController : RideController {
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
super.encode(buffer)
|
||||
buffer.writeString(topSpeedExpr.toString())
|
||||
buffer.writeString(glideTopSpeedExpr.toString())
|
||||
buffer.writeString(accelExpr.toString())
|
||||
buffer.writeString(handlingExpr.toString())
|
||||
buffer.writeString(altitudeExpr.toString())
|
||||
buffer.writeString(infiniteStamina.toString())
|
||||
buffer.writeString(infiniteAltitude.toString())
|
||||
buffer.writeExpression(topSpeedExpr)
|
||||
buffer.writeExpression(glideTopSpeedExpr)
|
||||
buffer.writeExpression(accelExpr)
|
||||
buffer.writeExpression(handlingExpr)
|
||||
buffer.writeExpression(altitudeExpr)
|
||||
buffer.writeExpression(infiniteStamina)
|
||||
buffer.writeExpression(infiniteAltitude)
|
||||
}
|
||||
|
||||
override fun decode(buffer: RegistryFriendlyByteBuf) {
|
||||
topSpeedExpr = buffer.readString().asExpression()
|
||||
glideTopSpeedExpr = buffer.readString().asExpression()
|
||||
accelExpr = buffer.readString().asExpression()
|
||||
handlingExpr = buffer.readString().asExpression()
|
||||
altitudeExpr = buffer.readString().asExpression()
|
||||
infiniteStamina = buffer.readString().asExpression()
|
||||
infiniteAltitude = buffer.readString().asExpression()
|
||||
topSpeedExpr = buffer.readExpression()
|
||||
glideTopSpeedExpr = buffer.readExpression()
|
||||
accelExpr = buffer.readExpression()
|
||||
handlingExpr = buffer.readExpression()
|
||||
altitudeExpr = buffer.readExpression()
|
||||
infiniteStamina = buffer.readExpression()
|
||||
infiniteAltitude = buffer.readExpression()
|
||||
}
|
||||
|
||||
|
||||
|
||||
+6
-13
@@ -16,14 +16,7 @@ import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
|
||||
import com.cobblemon.mod.common.api.riding.stats.RidingStat
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.getString
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.resolveDouble
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
@@ -72,16 +65,16 @@ class GliderAirController : RideController {
|
||||
override fun jumpForce(entity: PokemonEntity, driver: Player, jumpStrength: Int) = Vec3.ZERO
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
super.encode(buffer)
|
||||
buffer.writeString(glideSpeed.toString())
|
||||
buffer.writeString(speed.toString())
|
||||
buffer.writeExpression(glideSpeed)
|
||||
buffer.writeExpression(speed)
|
||||
}
|
||||
|
||||
override fun decode(buffer: RegistryFriendlyByteBuf) {
|
||||
glideSpeed = buffer.readString().asExpression()
|
||||
speed = buffer.readString().asExpression()
|
||||
glideSpeed = buffer.readExpression()
|
||||
speed = buffer.readExpression()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val KEY: ResourceLocation = cobblemonResource("air/glider")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -198,17 +198,17 @@ class HelicopterAirController : RideController {
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
super.encode(buffer)
|
||||
buffer.writeString(gravity.toString())
|
||||
buffer.writeString(horizontalAcceleration.toString())
|
||||
buffer.writeString(verticalVelocity.toString())
|
||||
buffer.writeString(speed.toString())
|
||||
buffer.writeExpression(gravity)
|
||||
buffer.writeExpression(horizontalAcceleration)
|
||||
buffer.writeExpression(verticalVelocity)
|
||||
buffer.writeExpression(speed)
|
||||
}
|
||||
|
||||
override fun decode(buffer: RegistryFriendlyByteBuf) {
|
||||
gravity = buffer.readString().asExpression()
|
||||
horizontalAcceleration = buffer.readString().asExpression()
|
||||
verticalVelocity = buffer.readString().asExpression()
|
||||
speed = buffer.readString().asExpression()
|
||||
gravity = buffer.readExpression()
|
||||
horizontalAcceleration = buffer.readExpression()
|
||||
verticalVelocity = buffer.readExpression()
|
||||
speed = buffer.readExpression()
|
||||
}
|
||||
|
||||
|
||||
|
||||
+20
-20
@@ -254,29 +254,29 @@ class JetAirController : RideController {
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
super.encode(buffer)
|
||||
buffer.writeString(gravity.toString())
|
||||
buffer.writeString(minSpeed.toString())
|
||||
buffer.writeString(handlingExpr.toString())
|
||||
buffer.writeString(handlingYawExpr.toString())
|
||||
buffer.writeString(topSpeedExpr.toString())
|
||||
buffer.writeString(accelExpr.toString())
|
||||
buffer.writeString(jumpExpr.toString())
|
||||
buffer.writeString(staminaExpr.toString())
|
||||
buffer.writeString(infiniteStamina.toString())
|
||||
buffer.writeString(infiniteAltitude.toString())
|
||||
buffer.writeExpression(gravity)
|
||||
buffer.writeExpression(minSpeed)
|
||||
buffer.writeExpression(handlingExpr)
|
||||
buffer.writeExpression(handlingYawExpr)
|
||||
buffer.writeExpression(topSpeedExpr)
|
||||
buffer.writeExpression(accelExpr)
|
||||
buffer.writeExpression(jumpExpr)
|
||||
buffer.writeExpression(staminaExpr)
|
||||
buffer.writeExpression(infiniteStamina)
|
||||
buffer.writeExpression(infiniteAltitude)
|
||||
}
|
||||
|
||||
override fun decode(buffer: RegistryFriendlyByteBuf) {
|
||||
gravity = buffer.readString().asExpression()
|
||||
minSpeed = buffer.readString().asExpression()
|
||||
handlingExpr = buffer.readString().asExpression()
|
||||
handlingYawExpr = buffer.readString().asExpression()
|
||||
topSpeedExpr = buffer.readString().asExpression()
|
||||
accelExpr = buffer.readString().asExpression()
|
||||
jumpExpr = buffer.readString().asExpression()
|
||||
staminaExpr = buffer.readString().asExpression()
|
||||
infiniteStamina = buffer.readString().asExpression()
|
||||
infiniteAltitude = buffer.readString().asExpression()
|
||||
gravity = buffer.readExpression()
|
||||
minSpeed = buffer.readExpression()
|
||||
handlingExpr = buffer.readExpression()
|
||||
handlingYawExpr = buffer.readExpression()
|
||||
topSpeedExpr = buffer.readExpression()
|
||||
accelExpr = buffer.readExpression()
|
||||
jumpExpr = buffer.readExpression()
|
||||
staminaExpr = buffer.readExpression()
|
||||
infiniteStamina = buffer.readExpression()
|
||||
infiniteAltitude = buffer.readExpression()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.util
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.cobblemon.mod.common.api.storage.party.PartyPosition
|
||||
import com.cobblemon.mod.common.api.storage.pc.PCPosition
|
||||
import com.cobblemon.mod.common.net.IntSize
|
||||
@@ -95,6 +96,14 @@ fun ByteBuf.readString(): String {
|
||||
return Utf8String.read(this, 32767)
|
||||
}
|
||||
|
||||
fun ByteBuf.writeExpression(expression: Expression): ByteBuf {
|
||||
return this.writeString(expression.getString())
|
||||
}
|
||||
|
||||
fun ByteBuf.readExpression(): Expression {
|
||||
return this.readString().asExpression()
|
||||
}
|
||||
|
||||
fun <T> ByteBuf.readNullable(reader: (ByteBuf) -> T): T? {
|
||||
val isPresent = this.readBoolean()
|
||||
if (isPresent) {
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package com.cobblemon.mod.common.util
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URI
|
||||
|
||||
object HttpService {
|
||||
|
||||
val gson: Gson = GsonBuilder().create()
|
||||
|
||||
inline fun <reified T> get(url: String): HttpResponse<T> {
|
||||
val connection = (URI.create(url).toURL().openConnection() as HttpURLConnection).also {
|
||||
it.requestMethod = "GET"
|
||||
it.connectTimeout = 5000
|
||||
it.readTimeout = 5000
|
||||
it.useCaches = false
|
||||
}
|
||||
return readFromUrlConnection<T>(connection)
|
||||
}
|
||||
|
||||
inline fun <reified T> post(url: String, body: Any? = null): HttpResponse<T> {
|
||||
val bodyJson = gson.toJson(body)
|
||||
val bodyBytes = bodyJson.toByteArray(Charsets.UTF_8)
|
||||
|
||||
val connection = (URI.create(url).toURL().openConnection() as HttpURLConnection).also {
|
||||
it.requestMethod = "POST"
|
||||
it.doOutput = true
|
||||
it.connectTimeout = 5000
|
||||
it.readTimeout = 5000
|
||||
it.useCaches = false
|
||||
it.setRequestProperty("Content-Type", "application/json; charset=UTF-8")
|
||||
it.setRequestProperty("Content-Length", bodyBytes.size.toString())
|
||||
}
|
||||
|
||||
connection.connect()
|
||||
connection.outputStream.use {
|
||||
it.write(bodyBytes)
|
||||
}
|
||||
|
||||
return readFromUrlConnection<T>(connection)
|
||||
}
|
||||
|
||||
inline fun <reified T> readFromUrlConnection(connection: HttpURLConnection): HttpResponse<T> {
|
||||
try {
|
||||
val status = connection.responseCode
|
||||
|
||||
if (status >= 400) {
|
||||
connection.errorStream.use {
|
||||
val error = readFromStream(it)
|
||||
return HttpResponse(error = HttpError(status, error), body = null)
|
||||
}
|
||||
}
|
||||
else {
|
||||
connection.inputStream.use {
|
||||
val bodyStr = readFromStream(it)
|
||||
return when {
|
||||
T::class == Unit::class -> HttpResponse(error = null, body = null)
|
||||
T::class == String::class -> HttpResponse(error = null, body = bodyStr as T)
|
||||
else -> HttpResponse(error = null, body = gson.fromJson(bodyStr, T::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
Cobblemon.LOGGER.error("Failed to read from connection url at ${connection.url}", e)
|
||||
return HttpResponse(error = HttpError(500, null), body = null)
|
||||
}
|
||||
}
|
||||
|
||||
fun readFromStream(stream: InputStream): String {
|
||||
ByteArrayOutputStream().use { result ->
|
||||
val buffer = ByteArray(1024)
|
||||
var length: Int
|
||||
while (stream.read(buffer).also { length = it } != -1) {
|
||||
result.write(buffer, 0, length)
|
||||
}
|
||||
return result.toString("UTF-8")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class HttpResponse<T>(val error: HttpError?, val body: T?)
|
||||
data class HttpError(val statusCode: Int, val details: String?)
|
||||
@@ -1253,7 +1253,6 @@
|
||||
"key.cobblemon.translateportraitleft": "Translate Portrait Left",
|
||||
"key.cobblemon.translateportraitright": "Translate Portrait Right",
|
||||
"key.cobblemon.printmodelsettings": "Print Model Settings",
|
||||
"key.cobblemon.toggleshowdowndebugui": "Toggle Showdown Debugger UI",
|
||||
"key.cobblemon.categories.cobblemon.debug": "Cobblemon Debug",
|
||||
|
||||
"cobblemon.subtitle.poke_ball.capture_succeeded": "Poké Ball capture succeeds",
|
||||
@@ -1562,12 +1561,20 @@
|
||||
"cobblemon.command.pokebox.last_pokemon": "Did not send that Player's last Pokémon to the PC.",
|
||||
"cobblemon.command.pokemonrestart": "Cleared %1$s's party and PC.",
|
||||
"cobblemon.command.freezepokemon.non_pokemon": "You are not looking at a Pokémon.",
|
||||
"cobblemon.command.spectatebattle.self_spectate_disallowed": "You can't spectate yourself.",
|
||||
"cobblemon.command.spectatebattle.while_battling_disallowed": "You can't spectate other people while battling.",
|
||||
"cobblemon.command.spectatebattle.player_not_in_battle": "This player is not in a battle.",
|
||||
"cobblemon.command.npcedit.non_npc": "You are not looking at a NPC.",
|
||||
"cobblemon.command.cobblemon_config.reload": "Reloading Cobblemon settings.",
|
||||
"cobblemon.command.unlockboxwallpaper.success": "%1$s can now use the %2$s wallpaper.",
|
||||
"cobblemon.command.unlockboxwallpaper.already": "%1$s already has the %2$s wallpaper.",
|
||||
"cobblemon.wallpaper_unlocked": "Wallpaper Unlocked",
|
||||
"cobblemon.unknown_wallpaper": "???",
|
||||
"cobblemon.command.changeboxcount": "%1$s now has %2$s PC boxes",
|
||||
"cobblemon.command.changeboxcount.removing_not_empty_box": "A box you are trying to remove is not empty",
|
||||
"cobblemon.command.boxcount": "%1$s currently has %2$s PC boxes",
|
||||
"cobblemon.command.changeboxcount.removing_too_many_boxes": "You can't remove all the boxes. %1$s currently has %2$s PC boxes",
|
||||
"cobblemon.command.changeboxcount.force_remove": "Click here to remove anyway",
|
||||
|
||||
"cobblemon.config.ui.search_configurations": "Search Configurations",
|
||||
"cobblemon.config.ui.reset": "Reset",
|
||||
@@ -7212,4 +7219,4 @@
|
||||
"cobblemon.ability.zenmode.desc": "Changes the Pokémon's shape when HP is half or less.",
|
||||
"cobblemon.ability.zerotohero": "Zero to Hero",
|
||||
"cobblemon.ability.zerotohero.desc": "The Pokémon transforms into its Hero Form when it switches out."
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"hitbox": "player",
|
||||
"presets": [
|
||||
"cobblemon:looks_in_one_direction"
|
||||
|
||||
],
|
||||
"variation": {
|
||||
"dirt": [
|
||||
@@ -105,4 +105,4 @@
|
||||
"garchomp level=100 pokeball=quick_ball held_item=cobblemon:yache_berry ability=roughskin nature=jolly moves=dragonclaw,earthquake,swordsdance,poisonjab hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=6 attack_ev=252 speed_ev=252"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,9 @@ val bundle: Configuration by configurations.creating {
|
||||
loom {
|
||||
val clientConfig = runConfigs.getByName("client")
|
||||
clientConfig.runDir = "runClient"
|
||||
// clientConfig.programArg("--username=GaryOak")
|
||||
clientConfig.programArg("--username=AshKetchum")
|
||||
//This is AshKetchum's UUID so you get an Ash Ketchum skin
|
||||
// clientConfig.programArg("--uuid=93e4e551-589a-41cb-ab2d-435266c8e035")
|
||||
clientConfig.programArg("--uuid=93e4e551-589a-41cb-ab2d-435266c8e035")
|
||||
val serverConfig = runConfigs.getByName("server")
|
||||
serverConfig.runDir = "runServer"
|
||||
}
|
||||
@@ -59,4 +59,4 @@ tasks {
|
||||
dependsOn(copyJar)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user