mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-21 14:46:25 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0dd0a78741 |
@@ -17,7 +17,6 @@
|
||||
- Substantially optimised spawning checks mainly by front-loading biome filtering.
|
||||
- When using the `cobblemon` or `generation_9` capture calculators a critical capture with a single shake will always play for successful captures when you've already registered the Pokémon as caught in your Pokédex.
|
||||
- Improved the performance of saving Pokédex and player data.
|
||||
- Pokémon hitbox now scales with entity attribute `generic.scale`.
|
||||
|
||||
### Fixes
|
||||
- Fixed Particles sometimes facing the wrong direction (looking at you, Swords Dance)
|
||||
@@ -72,7 +71,6 @@
|
||||
- Added `interpolate` boolean property to animated textures to allow gradual colour changes between frames.
|
||||
- Fixed species additions not being capable of changing implemented status.
|
||||
- Added support for action effects that are triggered by `|-activate|` Showdown instructions. `activate_{effect_id}` is the syntax.
|
||||
- Added Molang functions for rendering items `render_item(item_id, locator_name)` and `clear_items()`.
|
||||
|
||||
## [1.6.1 (January 26th, 2025)](#1-6-1)
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -67,4 +68,4 @@ object CobblemonCommands {
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -71,8 +71,6 @@ import com.cobblemon.mod.common.pokemon.Species
|
||||
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
|
||||
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
|
||||
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
|
||||
import com.cobblemon.mod.common.pokemon.riding.controllers.BirdAirController
|
||||
import com.cobblemon.mod.common.pokemon.riding.states.BirdAirState
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import com.mojang.datafixers.util.Either
|
||||
import java.util.UUID
|
||||
@@ -788,7 +786,6 @@ object MoLangFunctions {
|
||||
map.put("in_battle") { DoubleValue(pokemonEntity.isBattling) }
|
||||
map.put("is_moving") { DoubleValue((pokemonEntity.moveControl as? PokemonMoveControl)?.hasWanted() == true) }
|
||||
map.put("is_flying") { DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) }
|
||||
map.put("is_gliding") { DoubleValue(pokemonEntity.useRidingAltPose()) }
|
||||
map.put("has_aspect") { DoubleValue(it.getString(0) in pokemonEntity.aspects) }
|
||||
map.put("is_pokemon") { DoubleValue.ONE }
|
||||
map.put("is_holding_item") { DoubleValue(!pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).isEmpty) }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -134,11 +134,6 @@ data class RidingManager(val entity: PokemonEntity) {
|
||||
return controller.jumpForce(entity, driver, jumpStrength)
|
||||
}
|
||||
|
||||
fun useRidingAltPose(entity: PokemonEntity, driver: Player): Boolean {
|
||||
val controller = getController(entity) ?: return false
|
||||
return controller.useRidingAltPose(entity, driver)
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to supply the delta for the lerp() between velocity
|
||||
* vectors. Called inertia for now as the way it feels when
|
||||
|
||||
-2
@@ -111,8 +111,6 @@ interface RideController : Encodable, Decodable {
|
||||
|
||||
fun useAngVelSmoothing(entity: PokemonEntity): Boolean = false
|
||||
|
||||
fun useRidingAltPose(entity: PokemonEntity, driver: Player): Boolean = false
|
||||
|
||||
//If function is not overwritten by controllers then just perform the defualt
|
||||
//rolling function which is roll on mousex and pitch on mousey
|
||||
fun rotationOnMouseXY(
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+111
-87
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
|
||||
import com.cobblemon.mod.common.api.battles.interpreter.Effect
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
|
||||
@@ -23,11 +24,13 @@ import com.cobblemon.mod.common.battles.dispatch.GO
|
||||
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
|
||||
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
|
||||
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.DamageTakenEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.evolution.progress.RecoilEvolutionProgress
|
||||
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonBadlyStatus
|
||||
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.battleLang
|
||||
@@ -67,49 +70,40 @@ class DamageInstruction(
|
||||
}
|
||||
}
|
||||
}
|
||||
val newHealth = privateMessage.argumentAt(1)?.split(" ")?.get(0) ?: return
|
||||
val effect = privateMessage.effect()
|
||||
val source = privateMessage.battlePokemonFromOptional(battle)
|
||||
source?.let { ShowdownInterpreter.broadcastOptionalAbility(battle, effect, it) }
|
||||
}
|
||||
|
||||
private fun doRecoilEvoChecks(battlePokemon: BattlePokemon) {
|
||||
battlePokemon.effectedPokemon.let { pokemon ->
|
||||
if (RecoilEvolutionProgress.supports(pokemon)) {
|
||||
val healthStr = privateMessage.argumentAt(1) ?: throw UnsupportedOperationException(
|
||||
"Cant get recoil string"
|
||||
)
|
||||
val newHealth =
|
||||
"([0-9]+).*".toRegex().find(healthStr)?.groups?.get(1)?.value?.toIntOrNull()
|
||||
?: throw UnsupportedOperationException("Cant get recoil string")
|
||||
val difference = pokemon.currentHealth - newHealth
|
||||
if (difference > 0) {
|
||||
val progress = pokemon.evolutionProxy.current()
|
||||
.progressFirstOrCreate({ it is RecoilEvolutionProgress }) { RecoilEvolutionProgress() }
|
||||
progress.updateProgress(RecoilEvolutionProgress.Progress(progress.currentProgress().recoil + difference))
|
||||
}
|
||||
}
|
||||
if (source != null) {
|
||||
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, source)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun runActionEffect(battle: PokemonBattle, runtime: MoLangRuntime) {
|
||||
val effect = privateMessage.effect()
|
||||
val battlePokemon = publicMessage.battlePokemon(0, actor.battle) ?: return
|
||||
val publicPokemon = publicMessage.battlePokemon(0, actor.battle) ?: return
|
||||
var status = effect?.id?.let { Statuses.getStatus(it) }
|
||||
battle.dispatch {
|
||||
val pokemon = privateMessage.battlePokemon(0, battle) ?: return@dispatch GO
|
||||
//Showdown doesnt tell us on damage if its poison or toxic so we gotta consult the entity
|
||||
if (status is PoisonStatus) {
|
||||
status = pokemon.effectedPokemon.status?.status ?: status
|
||||
val isToxic = publicPokemon.effectedPokemon.status?.status is PoisonBadlyStatus
|
||||
val realStatus = if (isToxic) PoisonBadlyStatus() else status
|
||||
// Damage effects without a action effect receive the generic damage effect
|
||||
val actionEffect = if (realStatus != null) {
|
||||
realStatus.getActionEffect()
|
||||
} else if (effect != null) {
|
||||
ActionEffects.actionEffects[cobblemonResource("generic_damage")]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
// Damage effects without a action effect receive the generic damage effect
|
||||
val actionEffect = status?.getActionEffect() ?: effect?.let {
|
||||
ActionEffects.actionEffects["generic_damage".asIdentifierDefaultingNamespace()]
|
||||
} ?: return@dispatch GO
|
||||
if (actionEffect == null) {
|
||||
return@dispatch GO
|
||||
}
|
||||
|
||||
val providers = mutableListOf<Any>(battle)
|
||||
battlePokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
|
||||
val pokemonEntity = publicPokemon.effectedPokemon.entity
|
||||
if (pokemonEntity != null) {
|
||||
providers.add(UsersProvider(pokemonEntity))
|
||||
}
|
||||
|
||||
val context = ActionEffectContext(
|
||||
actionEffect = actionEffect,
|
||||
@@ -125,72 +119,24 @@ class DamageInstruction(
|
||||
}
|
||||
|
||||
override fun postActionEffect(battle: PokemonBattle) {
|
||||
val newHealth = privateMessage.argumentAt(1)?.split(" ")?.get(0) ?: return
|
||||
val hpStatus = privateMessage.argumentAt(1)?.split(" ")?.get(0)!!
|
||||
var causedFaint = hpStatus == "0"
|
||||
val remainingHealth = hpStatus.split("/")[0].toFloat()
|
||||
val maxHealth = hpStatus.split("/").getOrNull(1)?.toFloat()
|
||||
val battlePokemon = publicMessage.battlePokemon(0, actor.battle) ?: return
|
||||
var causedFaint = newHealth == "0"
|
||||
val effect = privateMessage.effect()
|
||||
val source = privateMessage.battlePokemonFromOptional(battle)
|
||||
val lastCauser = instructionSet.getMostRecentCauser(comparedTo = this)
|
||||
val pokemonName = battlePokemon.getName()
|
||||
val pokemonEntity = battlePokemon.entity
|
||||
|
||||
battle.dispatch {
|
||||
val pokemonName = battlePokemon.getName()
|
||||
val pokemonEntity = battlePokemon.entity
|
||||
//Play recoil animation if the pokemon recoiling isnt dead
|
||||
if (!causedFaint && pokemonEntity != null) {
|
||||
val pkt = PlayPosableAnimationPacket(pokemonEntity.id, setOf("recoil"), emptyList())
|
||||
pkt.sendToPlayersAround(
|
||||
x = pokemonEntity.x,
|
||||
y = pokemonEntity.y,
|
||||
z = pokemonEntity.z,
|
||||
worldKey = pokemonEntity.level().dimension(),
|
||||
distance = 50.0
|
||||
)
|
||||
}
|
||||
|
||||
val newHealthRatio: Float
|
||||
val remainingHealth = newHealth.split("/")[0].toInt()
|
||||
|
||||
playRecoilAnim(pokemonEntity, causedFaint)
|
||||
if (effect != null) {
|
||||
val lang = when (effect.id) {
|
||||
"blacksludge", "stickybarb", "jabocaberry", "rowapberry" -> battleLang("damage.item", pokemonName, effect.typelessData)
|
||||
"brn", "psn", "tox" -> {
|
||||
val status = Statuses.getStatus(effect.id)?.name?.path ?: return@dispatch GO
|
||||
lang("status.$status.hurt", pokemonName)
|
||||
}
|
||||
"aftermath", "innardsout" -> battleLang("damage.generic", pokemonName)
|
||||
"chloroblast", "steelbeam" -> battleLang("damage.mindblown", pokemonName)
|
||||
"jumpkick" -> battleLang("damage.highjumpkick", pokemonName)
|
||||
else -> battleLang("damage.${effect.id}", pokemonName, source?.getName() ?: Component.literal("UNKOWN"))
|
||||
}
|
||||
battle.broadcastChatMessage(lang.red())
|
||||
sendStatusMessage(battle, effect, pokemonName, source)
|
||||
}
|
||||
|
||||
if (causedFaint) {
|
||||
newHealthRatio = 0F
|
||||
battle.dispatch {
|
||||
battlePokemon.effectedPokemon.currentHealth = 0
|
||||
battlePokemon.sendUpdate()
|
||||
GO
|
||||
}
|
||||
causedFaint = true
|
||||
} else {
|
||||
val maxHealth = newHealth.split("/")[1].toInt()
|
||||
val difference = maxHealth - remainingHealth
|
||||
newHealthRatio = remainingHealth.toFloat() / maxHealth
|
||||
battle.dispatchToFront {
|
||||
battlePokemon.effectedPokemon.currentHealth = remainingHealth
|
||||
if (difference > 0) {
|
||||
battlePokemon.effectedPokemon.let { pokemon ->
|
||||
if (DamageTakenEvolutionProgress.supports(pokemon)) {
|
||||
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DamageTakenEvolutionProgress }) { DamageTakenEvolutionProgress() }
|
||||
progress.updateProgress(DamageTakenEvolutionProgress.Progress(progress.currentProgress().amount + difference))
|
||||
}
|
||||
}
|
||||
}
|
||||
battlePokemon.sendUpdate()
|
||||
GO
|
||||
}
|
||||
}
|
||||
privateMessage.pnxAndUuid(0)?.let { (pnx, _) -> battle.sendSidedUpdate(actor, BattleHealthChangePacket(pnx, remainingHealth.toFloat()), BattleHealthChangePacket(pnx, newHealthRatio)) }
|
||||
updateHealth(battle, battlePokemon, remainingHealth, maxHealth)
|
||||
|
||||
battle.minorBattleActions[battlePokemon.uuid] = privateMessage
|
||||
if (lastCauser is MoveInstruction) {
|
||||
@@ -215,4 +161,82 @@ class DamageInstruction(
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateHealth(battle: PokemonBattle, battlePokemon: BattlePokemon, remainingHealth: Float, maxHealth: Float?) {
|
||||
val causedFaint = maxHealth == null
|
||||
val newHealthRatio = if (maxHealth == null) 0F else remainingHealth / maxHealth
|
||||
if (causedFaint) {
|
||||
battle.dispatch {
|
||||
battlePokemon.effectedPokemon.currentHealth = 0
|
||||
battlePokemon.sendUpdate()
|
||||
GO
|
||||
}
|
||||
}
|
||||
else {
|
||||
val difference = maxHealth - remainingHealth
|
||||
battle.dispatchToFront {
|
||||
battlePokemon.effectedPokemon.currentHealth = remainingHealth.toInt()
|
||||
updateDamageTakenEvo(battlePokemon, difference.toInt())
|
||||
battlePokemon.sendUpdate()
|
||||
GO
|
||||
}
|
||||
}
|
||||
privateMessage.pnxAndUuid(0)?.let { (pnx, _ ) ->
|
||||
battle.sendSidedUpdate(actor, BattleHealthChangePacket(pnx, remainingHealth), BattleHealthChangePacket(pnx, newHealthRatio))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun doRecoilEvoChecks(battlePokemon: BattlePokemon) {
|
||||
battlePokemon.effectedPokemon.let { pokemon ->
|
||||
if (RecoilEvolutionProgress.supports(pokemon)) {
|
||||
val healthStr = privateMessage.argumentAt(1) ?: throw UnsupportedOperationException("Cant get recoil string")
|
||||
val newHealth =
|
||||
"([0-9]+).*".toRegex().find(healthStr)?.groups?.get(1)?.value?.toIntOrNull()
|
||||
?: throw UnsupportedOperationException("Cant get recoil string")
|
||||
val difference = pokemon.currentHealth - newHealth
|
||||
if (difference > 0) {
|
||||
val progress = pokemon.evolutionProxy.current()
|
||||
.progressFirstOrCreate({ it is RecoilEvolutionProgress }) { RecoilEvolutionProgress() }
|
||||
progress.updateProgress(RecoilEvolutionProgress.Progress(progress.currentProgress().recoil + difference))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendStatusMessage(battle: PokemonBattle, effect: Effect, pokemonName: Component, source: BattlePokemon?) {
|
||||
val lang = when (effect.id) {
|
||||
"blacksludge", "stickybarb", "jabocaberry", "rowapberry" -> battleLang("damage.item", pokemonName, effect.typelessData)
|
||||
"brn", "psn", "tox" -> {
|
||||
val status = Statuses.getStatus(effect.id)?.name?.path
|
||||
lang("status.$status.hurt", pokemonName)
|
||||
}
|
||||
"aftermath", "innardsout" -> battleLang("damage.generic", pokemonName)
|
||||
"chloroblast", "steelbeam" -> battleLang("damage.mindblown", pokemonName)
|
||||
"jumpkick" -> battleLang("damage.highjumpkick", pokemonName)
|
||||
else -> battleLang("damage.${effect.id}", pokemonName, source?.getName() ?: Component.literal("UNKOWN"))
|
||||
}
|
||||
battle.broadcastChatMessage(lang.red())
|
||||
}
|
||||
|
||||
private fun playRecoilAnim(pokemonEntity: PokemonEntity?, causedFaint: Boolean) {
|
||||
if (!causedFaint && pokemonEntity != null) {
|
||||
val pkt = PlayPosableAnimationPacket(pokemonEntity.id, setOf("recoil"), emptyList())
|
||||
pkt.sendToPlayersAround(
|
||||
x = pokemonEntity.x,
|
||||
y = pokemonEntity.y,
|
||||
z = pokemonEntity.z,
|
||||
worldKey = pokemonEntity.level().dimension(),
|
||||
distance = 50.0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateDamageTakenEvo(battlePokemon: BattlePokemon, damageTaken: Int) {
|
||||
battlePokemon.effectedPokemon.let { pokemon ->
|
||||
if (DamageTakenEvolutionProgress.supports(pokemon)) {
|
||||
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DamageTakenEvolutionProgress }) { DamageTakenEvolutionProgress() }
|
||||
progress.updateProgress(DamageTakenEvolutionProgress.Progress(progress.currentProgress().amount + damageTaken))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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
-4
@@ -18,7 +18,6 @@ import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
|
||||
import net.minecraft.client.gui.GuiGraphics
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
|
||||
@@ -116,10 +115,9 @@ class ModelWidget(
|
||||
heldItemRenderer.renderOnModel(
|
||||
state.currentModel!!,
|
||||
heldItem?: ItemStack.EMPTY,
|
||||
state,
|
||||
state.locatorStates,
|
||||
matrices,
|
||||
context.bufferSource(),
|
||||
Vec3(0.0,-90.0,-90.0)
|
||||
context.bufferSource()
|
||||
)
|
||||
|
||||
matrices.popPose()
|
||||
|
||||
+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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
-88
@@ -11,24 +11,24 @@ package com.cobblemon.mod.common.client.render.item
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags.WEARABLE_FACE_ITEMS
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags.WEARABLE_HAT_ITEMS
|
||||
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
|
||||
import com.cobblemon.mod.common.client.render.MatrixWrapper
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.NullObjectParser
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PosableState
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import com.mojang.math.Axis
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.renderer.LightTexture
|
||||
import net.minecraft.client.renderer.MultiBufferSource
|
||||
import net.minecraft.client.renderer.entity.ItemRenderer
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.item.ItemDisplayContext
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class HeldItemRenderer {
|
||||
private val itemRenderer: ItemRenderer = Minecraft.getInstance().itemRenderer
|
||||
private var displayContext: ItemDisplayContext = ItemDisplayContext.FIXED
|
||||
class HeldItemRenderer() {
|
||||
private val itemRenderer = Minecraft.getInstance().itemRenderer
|
||||
private var displayContext = ItemDisplayContext.FIXED
|
||||
private var scale = 1.0f
|
||||
|
||||
companion object {
|
||||
const val ITEM_FACE = "item_face"
|
||||
@@ -37,130 +37,99 @@ class HeldItemRenderer {
|
||||
}
|
||||
|
||||
fun render(
|
||||
entity: LivingEntity?,
|
||||
entity: PokemonEntity?,
|
||||
model: PosableModel,
|
||||
item: ItemStack,
|
||||
locators: Map<String, MatrixWrapper>,
|
||||
poseStack: PoseStack,
|
||||
buffer: MultiBufferSource,
|
||||
light: Int,
|
||||
seed: Int,
|
||||
rotation: Vec3
|
||||
seed: Int
|
||||
) {
|
||||
if (item.isEmpty) return
|
||||
displayContext = ItemDisplayContext.FIXED
|
||||
|
||||
poseStack.pushPose()
|
||||
when {
|
||||
//item_face locator
|
||||
(locators.containsKey(ITEM_FACE) && item.`is`(WEARABLE_FACE_ITEMS)) -> {
|
||||
displayContext = model.getLocatorDisplayContext(ITEM_FACE) ?: ItemDisplayContext.HEAD
|
||||
poseStack.mulPose(locators[ITEM_FACE]!!.matrix)
|
||||
poseStack.translate(0f, 0f, .28f)
|
||||
poseStack.scale(0.7f, 0.7f, 0.7f)
|
||||
poseStack.translate(0f, 0f, .28f * scale)
|
||||
poseStack.scale(0.7f * scale, 0.7f * scale, 0.7f * scale)
|
||||
}
|
||||
//item_hat locator
|
||||
(locators.containsKey(ITEM_HAT) && item.`is`(WEARABLE_HAT_ITEMS)) -> {
|
||||
(locators.containsKey("item_hat") && item.`is`(WEARABLE_HAT_ITEMS)) -> {
|
||||
displayContext = model.getLocatorDisplayContext(ITEM_HAT) ?: ItemDisplayContext.HEAD
|
||||
poseStack.mulPose(locators[ITEM_HAT]!!.matrix)
|
||||
poseStack.translate(0f, -0.26f, 0f)
|
||||
poseStack.scale(.68f, .68f, .68f)
|
||||
poseStack.mulPose(locators["item_hat"]!!.matrix)
|
||||
poseStack.translate(0f, -0.26f * scale, 0f)
|
||||
poseStack.scale(.68f * scale, .68f * scale, .68f * scale)
|
||||
}
|
||||
//item locator
|
||||
(locators.containsKey(ITEM)) -> {
|
||||
(locators.containsKey("item")) -> {
|
||||
updateModifiers("item",locators)
|
||||
poseStack.mulPose(locators["item"]!!.matrix)
|
||||
|
||||
displayContext = model.getLocatorDisplayContext(ITEM) ?: ItemDisplayContext.FIXED
|
||||
poseStack.mulPose(locators[ITEM]!!.matrix)
|
||||
applyContextTransformation(poseStack, rotation)
|
||||
|
||||
when (displayContext) {
|
||||
ItemDisplayContext.FIXED -> {
|
||||
poseStack.translate(0f, 0.01666f * scale, 0f)
|
||||
poseStack.scale(.5f * scale, .5f * scale, .5f * scale)
|
||||
poseStack.mulPose(Axis.XP.rotationDegrees(90f))
|
||||
}
|
||||
ItemDisplayContext.THIRD_PERSON_RIGHT_HAND -> {
|
||||
poseStack.translate(0.075f* scale, 0f * scale, -0.05f* scale)
|
||||
poseStack.scale(scale, scale, scale)
|
||||
poseStack.mulPose(Axis.XP.rotationDegrees(-90f))
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees(-90f))
|
||||
}
|
||||
else -> {
|
||||
poseStack.scale(scale, scale, scale)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Don't render any item
|
||||
else -> {
|
||||
else -> { // Don't render any item
|
||||
poseStack.popPose()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
itemRenderer.renderStatic(entity, item, displayContext, (displayContext==ItemDisplayContext.THIRD_PERSON_LEFT_HAND), poseStack, buffer, null, light, OverlayTexture.NO_OVERLAY, seed)
|
||||
itemRenderer.renderStatic(entity, item, displayContext, false, poseStack, buffer, null, light, OverlayTexture.NO_OVERLAY, seed)
|
||||
poseStack.popPose()
|
||||
}
|
||||
|
||||
private fun renderAnimationItem(
|
||||
model: PosableModel,
|
||||
state: PosableState,
|
||||
locators: Map<String, MatrixWrapper>,
|
||||
poseStack: PoseStack,
|
||||
buffer: MultiBufferSource,
|
||||
light: Int,
|
||||
entity: LivingEntity?,
|
||||
rotation: Vec3
|
||||
) {
|
||||
state.itemRenderingLocations.forEach { (locatorName, item) ->
|
||||
poseStack.pushPose()
|
||||
if (locators.containsKey(locatorName)) {
|
||||
displayContext = model.getLocatorDisplayContext(locatorName)?: ItemDisplayContext.FIXED
|
||||
poseStack.mulPose(locators[locatorName]!!.matrix)
|
||||
applyContextTransformation(poseStack, rotation)
|
||||
}
|
||||
itemRenderer.renderStatic(entity, item, displayContext, (displayContext==ItemDisplayContext.THIRD_PERSON_LEFT_HAND), poseStack, buffer, null, light, OverlayTexture.NO_OVERLAY, 0)
|
||||
poseStack.popPose()
|
||||
}
|
||||
}
|
||||
|
||||
fun renderOnModel(
|
||||
model: PosableModel,
|
||||
item: ItemStack,
|
||||
state: PosableState,
|
||||
locators: Map<String, MatrixWrapper>,
|
||||
poseStack: PoseStack,
|
||||
buffer: MultiBufferSource,
|
||||
rotation: Vec3 = Vec3(0.0,0.0,0.0),
|
||||
light: Int = LightTexture.pack(11, 7),
|
||||
) {
|
||||
val locators: Map<String, MatrixWrapper> = state.locatorStates
|
||||
if (state.itemRenderingLocations.isNotEmpty()) {
|
||||
renderAnimationItem(model, state, locators, poseStack, buffer, light, null, rotation)
|
||||
}
|
||||
else if (!item.`is`(CobblemonItemTags.HIDDEN_ITEMS)) {
|
||||
render(null, model, item, locators, poseStack, buffer, light, 0, rotation)
|
||||
}
|
||||
light: Int = LightTexture.pack(11, 7)
|
||||
){
|
||||
if (!item.`is`(CobblemonItemTags.HIDDEN_ITEMS)) render(null, model, item, locators, poseStack, buffer, light, 0)
|
||||
}
|
||||
|
||||
fun renderOnEntity(
|
||||
entity: LivingEntity,
|
||||
item: ItemStack,
|
||||
entity: PokemonEntity,
|
||||
delegate: PokemonClientDelegate,
|
||||
model: PosableModel,
|
||||
state: PosableState,
|
||||
poseStack: PoseStack,
|
||||
buffer: MultiBufferSource,
|
||||
light: Int,
|
||||
rotation: Vec3 = Vec3(0.0,0.0,0.0)
|
||||
light: Int
|
||||
) {
|
||||
val locators: Map<String, MatrixWrapper> = state.locatorStates
|
||||
if (state.itemRenderingLocations.isNotEmpty()) {
|
||||
renderAnimationItem(model, state, locators, poseStack, buffer, light, entity, rotation)
|
||||
}
|
||||
else {
|
||||
render(entity, model, item, locators, poseStack, buffer, light, 0, rotation)
|
||||
}
|
||||
val shownItem = entity.shownItem
|
||||
val locators: Map<String, MatrixWrapper> = delegate.locatorStates
|
||||
|
||||
render(entity, model, shownItem, locators, poseStack, buffer, light, 0)
|
||||
}
|
||||
|
||||
private fun applyContextTransformation(poseStack: PoseStack, rotation: Vec3) {
|
||||
when (displayContext) {
|
||||
ItemDisplayContext.FIXED -> {
|
||||
poseStack.mulPose(Axis.XP.rotationDegrees(90.0f))
|
||||
poseStack.scale(.5f, .5f, .5f )
|
||||
poseStack.translate(0f, 0.01666f, 0f)
|
||||
private fun updateModifiers(name: String, locators: Map<String, MatrixWrapper>) {
|
||||
locators.forEach { (locator: String, m: MatrixWrapper) ->
|
||||
val modifiers: Map<String, Float>
|
||||
if (locator.startsWith("_null_$name[")) {
|
||||
modifiers = NullObjectParser.parseNullObject(locator).modifiers
|
||||
if (modifiers.containsKey("scale")) scale = modifiers["scale"]!!
|
||||
if (modifiers.containsKey("mode")) displayContext = ItemDisplayContext.entries[((modifiers["mode"]!!).toInt()) % 9]
|
||||
return
|
||||
}
|
||||
ItemDisplayContext.THIRD_PERSON_RIGHT_HAND, ItemDisplayContext.THIRD_PERSON_LEFT_HAND -> {
|
||||
poseStack.mulPose(Axis.XP.rotationDegrees((rotation.x).toFloat()))
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees((rotation.y).toFloat()))
|
||||
poseStack.mulPose(Axis.ZP.rotationDegrees((rotation.z).toFloat()))
|
||||
poseStack.translate(0.025f * if (displayContext==ItemDisplayContext.THIRD_PERSON_LEFT_HAND) 1 else -1 , 0.0f, 0.0f)
|
||||
}
|
||||
ItemDisplayContext.HEAD -> {
|
||||
poseStack.mulPose(Axis.XP.rotationDegrees(90.0f))
|
||||
poseStack.scale(.6f, .6f, .6f)
|
||||
poseStack.translate(0f, -0.6f, 0f)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-4
@@ -37,7 +37,6 @@ import net.minecraft.nbt.Tag
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent<T, PlayerModel<T>>) : RenderLayer<T, PlayerModel<T>>(renderLayerParent) {
|
||||
|
||||
@@ -164,10 +163,9 @@ class PokemonOnShoulderRenderer<T : Player>(renderLayerParent: RenderLayerParent
|
||||
heldItemRenderer.renderOnModel(
|
||||
model,
|
||||
shoulderData.shownItem,
|
||||
state,
|
||||
state.locatorStates,
|
||||
matrixStack,
|
||||
buffer,
|
||||
Vec3(0.0,-90.0,-90.0)
|
||||
buffer
|
||||
)
|
||||
|
||||
model.setDefault()
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.client.render.models.blockbench
|
||||
|
||||
/**
|
||||
* Uses the name of null objects in a model to get modifier values for a locator.
|
||||
* @author Josh
|
||||
*/
|
||||
object NullObjectParser {
|
||||
fun parseNullObject(input: String): NullObjectModifier {
|
||||
// Extract modifiedLocator
|
||||
val modifiedLocator = input.replaceFirst("^_null_".toRegex(), "").replace("\\[.*\\]$".toRegex(), "")
|
||||
|
||||
// Extract modifiers
|
||||
val modifiers: MutableMap<String, Float> = HashMap()
|
||||
val start = input.indexOf('[')
|
||||
val end = input.indexOf(']')
|
||||
|
||||
if (start != -1 && end != -1 && end > start) {
|
||||
val modifierPart = input.substring(start + 1, end)
|
||||
val modifierPairs = modifierPart.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
|
||||
for (pair in modifierPairs) {
|
||||
val keyValue = pair.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
if (keyValue.size == 2) {
|
||||
val key = keyValue[0].trim { it <= ' ' }
|
||||
try {
|
||||
val value = keyValue[1].trim { it <= ' ' }.toFloat()
|
||||
modifiers[key] = value
|
||||
} catch (e: NumberFormatException) {
|
||||
System.err.println("Invalid number format for: " + keyValue[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NullObjectModifier(modifiedLocator, modifiers)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores modifiers for a particular locator.
|
||||
*/
|
||||
class NullObjectModifier(val locatorName: String, val modifiers: Map<String, Float>)
|
||||
}
|
||||
-2
@@ -17,7 +17,6 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.client.ClientMoLangFunctions.animationFunctions
|
||||
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
|
||||
import com.cobblemon.mod.common.client.entity.NPCClientDelegate
|
||||
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
|
||||
import com.cobblemon.mod.common.client.render.AnimatedModelTextureSupplier
|
||||
import com.cobblemon.mod.common.client.render.ModelLayer
|
||||
@@ -57,7 +56,6 @@ import net.minecraft.client.renderer.texture.DynamicTexture
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.item.ItemDisplayContext
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.Matrix4f
|
||||
|
||||
-26
@@ -36,18 +36,14 @@ import com.cobblemon.mod.common.client.render.models.blockbench.quirk.QuirkData
|
||||
import com.cobblemon.mod.common.entity.PosableEntity
|
||||
import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.mojang.brigadier.StringReader
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.multiplayer.ClientLevel
|
||||
import net.minecraft.client.resources.sounds.SimpleSoundInstance
|
||||
import net.minecraft.commands.arguments.item.ItemParser
|
||||
import net.minecraft.sounds.SoundEvent
|
||||
import net.minecraft.sounds.SoundSource
|
||||
import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.Matrix4f
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
/**
|
||||
@@ -128,10 +124,6 @@ abstract class PosableState : Schedulable {
|
||||
/** This gets called 500 million times so use a mutable value for runtime */
|
||||
private val reusableAnimTime = DoubleValue(0.0)
|
||||
|
||||
val itemRenderingLocations = mutableMapOf<String, ItemStack>()
|
||||
// var itemRenderingLocation = ""
|
||||
// var animationItem : ItemStack? = null
|
||||
|
||||
/** All of the MoLang functions that can be applied to something with this state. */
|
||||
val functions = QueryStruct(hashMapOf())
|
||||
.addFunction("anim_time") {
|
||||
@@ -156,24 +148,6 @@ abstract class PosableState : Schedulable {
|
||||
)
|
||||
}
|
||||
}
|
||||
.addFunction("render_item") { params ->
|
||||
if (params.get<MoValue>(0) !is StringValue) return@addFunction Unit
|
||||
|
||||
val client = Minecraft.getInstance().connection ?: return@addFunction Unit
|
||||
val result = ItemParser(client.registryAccess()).parse(StringReader(params.getString(0)))
|
||||
val item = ItemStack(result.item)
|
||||
item.applyComponents(result.components)
|
||||
|
||||
val renderLocation = if (params.contains(1)) params.getString(1) else "item"
|
||||
|
||||
if (!item.isEmpty && locatorStates.containsKey(renderLocation)) {
|
||||
itemRenderingLocations.put(renderLocation, item)
|
||||
} else {
|
||||
if (itemRenderingLocations.containsKey(renderLocation)) itemRenderingLocations.remove(renderLocation)
|
||||
return@addFunction Unit
|
||||
}
|
||||
}
|
||||
.addFunction("clear_items") { itemRenderingLocations.clear() }
|
||||
.addFunction("play_animation") { params ->
|
||||
val animationParameter = params.get<MoValue>(0)
|
||||
val animation = if (animationParameter is ObjectValue<*>) {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
package com.cobblemon.mod.common.client.render.npc
|
||||
|
||||
import com.cobblemon.mod.common.client.entity.NPCClientDelegate
|
||||
import com.cobblemon.mod.common.client.render.item.HeldItemRenderer
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.PosableEntityModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.npc.PosableNPCModel
|
||||
import com.cobblemon.mod.common.client.render.models.blockbench.repository.RenderContext
|
||||
@@ -20,7 +19,6 @@ import net.minecraft.client.renderer.MultiBufferSource
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider.Context
|
||||
import net.minecraft.client.renderer.entity.LivingEntityRenderer
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.math.min
|
||||
|
||||
class NPCRenderer(context: Context) : LivingEntityRenderer<NPCEntity, PosableEntityModel<NPCEntity>>(context, PosableNPCModel(), 0.5f) {
|
||||
@@ -28,8 +26,6 @@ class NPCRenderer(context: Context) : LivingEntityRenderer<NPCEntity, PosableEnt
|
||||
return VaryingModelRepository.getTexture(entity.npc.resourceIdentifier, (entity.delegate as NPCClientDelegate))
|
||||
}
|
||||
|
||||
private val heldItemRenderer = HeldItemRenderer()
|
||||
|
||||
override fun render(
|
||||
entity: NPCEntity,
|
||||
entityYaw: Float,
|
||||
@@ -60,20 +56,6 @@ class NPCRenderer(context: Context) : LivingEntityRenderer<NPCEntity, PosableEnt
|
||||
model.blue = 1F
|
||||
model.resetLayerContext()
|
||||
|
||||
if (entity.deathTime < 1){
|
||||
//Render Held Item
|
||||
heldItemRenderer.renderOnEntity(
|
||||
entity,
|
||||
entity.mainHandItem,
|
||||
model,
|
||||
clientDelegate,
|
||||
poseMatrix,
|
||||
buffer,
|
||||
packedLight,
|
||||
Vec3(-90.0,0.0,0.0)
|
||||
)
|
||||
}
|
||||
|
||||
// if (this.shouldRenderLabel(entity)) {
|
||||
// this.renderLabelIfPresent(entity, entity.displayName, poseMatrix, buffer, packedLight)
|
||||
// }
|
||||
|
||||
+3
-5
@@ -101,7 +101,7 @@ class PokemonRenderer(
|
||||
packedLight: Int
|
||||
) {
|
||||
val clientDelegate = entity.delegate as PokemonClientDelegate
|
||||
shadowRadius = (min((entity.boundingBox.maxX - entity.boundingBox.minX), (entity.boundingBox.maxZ) - (entity.boundingBox.minZ)).toFloat() / 1.5F * (entity.delegate as PokemonClientDelegate).entityScaleModifier)/entity.scale
|
||||
shadowRadius = min((entity.boundingBox.maxX - entity.boundingBox.minX), (entity.boundingBox.maxZ) - (entity.boundingBox.minZ)).toFloat() / 1.5F * (entity.delegate as PokemonClientDelegate).entityScaleModifier
|
||||
model.posableModel = VaryingModelRepository.getPoser(entity.pokemon.species.resourceIdentifier, clientDelegate)
|
||||
model.posableModel.context = model.context
|
||||
model.setupEntityTypeContext(entity)
|
||||
@@ -160,13 +160,11 @@ class PokemonRenderer(
|
||||
//Render Held Item
|
||||
heldItemRenderer.renderOnEntity(
|
||||
entity,
|
||||
entity.shownItem,
|
||||
model.posableModel,
|
||||
clientDelegate,
|
||||
model.posableModel,
|
||||
poseMatrix,
|
||||
buffer,
|
||||
packedLight,
|
||||
Vec3(0.0,-90.0,90.0)
|
||||
packedLight
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -557,10 +557,10 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
|
||||
|
||||
private fun pokemonExpressions(pokemon: Pokemon): List<String> {
|
||||
return listOf(
|
||||
"v.actioning_pokemon_name=\'${pokemon.species.name}\';",
|
||||
"v.actioning_pokemon_name=\"${pokemon.species.name}\";",
|
||||
"v.actioning_pokemon_level=${pokemon.level};",
|
||||
"v.actioning_pokemon_ball=\'${pokemon.caughtBall.name}\';",
|
||||
"v.actioning_pokemon_shiny=\'${pokemon.shiny}\';",
|
||||
"v.actioning_pokemon_ball=\"${pokemon.caughtBall.name}\";",
|
||||
"v.actioning_pokemon_shiny=\"${pokemon.shiny}\";",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -959,7 +959,6 @@ open class PokemonEntity(
|
||||
val scale = effects.mockEffect?.scale ?: (form.baseScale * pokemon.scaleModifier)
|
||||
var result = this.exposedForm.hitbox.scale(scale)
|
||||
result = result.withEyeHeight(this.exposedForm.eyeHeight(this) * result.height)
|
||||
result = result.scale(this.scale)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1311,7 +1310,7 @@ open class PokemonEntity(
|
||||
}
|
||||
} else if (form.behaviour.moving.swim.canBreatheUnderwater && !form.behaviour.moving.swim.canWalkOnWater) {
|
||||
// Use half hitbox height for swimmers
|
||||
val halfHeight = getDimensions(this.pose).height / 2.0
|
||||
val halfHeight = form.hitbox.height * form.baseScale / 2.0
|
||||
for (i in 1..halfHeight.toInt()) {
|
||||
blockPos = blockPos.below()
|
||||
if (!this.level().isWaterAt(blockPos) || !this.level().getBlockState(blockPos).getCollisionShape(this.level(), blockPos).isEmpty) {
|
||||
@@ -1813,11 +1812,6 @@ open class PokemonEntity(
|
||||
return this.riding.speed(this, controller)
|
||||
}
|
||||
|
||||
fun useRidingAltPose(): Boolean {
|
||||
val driver = this.controllingPassenger as? Player ?: return false
|
||||
return this.riding.useRidingAltPose(this, driver)
|
||||
}
|
||||
|
||||
var jumpInputStrength: Int = 0 // move this
|
||||
override fun onPlayerJump(strength: Int) {
|
||||
// See if this controls the hot bar element
|
||||
|
||||
-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()
|
||||
)
|
||||
}
|
||||
}
|
||||
-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
|
||||
}
|
||||
}
|
||||
}
|
||||
+106
-237
@@ -9,7 +9,7 @@
|
||||
package com.cobblemon.mod.common.pokemon.riding.controllers
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.bedrockk.molang.runtime.MoLangMath.lerp
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.Rollable
|
||||
import com.cobblemon.mod.common.api.riding.controller.RideController
|
||||
@@ -19,9 +19,7 @@ import com.cobblemon.mod.common.entity.PoseType
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.pokemon.riding.states.BirdAirState
|
||||
import com.cobblemon.mod.common.util.*
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
@@ -34,37 +32,81 @@ class BirdAirController : RideController {
|
||||
override val poseProvider = PoseProvider(PoseType.HOVER)
|
||||
.with(PoseOption(PoseType.FLY) {
|
||||
val player = it.passengers.firstOrNull() as? Player ?: return@PoseOption false
|
||||
it.deltaMovement.length() > 0.5 })
|
||||
it.deltaMovement.length() > 0.3 })
|
||||
/*
|
||||
.with(PoseOption(PoseType.FLY) {
|
||||
val player = it.passengers.firstOrNull() as? Player ?: return@PoseOption false
|
||||
player.zza > 0.0f // Checks if the player is actively attempting to move forward
|
||||
})
|
||||
*/
|
||||
|
||||
override val condition: (PokemonEntity) -> Boolean = { true }
|
||||
|
||||
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression()
|
||||
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
|
||||
private set
|
||||
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression()
|
||||
var gravity: Expression = "0".asExpression()
|
||||
private set
|
||||
var glideTopSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.0, 1.0)".asExpression()
|
||||
var horizontalAcceleration: Expression = "0.1".asExpression()
|
||||
private set
|
||||
// Max accel is a whole 1.0 in 3 seconds. The conversion in the function below is to convert seconds to ticks
|
||||
var accelExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression()
|
||||
private set
|
||||
//Seconds self propelled flight (glide is not self propelled in this case)
|
||||
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression()
|
||||
private set
|
||||
//max y level for the ride
|
||||
var altitudeExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
|
||||
var verticalAcceleration: Expression = "0.1".asExpression()
|
||||
private set
|
||||
|
||||
var infiniteStamina: Expression = "false".asExpression()
|
||||
// Where 90 is the width of the range and 45 is the offset.
|
||||
// So the handling interpolates between 135 and 45
|
||||
var handlingExpr: Expression = "(q.get_ride_stats('SKILL', 'AIR') / 100) * 90 + 45".asExpression()
|
||||
private set
|
||||
var infiniteAltitude: Expression = "false".asExpression()
|
||||
var topSpeedExpr: Expression = "(q.get_ride_stats('SPEED', 'AIR') / 100) * (1.0 - 0.35) + 0.35".asExpression()
|
||||
private set
|
||||
//Reaches max top speed from a standstill at minimum 8 seconds and max 3 seconds.
|
||||
//This is a bit of a confusing expression and likely should be cleaned up to be more readable
|
||||
var accelExpr: Expression = "(q.get_ride_stats('ACCELERATION', 'AIR') / 100) * (0.01666666666 - 0.00625) + 0.00625".asExpression()
|
||||
private set
|
||||
|
||||
override fun speed(entity: PokemonEntity, driver: Player): Float {
|
||||
|
||||
//Retrieve the current bird controller state and stats
|
||||
//Retrieve the current bird controller state
|
||||
//val state = entity.riding.getState(KEY) { e -> BirdAirControllerState(e) }
|
||||
val state = getState(entity, ::BirdAirState)
|
||||
|
||||
return state.rideVel.length().toFloat()
|
||||
//retrieve stats
|
||||
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
|
||||
val accel = getRuntime(entity).resolveDouble(accelExpr)
|
||||
|
||||
val maxGlideSpeed = topSpeed * 2
|
||||
var glideSpeedChange = 0.00
|
||||
|
||||
//speed up and slow down based on input
|
||||
if (driver.zza > 0.0 && state.currSpeed < topSpeed) {
|
||||
state.currSpeed = min(state.currSpeed + accel , topSpeed)
|
||||
} else if (driver.zza < 0.0 && state.currSpeed > 0.0) {
|
||||
//Decelerate is currently always a constant half of max acceleration.
|
||||
state.currSpeed = max(state.currSpeed - (0.0166 / 2), 0.0)
|
||||
}
|
||||
|
||||
val rollable = driver as? Rollable
|
||||
if (rollable != null) {
|
||||
glideSpeedChange = sin(Math.toRadians(rollable.pitch.toDouble()))
|
||||
glideSpeedChange = glideSpeedChange.pow(3)
|
||||
|
||||
//TODO: Possibly create a deadzone around parallel where speed isn't taken away from?
|
||||
if (glideSpeedChange <= 0.0) {
|
||||
//Ensures that a propelling force is still able to be applied when
|
||||
//climbing in height
|
||||
if (driver.zza <= 0) {
|
||||
//speed decrease should be 2x speed increase?
|
||||
state.currSpeed = max(state.currSpeed + (0.0166) * glideSpeedChange, 0.0 )
|
||||
}
|
||||
} else {
|
||||
// only add to the speed if it hasn't exceeded the current
|
||||
//glide angles maximum amount of speed that it can give.
|
||||
state.currSpeed = min(state.currSpeed + ((0.0166 * 2) * glideSpeedChange), maxGlideSpeed)
|
||||
}
|
||||
}
|
||||
|
||||
//air resistance
|
||||
state.currSpeed = max(state.currSpeed - 0.005, 0.0)
|
||||
|
||||
return state.currSpeed.toFloat()
|
||||
}
|
||||
|
||||
override fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2 {
|
||||
@@ -76,60 +118,62 @@ class BirdAirController : RideController {
|
||||
var leftForce = 0.0
|
||||
var upForce = 0.0
|
||||
var forwardForce = 0.0
|
||||
val state = getState(entity, ::BirdAirState)
|
||||
|
||||
//Perform ride velocity update
|
||||
calculateRideSpaceVel(entity, driver, state)
|
||||
|
||||
//Translate ride space velocity to world space velocity.
|
||||
val rollable = driver as? Rollable
|
||||
if (rollable != null) {
|
||||
//Need to deadzone this when straight up or down
|
||||
// test with and without strafing added. May be wanted while moving
|
||||
leftForce = -1.0 * sin(Math.toRadians(rollable.roll.toDouble()))
|
||||
|
||||
upForce += -1.0 * sin(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.z
|
||||
forwardForce += cos(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.z
|
||||
upForce = -1.0 * sin(Math.toRadians(rollable.pitch.toDouble()))
|
||||
|
||||
upForce += cos(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.y
|
||||
forwardForce += sin(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.y
|
||||
forwardForce = cos(Math.toRadians(rollable.pitch.toDouble()))
|
||||
}
|
||||
|
||||
//TODO: Add in an upwards and downwards force when pressing space maybe?
|
||||
|
||||
//Bring the ride out of the sky when stamina is depleted.
|
||||
if (state.stamina <= 0.0) {
|
||||
upForce -= 0.3
|
||||
//Leave out tap to flap right now
|
||||
/*
|
||||
var jumpVector = Vec3.ZERO
|
||||
if (entity.jumpInputStrength > 0) {
|
||||
val runtime = getRuntime(entity)
|
||||
runtime.environment.query.addFunction("jump_strength") { DoubleValue(entity.jumpInputStrength.toDouble()) }
|
||||
val jumpForces = this.jumpVector.map { runtime.resolveFloat(it) }
|
||||
jumpVector = Vec3(jumpForces[0].toDouble(), jumpForces[1].toDouble(), jumpForces[2].toDouble())
|
||||
jumpVector = jumpVector.toVector3f().mul((driver as Rollable).orientation).toVec3d()
|
||||
entity.jumpInputStrength = 0
|
||||
entity.addDeltaMovement(jumpVector)
|
||||
}
|
||||
*/
|
||||
//Retrieve the current composite controller state
|
||||
val state = getState(entity, ::BirdAirState)
|
||||
|
||||
val altitudeLimit = getRuntime(entity).resolveDouble(altitudeExpr)
|
||||
val velocity = Vec3(0.0 , upForce * state.currSpeed, forwardForce * state.currSpeed)
|
||||
|
||||
//Only limit altitude if altitude is not infinite
|
||||
if (!getRuntime(entity).resolveBoolean(infiniteAltitude)) {
|
||||
//Provide a hard limit on altitude
|
||||
upForce = if (entity.y >= altitudeLimit && upForce > 0) 0.0 else upForce
|
||||
}
|
||||
|
||||
val velocity = Vec3(state.rideVel.x , upForce, forwardForce)
|
||||
return velocity
|
||||
}
|
||||
|
||||
override fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
|
||||
|
||||
val rollable = driver as? Rollable
|
||||
val state = getState(entity, ::BirdAirState)
|
||||
|
||||
//TODO: Tie in handling
|
||||
val handling = getRuntime(entity).resolveDouble(handlingExpr)
|
||||
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
|
||||
val rotationChangeRate = 10.0
|
||||
|
||||
if (rollable != null) {
|
||||
var yawForce = rotationChangeRate * sin(Math.toRadians(rollable.roll.toDouble()))
|
||||
//for a bit of correction on the rolls influence pitch as well
|
||||
var pitchForce = -0.35 * rotationChangeRate * abs(sin(Math.toRadians(rollable.roll.toDouble())))
|
||||
//for a bit of correction on the rolls limit it to a quarter the amount
|
||||
var pitchForce = -0.25 * rotationChangeRate * abs(sin(Math.toRadians(rollable.roll.toDouble())))
|
||||
|
||||
//limit rotation modulation when pitched up heavily or pitched down heavily
|
||||
yawForce *= abs(cos(Math.toRadians(rollable.pitch.toDouble())))
|
||||
pitchForce *= abs(cos(Math.toRadians(rollable.pitch.toDouble()))) * 1.5
|
||||
|
||||
//Create a deadzone for easier leveling out
|
||||
if (rollable.roll <= 0.0 && rollable.roll >= -0.0) {
|
||||
yawForce = 0.0
|
||||
pitchForce = 0.0
|
||||
}
|
||||
return Vec3(yawForce, pitchForce, 0.0)
|
||||
}
|
||||
|
||||
@@ -147,56 +191,42 @@ class BirdAirController : RideController {
|
||||
deltaTime: Double
|
||||
): Vec3 {
|
||||
if (driver !is Rollable) return Vec3.ZERO
|
||||
|
||||
val rollable = driver as Rollable
|
||||
|
||||
val state = getState(entity, ::BirdAirState)
|
||||
val handling = getRuntime(entity).resolveDouble(handlingExpr)
|
||||
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
|
||||
|
||||
//Smooth out mouse input.
|
||||
val smoothingSpeed = 4
|
||||
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
|
||||
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
|
||||
val xInput = xMouseSmoother.getNewDeltaValue(xMouse * 0.1 * invertRoll, deltaTime * smoothingSpeed);
|
||||
val yInput = yMouseSmoother.getNewDeltaValue(yMouse * 0.1 * invertPitch, deltaTime * smoothingSpeed);
|
||||
var xInput = xMouseSmoother.getNewDeltaValue(xMouse * 0.1 * invertRoll, deltaTime * smoothingSpeed);
|
||||
val YInput = yMouseSmoother.getNewDeltaValue(yMouse * 0.1 * invertPitch, deltaTime * smoothingSpeed);
|
||||
|
||||
//limit rolling based on handling and current speed.
|
||||
//modulated by speed so that when flapping idle in air you are ont wobbling around to look around
|
||||
val rotMin = 15.0
|
||||
var rollForce = xInput
|
||||
val rotLimit = max(handling * normalizeVal(state.rideVel.length(), 0.0, topSpeed).pow(3), rotMin)
|
||||
//limit rolling based on healing
|
||||
val rotLimit = handling
|
||||
|
||||
//Limit roll by non linearly decreasing inputs towards
|
||||
// a rotation limit based on the current distance from
|
||||
// that rotation limit
|
||||
if (abs(rollable.roll + rollForce) < rotLimit) {
|
||||
if (sign(rollForce) == sign(rollable.roll).toDouble()) {
|
||||
if (abs(rollable.roll + xInput) < rotLimit) {
|
||||
if (sign(xInput) == sign(rollable.roll).toDouble()) {
|
||||
val d = abs(abs(rollable.roll) - rotLimit)
|
||||
rollForce *= (d.pow(2)) / (rotLimit.pow(2))
|
||||
xInput *= (d.pow(2)) / (rotLimit.pow(2))
|
||||
}
|
||||
} else if (sign(rollForce) == sign(rollable.roll).toDouble()) {
|
||||
rollForce = 0.0
|
||||
} else if (sign(xInput) == sign(rollable.roll).toDouble()) {
|
||||
xInput = 0.0
|
||||
}
|
||||
|
||||
//Give the ability to yaw with x mouse input when at low speeds.
|
||||
val yawForce = xInput * ( 1.0 - normalizeVal(state.rideVel.length(), 0.0, topSpeed).pow(3))
|
||||
|
||||
//yaw, pitch, roll
|
||||
return Vec3(yawForce, yInput, rollForce)
|
||||
return Vec3(0.0, YInput, xInput)
|
||||
}
|
||||
|
||||
override fun canJump(
|
||||
entity: PokemonEntity,
|
||||
driver: Player
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun setRideBar(entity: PokemonEntity, driver: Player): Float {
|
||||
//Retrieve stamina from state
|
||||
val state = getState(entity, ::BirdAirState)
|
||||
|
||||
return (state.stamina / 1.0f)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun jumpForce(
|
||||
@@ -213,190 +243,29 @@ class BirdAirController : RideController {
|
||||
|
||||
override fun shouldRoll(entity: PokemonEntity) = true
|
||||
|
||||
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
|
||||
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
|
||||
val glideTopSpeed = getRuntime(entity).resolveDouble(glideTopSpeedExpr)
|
||||
val state = getState(entity, ::BirdAirState)
|
||||
|
||||
//Must I ensure that topspeed is greater than minimum?
|
||||
val normalizedGlideSpeed = normalizeVal(state.rideVel.length(), topSpeed, glideTopSpeed)
|
||||
|
||||
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
|
||||
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
|
||||
|
||||
}
|
||||
|
||||
override fun useAngVelSmoothing(entity: PokemonEntity): Boolean = false
|
||||
|
||||
override fun useRidingAltPose(entity: PokemonEntity, driver: Player): Boolean {
|
||||
val state = getState(entity, ::BirdAirState)
|
||||
return state.gliding
|
||||
}
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
super.encode(buffer)
|
||||
buffer.writeString(gravity.toString())
|
||||
buffer.writeString(horizontalAcceleration.toString())
|
||||
buffer.writeString(verticalAcceleration.toString())
|
||||
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())
|
||||
}
|
||||
|
||||
override fun decode(buffer: RegistryFriendlyByteBuf) {
|
||||
gravity = buffer.readString().asExpression()
|
||||
horizontalAcceleration = buffer.readString().asExpression()
|
||||
verticalAcceleration = buffer.readString().asExpression()
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
val KEY = cobblemonResource("air/bird")
|
||||
}
|
||||
|
||||
/*
|
||||
* Normalizes the given value between a min and a max.
|
||||
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
|
||||
* and 1.0 represents x is at or above it.
|
||||
*/
|
||||
fun normalizeVal(x: Double, min: Double, max: Double): Double {
|
||||
require(max > min) { "max must be greater than min" }
|
||||
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculates the change in the ride space vector due to player input and ride state
|
||||
*/
|
||||
fun calculateRideSpaceVel(entity: PokemonEntity, driver: Player, state: BirdAirState) {
|
||||
|
||||
|
||||
//retrieve stats
|
||||
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
|
||||
val glideTopSpeed = getRuntime(entity).resolveDouble(glideTopSpeedExpr)
|
||||
val accel = getRuntime(entity).resolveDouble(accelExpr)
|
||||
val staminaStat = getRuntime(entity).resolveDouble(staminaExpr)
|
||||
|
||||
var glideSpeedChange = 0.0
|
||||
|
||||
val currSpeed = state.rideVel.length()
|
||||
|
||||
//Flag for determining if player is actively inputting
|
||||
var activeInput = false
|
||||
|
||||
//speed up and slow down based on input
|
||||
if (driver.zza != 0.0f && state.stamina > 0.0) {
|
||||
//make sure it can't exceed top speed
|
||||
val forwardInput = when {
|
||||
driver.zza > 0 && state.rideVel.z > topSpeed -> 0.0
|
||||
driver.zza < 0 && state.rideVel.z < (-topSpeed / 3.0) -> 0.0
|
||||
else -> driver.zza.sign
|
||||
}
|
||||
|
||||
state.rideVel = Vec3(
|
||||
state.rideVel.x,
|
||||
state.rideVel.y,
|
||||
(state.rideVel.z + (accel * forwardInput.toDouble())))
|
||||
|
||||
activeInput = true
|
||||
}
|
||||
|
||||
val rollable = driver as? Rollable
|
||||
if (rollable != null) {
|
||||
//Base glide speed change on current pitch of the ride.
|
||||
glideSpeedChange = sin(Math.toRadians(rollable.pitch.toDouble()))
|
||||
glideSpeedChange = glideSpeedChange.pow(3) * 0.5
|
||||
|
||||
//TODO: Possibly create a deadzone around parallel where glide doesn't affect speed?
|
||||
if (glideSpeedChange <= 0.0) {
|
||||
//Ensures that a propelling force is still able to be applied when
|
||||
//climbing in height
|
||||
if (driver.zza <= 0) {
|
||||
//speed decrease should be 2x speed increase?
|
||||
//state.currSpeed = max(state.currSpeed + (0.0166) * glideSpeedChange, 0.0 )
|
||||
state.rideVel = Vec3(
|
||||
state.rideVel.x,
|
||||
state.rideVel.y,
|
||||
lerp( state.rideVel.z, 0.0,glideSpeedChange * -0.0166 * 2 ))
|
||||
}
|
||||
} else {
|
||||
// only add to the speed if it hasn't exceeded the current
|
||||
//glide angles maximum amount of speed that it can give.
|
||||
//state.currSpeed = min(state.currSpeed + ((0.0166 * 2) * glideSpeedChange), maxGlideSpeed)
|
||||
state.rideVel = Vec3(
|
||||
state.rideVel.x,
|
||||
state.rideVel.y,
|
||||
min(state.rideVel.z + ((0.0166 * 2) * glideSpeedChange), glideTopSpeed))
|
||||
}
|
||||
}
|
||||
|
||||
//Lateral movement based on driver input.
|
||||
val latTopSpeed = topSpeed / 2.0
|
||||
if (driver.xxa != 0.0f && state.stamina > 0.0) {
|
||||
state.rideVel = Vec3(
|
||||
(state.rideVel.x + (accel * driver.xxa)).coerceIn(-latTopSpeed, latTopSpeed),
|
||||
state.rideVel.y,
|
||||
state.rideVel.z)
|
||||
activeInput = true
|
||||
}
|
||||
else {
|
||||
state.rideVel = Vec3(
|
||||
lerp(state.rideVel.x, 0.0, latTopSpeed / 20.0),
|
||||
state.rideVel.y,
|
||||
state.rideVel.z)
|
||||
}
|
||||
|
||||
//Vertical movement based on driver input.
|
||||
val vertTopSpeed = topSpeed / 2.0
|
||||
val vertInput = when {
|
||||
Minecraft.getInstance().options.keyJump.isDown() -> 1.0
|
||||
Minecraft.getInstance().options.keyShift.isDown() -> -1.0
|
||||
else -> 0.0
|
||||
}
|
||||
|
||||
if (vertInput != 0.0 && state.stamina > 0.0) {
|
||||
state.rideVel = Vec3(
|
||||
state.rideVel.x,
|
||||
(state.rideVel.y + (accel * vertInput)).coerceIn(-vertTopSpeed, vertTopSpeed),
|
||||
state.rideVel.z)
|
||||
activeInput = true
|
||||
}
|
||||
else {
|
||||
state.rideVel = Vec3(
|
||||
state.rideVel.x,
|
||||
lerp(state.rideVel.y, 0.0, vertTopSpeed / 20.0),
|
||||
state.rideVel.z)
|
||||
}
|
||||
|
||||
//Check if the ride should be gliding
|
||||
if (activeInput && state.stamina > 0.0) {
|
||||
state.gliding = false
|
||||
}else {
|
||||
state.gliding = true
|
||||
}
|
||||
|
||||
//Only perform stamina logic if the ride does not have infinite stamina
|
||||
if (!getRuntime(entity).resolveBoolean(infiniteStamina)) {
|
||||
if (activeInput) {
|
||||
state.stamina -= (0.05 / staminaStat).toFloat()
|
||||
}
|
||||
|
||||
//Lose a base amount of stamina just for being airborne
|
||||
state.stamina -= (0.01 / staminaStat).toFloat()
|
||||
}
|
||||
else
|
||||
{
|
||||
state.stamina = 1.0f
|
||||
}
|
||||
|
||||
//air resistance
|
||||
state.rideVel = Vec3(
|
||||
state.rideVel.x,
|
||||
state.rideVel.y,
|
||||
lerp( state.rideVel.z,0.0, topSpeed / ( 20.0 * 30.0)))
|
||||
}
|
||||
}
|
||||
+9
-7
@@ -49,9 +49,9 @@ class JetAirController : RideController {
|
||||
var accelExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.0)), (1.0 / (20.0 * 5.0)))".asExpression()
|
||||
private set
|
||||
// Between 60 seconds and 10 seconds at the lowest when at full speed.
|
||||
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 60.0, 10.0)".asExpression()
|
||||
var staminaExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 60.0, 10.0)".asExpression()
|
||||
private set
|
||||
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 300.0, 128.0)".asExpression()
|
||||
var jumpExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 300.0, 128.0)".asExpression()
|
||||
private set
|
||||
|
||||
// Make configurable by json
|
||||
@@ -123,12 +123,14 @@ class JetAirController : RideController {
|
||||
upForce = if(state.stamina > 0.0) upForce else -0.7
|
||||
|
||||
val altitudeLimit = getRuntime(entity).resolveDouble(jumpExpr)
|
||||
val pushingHeightLimit = if(getRuntime(entity).resolveBoolean(infiniteAltitude)) false
|
||||
else (entity.y >= 128 && entity.xRot <= 0)
|
||||
|
||||
//Only limit altitude if altitude is not infinite
|
||||
if (!getRuntime(entity).resolveBoolean(infiniteAltitude)) {
|
||||
//Provide a hard limit on altitude
|
||||
upForce = if (entity.y >= altitudeLimit && upForce > 0) 0.0 else upForce
|
||||
}
|
||||
|
||||
//Incorporate the altitude limiter.
|
||||
//Apply downwards force if limit is reached. Resist upwards force on a buffer of 30 blocks past the limit.
|
||||
upForce = if(pushingHeightLimit) upForce - upForce * ((entity.y - altitudeLimit) / 30) - 0.5
|
||||
else upForce
|
||||
|
||||
val velocity = Vec3(0.0 , upForce, forwardForce )
|
||||
|
||||
|
||||
+15
-40
@@ -23,7 +23,6 @@ import com.cobblemon.mod.common.util.getString
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.util.SmoothDouble
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
@@ -72,37 +71,15 @@ class RunUpToFlightCompositeController : RideController {
|
||||
driver: Player
|
||||
): Float {
|
||||
val state = getState(entity, ::RunUpToFlightCompositeState)
|
||||
if (state.activeController == flightController && entity.onGround() &&
|
||||
state.timeTransitioned + 20 < entity.level().gameTime) {
|
||||
if (state.activeController == flightController && entity.onGround() && state.timeTransitioned + 20 < entity.level().gameTime) {
|
||||
|
||||
//Pass the speed to the next state
|
||||
val flightState = flightController.getState(entity, ::BirdAirState)
|
||||
val groundState = landController.getState(entity, ::GenericLandState)
|
||||
groundState.currSpeed = flightState.currSpeed
|
||||
groundState.rideVel = flightState.rideVel
|
||||
groundState.stamina = flightState.stamina
|
||||
|
||||
getState(entity, ::RunUpToFlightCompositeState).let {
|
||||
it.activeController = landController
|
||||
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
|
||||
it.timeTransitioned = entity.level().gameTime
|
||||
}
|
||||
}
|
||||
else if (state.activeController == landController && Minecraft.getInstance().options.keyJump.isDown &&
|
||||
state.timeTransitioned + 20 < entity.level().gameTime) {
|
||||
|
||||
//Pass the speed to the next state
|
||||
val flightState = flightController.getState( entity, ::BirdAirState )
|
||||
val groundState = landController.getState( entity, ::GenericLandState )
|
||||
flightState.currSpeed = groundState.currSpeed
|
||||
flightState.rideVel = groundState.rideVel
|
||||
flightState.stamina = groundState.stamina
|
||||
|
||||
getState(entity, ::RunUpToFlightCompositeState).let {
|
||||
it.activeController = flightController
|
||||
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
|
||||
it.timeTransitioned = entity.level().gameTime
|
||||
}
|
||||
getState(entity, ::RunUpToFlightCompositeState).activeController = landController
|
||||
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
|
||||
}
|
||||
|
||||
return getActiveController(entity).speed(entity, driver)
|
||||
@@ -124,14 +101,6 @@ class RunUpToFlightCompositeController : RideController {
|
||||
return getActiveController(entity).angRollVel(entity, driver, deltaTime)
|
||||
}
|
||||
|
||||
override fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
|
||||
return getActiveController(entity).rideFovMult(entity, driver)
|
||||
}
|
||||
|
||||
override fun setRideBar(entity: PokemonEntity, driver: Player): Float {
|
||||
return getActiveController(entity).setRideBar(entity, driver)
|
||||
}
|
||||
|
||||
override fun canJump(entity: PokemonEntity, driver: Player): Boolean {
|
||||
return getActiveController(entity).canJump(entity, driver)
|
||||
}
|
||||
@@ -142,9 +111,19 @@ class RunUpToFlightCompositeController : RideController {
|
||||
jumpStrength: Int
|
||||
): Vec3 {
|
||||
val controller = getActiveController(entity)
|
||||
val state = getState(entity, ::CompositeState)
|
||||
val isJumping = Minecraft.getInstance().options.keyJump.isDown
|
||||
if (controller == landController && jumpStrength >= getRuntime(entity).resolveInt(minimumJump)) {
|
||||
|
||||
//Pass the speed to the next state
|
||||
val flightState = flightController.getState(entity, ::BirdAirState )
|
||||
val groundState = landController.getState(entity, ::GenericLandState)
|
||||
flightState.currSpeed = groundState.currSpeed
|
||||
|
||||
getState(entity, ::RunUpToFlightCompositeState).let {
|
||||
it.activeController = flightController
|
||||
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
|
||||
it.timeTransitioned = entity.level().gameTime
|
||||
}
|
||||
}
|
||||
return controller.jumpForce(entity, driver, jumpStrength)
|
||||
}
|
||||
|
||||
@@ -160,10 +139,6 @@ class RunUpToFlightCompositeController : RideController {
|
||||
|
||||
override fun useAngVelSmoothing(entity: PokemonEntity): Boolean = getActiveController(entity).useAngVelSmoothing(entity)
|
||||
|
||||
override fun useRidingAltPose(entity: PokemonEntity, driver: Player): Boolean {
|
||||
return getActiveController(entity).useRidingAltPose(entity, driver)
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY
|
||||
(entity: PokemonEntity,
|
||||
driver: Player,
|
||||
|
||||
-2
@@ -84,7 +84,6 @@ class RunUpToJetFlightCompositeController : RideController {
|
||||
val groundState = landController.getState(entity, ::GenericLandState)
|
||||
groundState.currSpeed = flightState.currSpeed
|
||||
groundState.stamina = flightState.stamina
|
||||
groundState.rideVel = flightState.rideVel
|
||||
|
||||
//Clear accumulated rotation input when transferring to ground input
|
||||
flightState.currMouseXForce = 0.0
|
||||
@@ -109,7 +108,6 @@ class RunUpToJetFlightCompositeController : RideController {
|
||||
val groundState = landController.getState(entity, ::GenericLandState)
|
||||
flightState.currSpeed = groundState.currSpeed
|
||||
flightState.stamina = groundState.stamina
|
||||
flightState.rideVel = groundState.rideVel
|
||||
|
||||
getState(entity, ::RunUpToFlightCompositeState).let {
|
||||
it.activeController = flightController
|
||||
|
||||
+1
-3
@@ -14,7 +14,5 @@ import net.minecraft.world.phys.Vec3
|
||||
|
||||
data class BirdAirState(val entity: PokemonEntity) : RidingState {
|
||||
var currSpeed: Double = 0.0
|
||||
var stamina: Float = 1.0f
|
||||
var rideVel: Vec3 = Vec3.ZERO
|
||||
var gliding: Boolean = false
|
||||
var lastVelocity: Vec3 = Vec3(0.0, 0.0, 0.0)
|
||||
}
|
||||
@@ -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?)
|
||||
+101
-278
@@ -124,14 +124,8 @@
|
||||
},
|
||||
"pokeball": {
|
||||
"scale": {
|
||||
"0.4583": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0.3, 0.3, 0.3]
|
||||
},
|
||||
"0.5417": {
|
||||
"pre": [0.3, 0.3, 0.3],
|
||||
"post": [0.3, 0.3, 0.3]
|
||||
},
|
||||
"0.4583": {"pre": [0, 0, 0], "post": [0.3, 0.3, 0.3]},
|
||||
"0.5417": {"pre": [0.3, 0.3, 0.3], "post": [0.3, 0.3, 0.3]},
|
||||
"0.7083": [1, 1, 1]
|
||||
}
|
||||
},
|
||||
@@ -562,6 +556,105 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.trainer_generic.send_out": {
|
||||
"animation_length": 3.625,
|
||||
"bones": {
|
||||
"torso": {
|
||||
"rotation": {
|
||||
"0.0": [1, 0, 0],
|
||||
"0.9375": [1, 0, 0],
|
||||
"1.375": [1, -7.5, 0],
|
||||
"2.875": [1, -7.5, 0],
|
||||
"3.375": [1, 0, 0],
|
||||
"3.625": [1, 0, 0]
|
||||
}
|
||||
},
|
||||
"head": {
|
||||
"rotation": {
|
||||
"0.0": [-1, 0, 0],
|
||||
"0.9375": [-1, 0, 0],
|
||||
"1.375": [-1, 7.5, 0],
|
||||
"2.875": [-1, 7.5, 0],
|
||||
"3.375": [-1, 0, 0],
|
||||
"3.625": [-1, 0, 0]
|
||||
}
|
||||
},
|
||||
"arm_ball": {
|
||||
"rotation": {
|
||||
"0.0": [3, 0, 3.5],
|
||||
"0.5": [3, 0, 3.5],
|
||||
"0.9375": {
|
||||
"pre": [14.5, 2.5, -17.5],
|
||||
"post": [14.5, 2.5, -17.5],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.125": {
|
||||
"post": [5, 2.5, -0.5],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.375": [-91, 7.5, 0],
|
||||
"2.875": [-91, 7.5, 0],
|
||||
"3.1875": {
|
||||
"pre": [5, 2.5, -0.5],
|
||||
"post": [5, 2.5, -0.5],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"3.375": {
|
||||
"post": [14.5, 2.5, -17.5],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"3.625": [3, 0, 3.5]
|
||||
},
|
||||
"position": {
|
||||
"0.0": [0, 0.1, 0],
|
||||
"3.625": [0, 0.1, 0]
|
||||
}
|
||||
},
|
||||
"arm_left": {
|
||||
"rotation": {
|
||||
"0.0": [3, 0, -3.5],
|
||||
"0.9375": [3, 0, -3.5],
|
||||
"1.375": [13, 0, -6],
|
||||
"2.875": [13, 0, -6],
|
||||
"3.375": [3, 0, -3.5]
|
||||
},
|
||||
"position": [0, 0.1, 0]
|
||||
},
|
||||
"pokeball": {
|
||||
"scale": {
|
||||
"0.9375": {"pre": [0, 0, 0], "post": [0.3, 0.3, 0.3]},
|
||||
"1.0": {"pre": [0.3, 0.3, 0.3], "post": [0.3, 0.3, 0.3]},
|
||||
"1.1875": [1, 1, 1],
|
||||
"3.0625": [1, 1, 1],
|
||||
"3.25": [0.4, 0.4, 0.4],
|
||||
"3.375": {"pre": [0.4, 0.4, 0.4], "post": [0, 0, 0]}
|
||||
}
|
||||
},
|
||||
"leg_right": {
|
||||
"rotation": {
|
||||
"0.9375": [0, 8, 0.5],
|
||||
"1.375": [1, 10, 0.5],
|
||||
"2.875": [1, 10, 0.5],
|
||||
"3.375": [0, 8, 0.5]
|
||||
},
|
||||
"position": {
|
||||
"0.9375": [-0.2, 0, 0],
|
||||
"1.375": [-0.2, 0, -0.2],
|
||||
"2.875": [-0.2, 0, -0.2],
|
||||
"3.375": [-0.2, 0, 0]
|
||||
}
|
||||
},
|
||||
"leg_left": {
|
||||
"rotation": {
|
||||
"0.9375": [0, -8, -0.5],
|
||||
"1.375": [0, -10, -0.5],
|
||||
"2.875": [0, -10, -0.5],
|
||||
"3.375": [0, -8, -0.5]
|
||||
},
|
||||
"position": [0.2, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.trainer_generic.command": {
|
||||
"animation_length": 2,
|
||||
"bones": {
|
||||
@@ -903,276 +996,6 @@
|
||||
"scale": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"animation.trainer_generic.return": {
|
||||
"animation_length": 3.625,
|
||||
"bones": {
|
||||
"torso": {
|
||||
"rotation": {
|
||||
"0.0": [1, 0, 0],
|
||||
"0.9375": [1, 0, 0],
|
||||
"1.375": [1, -7.5, 0],
|
||||
"2.875": [1, -7.5, 0],
|
||||
"3.375": [1, 0, 0],
|
||||
"3.625": [1, 0, 0]
|
||||
}
|
||||
},
|
||||
"head": {
|
||||
"rotation": {
|
||||
"0.0": [-1, 0, 0],
|
||||
"0.9375": [-1, 0, 0],
|
||||
"1.375": [-1, 7.5, 0],
|
||||
"2.875": [-1, 7.5, 0],
|
||||
"3.375": [-1, 0, 0],
|
||||
"3.625": [-1, 0, 0]
|
||||
}
|
||||
},
|
||||
"arm_ball": {
|
||||
"rotation": {
|
||||
"0.0": [3, 0, 3.5],
|
||||
"0.5": [3, 0, 3.5],
|
||||
"0.9375": {
|
||||
"pre": [14.5, 2.5, -17.5],
|
||||
"post": [14.5, 2.5, -17.5],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.125": {
|
||||
"post": [5, 2.5, -0.5],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.375": [-91, 7.5, 0],
|
||||
"2.875": [-91, 7.5, 0],
|
||||
"3.1875": {
|
||||
"pre": [5, 2.5, -0.5],
|
||||
"post": [5, 2.5, -0.5],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"3.375": {
|
||||
"post": [14.5, 2.5, -17.5],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"3.625": [3, 0, 3.5]
|
||||
},
|
||||
"position": {
|
||||
"0.0": [0, 0.1, 0],
|
||||
"3.625": [0, 0.1, 0]
|
||||
}
|
||||
},
|
||||
"arm_left": {
|
||||
"rotation": {
|
||||
"0.0": [3, 0, -3.5],
|
||||
"0.9375": [3, 0, -3.5],
|
||||
"1.375": [13, 0, -6],
|
||||
"2.875": [13, 0, -6],
|
||||
"3.375": [3, 0, -3.5]
|
||||
},
|
||||
"position": [0, 0.1, 0]
|
||||
},
|
||||
"pokeball": {
|
||||
"scale": {
|
||||
"0.9375": {
|
||||
"pre": [0, 0, 0],
|
||||
"post": [0.3, 0.3, 0.3]
|
||||
},
|
||||
"1.0": [0.3, 0.3, 0.3],
|
||||
"1.1875": [1, 1, 1],
|
||||
"3.0625": [1, 1, 1],
|
||||
"3.25": [0.4, 0.4, 0.4],
|
||||
"3.375": {
|
||||
"pre": [0.4, 0.4, 0.4],
|
||||
"post": [0, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
"leg_right": {
|
||||
"rotation": {
|
||||
"0.9375": [0, 8, 0.5],
|
||||
"1.375": [1, 10, 0.5],
|
||||
"2.875": [1, 10, 0.5],
|
||||
"3.375": [0, 8, 0.5]
|
||||
},
|
||||
"position": {
|
||||
"0.9375": [-0.2, 0, 0],
|
||||
"1.375": [-0.2, 0, -0.2],
|
||||
"2.875": [-0.2, 0, -0.2],
|
||||
"3.375": [-0.2, 0, 0]
|
||||
}
|
||||
},
|
||||
"leg_left": {
|
||||
"rotation": {
|
||||
"0.9375": [0, -8, -0.5],
|
||||
"1.375": [0, -10, -0.5],
|
||||
"2.875": [0, -10, -0.5],
|
||||
"3.375": [0, -8, -0.5]
|
||||
},
|
||||
"position": [0.2, 0, 0]
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
"1.0": "q.render_item(v.actioning_pokemon_ball, 'item_right');",
|
||||
"3.3125": "q.clear_items();"
|
||||
}
|
||||
},
|
||||
"animation.trainer_generic.send_out": {
|
||||
"animation_length": 2.875,
|
||||
"bones": {
|
||||
"torso_crouch": {
|
||||
"rotation": {
|
||||
"0.9167": [0, 0, 0],
|
||||
"1.4167": {
|
||||
"pre": [0, 10, 0],
|
||||
"post": [0, 10, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.5417": {
|
||||
"post": [0, -10, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.7917": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"arms": {
|
||||
"rotation": {
|
||||
"0.9167": [0, 0, 0],
|
||||
"1.4167": {
|
||||
"pre": [0, 10, 0],
|
||||
"post": [0, 10, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.5417": {
|
||||
"post": [0, -10, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.7917": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"torso_item_right": {
|
||||
"rotation": [0.5, 0, 0]
|
||||
},
|
||||
"arms_item_right": {
|
||||
"rotation": {
|
||||
"0.9167": [0, 0, 0],
|
||||
"1.4167": {
|
||||
"pre": [0, 10, 0],
|
||||
"post": [0, 10, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.5417": {
|
||||
"post": [0, -10, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.7917": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"arm_item_right": {
|
||||
"rotation": {
|
||||
"0.9167": [1, 0, 2.5],
|
||||
"1.4167": {
|
||||
"pre": [-85, 10, -10],
|
||||
"post": [-85, 10, -10],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.5417": {
|
||||
"post": [-72.5, -30, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.7917": [1, 0, 2.5]
|
||||
},
|
||||
"position": [0, 0.1, 0]
|
||||
},
|
||||
"torso_item_left": {
|
||||
"rotation": [0.5, 0, 0]
|
||||
},
|
||||
"arms_item_left": {
|
||||
"rotation": {
|
||||
"0.9167": [0, 0, 0],
|
||||
"1.4167": {
|
||||
"pre": [0, 10, 0],
|
||||
"post": [0, 10, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.5417": {
|
||||
"post": [0, -10, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.7917": [0, 0, 0]
|
||||
}
|
||||
},
|
||||
"arm_item_left": {
|
||||
"rotation": {
|
||||
"0.9167": [1, 0, -2.5],
|
||||
"1.4167": {
|
||||
"pre": [4, 0, 0],
|
||||
"post": [4, 0, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.5417": {
|
||||
"post": [-8, 0, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.7917": [1, 0, -2.5]
|
||||
},
|
||||
"position": [0, 0.1, 0]
|
||||
},
|
||||
"torso": {
|
||||
"rotation": [0.5, 0, 0]
|
||||
},
|
||||
"head": {
|
||||
"rotation": [-0.5, 0, 0]
|
||||
},
|
||||
"arm_right": {
|
||||
"rotation": {
|
||||
"0.9167": [1, 0, 2.5],
|
||||
"1.4167": {
|
||||
"pre": [-85, 10, -10],
|
||||
"post": [-85, 10, -10],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.5417": {
|
||||
"post": [-72.5, -30, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.7917": [1, 0, 2.5]
|
||||
},
|
||||
"position": [0, 0.1, 0]
|
||||
},
|
||||
"arm_left": {
|
||||
"rotation": {
|
||||
"0.9167": [1, 0, -2.5],
|
||||
"1.4167": {
|
||||
"pre": [4, 0, 0],
|
||||
"post": [4, 0, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.5417": {
|
||||
"post": [-8, 0, 0],
|
||||
"lerp_mode": "catmullrom"
|
||||
},
|
||||
"1.7917": [1, 0, -2.5]
|
||||
},
|
||||
"position": [0, 0.1, 0]
|
||||
},
|
||||
"leg_right": {
|
||||
"rotation": [0, 2, 0.5],
|
||||
"position": [-0.05, 0, 0]
|
||||
},
|
||||
"leg_left": {
|
||||
"rotation": [0, -2, -0.5],
|
||||
"position": [0.05, 0, 0]
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
"0.0": "q.render_item(v.actioning_pokemon_ball, 'item_right');",
|
||||
"1.5833": "q.render_item('air', 'item_right');"
|
||||
}
|
||||
}
|
||||
},
|
||||
"geometry.animations": {
|
||||
"texturewidth": 64,
|
||||
"textureheight": 64
|
||||
},
|
||||
"geometry.unknown": {
|
||||
"texturewidth": 16,
|
||||
"textureheight": 16
|
||||
}
|
||||
}
|
||||
@@ -81,14 +81,6 @@
|
||||
{"origin": [-7, 12, -2], "size": [3, 12, 4], "inflate": 0.25, "uv": [40, 32]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "item_right",
|
||||
"parent": "arm_right",
|
||||
"pivot": [-5.5, 12, -2],
|
||||
"locators": {
|
||||
"item_right": [-5.5, 12, -2]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pokeball",
|
||||
"parent": "arm_ball",
|
||||
@@ -107,14 +99,6 @@
|
||||
{"origin": [4, 12, -2], "size": [3, 12, 4], "inflate": 0.25, "uv": [48, 48]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "item_left",
|
||||
"parent": "arm_left",
|
||||
"pivot": [5.5, 12, -2],
|
||||
"locators": {
|
||||
"item_left": [5.5, 12, -2]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "leg_right",
|
||||
"parent": "body",
|
||||
|
||||
@@ -97,14 +97,6 @@
|
||||
{"origin": [-7, 12, -2], "size": [3, 11, 4], "inflate": 0.25, "uv": [40, 31]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "item_right",
|
||||
"parent": "arm_right",
|
||||
"pivot": [-5.5, 12, -2],
|
||||
"locators": {
|
||||
"item_right": [-5.5, 12, -2]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "arm_left",
|
||||
"parent": "torso",
|
||||
@@ -114,14 +106,6 @@
|
||||
{"origin": [4, 12, -2], "size": [3, 11, 4], "inflate": 0.25, "uv": [46, 48]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "item_left",
|
||||
"parent": "arm_left",
|
||||
"pivot": [5.5, 12, -2],
|
||||
"locators": {
|
||||
"item_left": [5.5, 12, -2]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "leg_right",
|
||||
"parent": "body",
|
||||
|
||||
@@ -81,14 +81,6 @@
|
||||
{"origin": [-8, 12, -2], "size": [4, 12, 4], "inflate": 0.25, "uv": [40, 32]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "item_right",
|
||||
"parent": "arm_right",
|
||||
"pivot": [-6, 12, -2],
|
||||
"locators": {
|
||||
"item_right": [-6, 12, -2]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pokeball",
|
||||
"parent": "arm_ball",
|
||||
@@ -107,14 +99,6 @@
|
||||
{"origin": [4, 12, -2], "size": [4, 12, 4], "inflate": 0.25, "uv": [48, 48]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "item_left",
|
||||
"parent": "arm_left",
|
||||
"pivot": [6, 12, -2],
|
||||
"locators": {
|
||||
"item_left": [6, 12, -2]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "leg_right",
|
||||
"parent": "body",
|
||||
|
||||
@@ -79,18 +79,29 @@
|
||||
"cubes": [
|
||||
{"origin": [-8, 12, -2], "size": [4, 12, 4], "uv": [40, 16]},
|
||||
{"origin": [-8, 12, -2], "size": [4, 12, 4], "inflate": 0.25, "uv": [40, 32]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pokeball",
|
||||
"parent": "arm_ball",
|
||||
"pivot": [-6, 12, 0],
|
||||
"cubes": [
|
||||
{"origin": [-8, 8, -2], "size": [4, 2, 4], "pivot": [-6, 10, 0], "rotation": [90, 0, 0], "uv": [0, 122]}
|
||||
],
|
||||
"locators": {
|
||||
"beam": [-6, 8, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "item_right",
|
||||
"parent": "arm_right",
|
||||
"pivot": [-6, 12, -2],
|
||||
"locators": {
|
||||
"item_right": [-6, 12, -2]
|
||||
}
|
||||
"name": "pokeball_lid",
|
||||
"parent": "pokeball",
|
||||
"pivot": [-6, 10, 0],
|
||||
"rotation": [90, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-6.5, 9.5, -2.02], "size": [1, 1, 0], "uv": [0, 123]},
|
||||
{"origin": [-7, 9, -2.01], "size": [2, 2, 0], "uv": [0, 124]},
|
||||
{"origin": [-8, 10, -2], "size": [4, 2, 4], "uv": [0, 116]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arm_left",
|
||||
@@ -101,14 +112,6 @@
|
||||
{"origin": [4, 12, -2], "size": [4, 12, 4], "inflate": 0.25, "uv": [48, 48]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "item_left",
|
||||
"parent": "arm_left",
|
||||
"pivot": [6, 12, -2],
|
||||
"locators": {
|
||||
"item_left": [6, 12, -2]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "leg_right",
|
||||
"parent": "body",
|
||||
|
||||
@@ -3,16 +3,11 @@
|
||||
"portraitTranslation": [0.0, 1.1, 0.0],
|
||||
"profileScale": 1.0,
|
||||
"profileTranslation": [0.0, -0.5, 0.0],
|
||||
"properties": {
|
||||
"item_display_context": "THIRD_PERSON_RIGHT_HAND",
|
||||
"item_right_display_context": "THIRD_PERSON_RIGHT_HAND",
|
||||
"item_left_display_context": "THIRD_PERSON_LEFT_HAND"
|
||||
},
|
||||
"animations": {
|
||||
"win": "q.bedrock_primary('trainer_generic', 'win', q.curve('symmetrical_wide'))",
|
||||
"lose": "q.bedrock_primary('trainer_generic', 'lose', q.curve('symmetrical_wide'))",
|
||||
"send_out": "q.bedrock_primary('trainer_generic', 'send_out', q.curve('symmetrical'), q.exclude_labels('look'))",
|
||||
"recall": "q.bedrock_primary('trainer_generic', 'return', q.curve('symmetrical_wide'), q.exclude_labels('look'))",
|
||||
"recall": "q.bedrock_primary('trainer_generic', 'send_out', q.curve('symmetrical_wide'), q.exclude_labels('look'))",
|
||||
"punch_left": "q.primary_animation(q.punch('head', 'body', 'arm_left', 'arm_right', false), q.curve('symmetrical_wide'), q.exclude_labels('look'))",
|
||||
"punch_right": "q.primary_animation(q.punch('head', 'body', 'arm_right', 'arm_left', true), q.curve('symmetrical_wide'), q.exclude_labels('look'))"
|
||||
},
|
||||
|
||||
+1
-1
@@ -14543,7 +14543,7 @@
|
||||
"rotation": [0, -6, -15]
|
||||
},
|
||||
"wing_right_elbow": {
|
||||
"rotation": [0, "-6", -15],
|
||||
"rotation": [0, -6, -15],
|
||||
"position": [0.45, 0.1, 0]
|
||||
},
|
||||
"wing_right_membrane": {
|
||||
|
||||
+113
-146
@@ -20,7 +20,7 @@
|
||||
"parent": "charizard",
|
||||
"pivot": [0, 0, -1],
|
||||
"locators": {
|
||||
"root": [0, 0, 0]
|
||||
"root": [0, 0, -1]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -28,7 +28,7 @@
|
||||
"parent": "charizard",
|
||||
"pivot": [0, 41, -1],
|
||||
"locators": {
|
||||
"top": [0, 41, 0]
|
||||
"top": [0, 41, -1]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -116,7 +116,8 @@
|
||||
{
|
||||
"name": "head_correction",
|
||||
"parent": "head",
|
||||
"pivot": [0, 43.5, -0.5]
|
||||
"pivot": [0, 43.5, -0.5],
|
||||
"rotation": [-95, 0, 0]
|
||||
},
|
||||
{
|
||||
"name": "head_ai",
|
||||
@@ -127,20 +128,15 @@
|
||||
"name": "head_angle",
|
||||
"parent": "head_ai",
|
||||
"pivot": [0, 43.5, -0.5],
|
||||
"rotation": [95, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 41.5, -2], "size": [7, 7, 5], "uv": [48, 0]},
|
||||
{"origin": [-3.5, 41.5, -4], "size": [7, 4, 2], "uv": [95, 64]}
|
||||
{"origin": [-3.5, 41.5, -4], "size": [7, 4, 2], "uv": [95, 64]},
|
||||
{"origin": [-2.5, 48, -2], "size": [5, 5, 2], "inflate": 0.02, "uv": [37, 31]},
|
||||
{"origin": [-2, 51.48744, -3.03033], "size": [0, 1, 1], "inflate": 0.01, "pivot": [-2, 52.51777, -2], "rotation": [45, 0, 15], "uv": [43, 42]},
|
||||
{"origin": [2, 51.48744, -3.03033], "size": [0, 1, 1], "inflate": 0.01, "pivot": [2, 52.51777, -2], "rotation": [45, 0, -15], "uv": [41, 42]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "item_hat",
|
||||
"parent": "head_angle",
|
||||
"pivot": [0, 45, 3],
|
||||
"rotation": [-90, 0, 0],
|
||||
"locators": {
|
||||
"item_hat": [0, 45, 3]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_head",
|
||||
"parent": "head_angle",
|
||||
@@ -150,51 +146,59 @@
|
||||
"head": [0, 46.5, 3]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_face",
|
||||
"parent": "head_angle",
|
||||
"pivot": [0, 53.75, -0.25],
|
||||
"rotation": [-90, 0, 0],
|
||||
"locators": {
|
||||
"face": [0, 53.75, -0.25]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_item_face",
|
||||
"parent": "head_angle",
|
||||
"pivot": [0, 48.5, 0.5],
|
||||
"rotation": [-90, 0, 0],
|
||||
"locators": {
|
||||
"item_face": [0, 48.5, 0.5]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_mouth",
|
||||
"parent": "head_angle",
|
||||
"pivot": [0, 45.75, -2],
|
||||
"pivot": [0, 48.75, -3],
|
||||
"rotation": [-90, 0, 0],
|
||||
"locators": {
|
||||
"mouth": [0, 45.75, -2],
|
||||
"special": [0, 45.75, -2]
|
||||
"mouth": [0, 48.75, -3]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "eyes",
|
||||
"name": "locator_special_attack",
|
||||
"parent": "head_angle",
|
||||
"pivot": [0, -3.5, 1.5]
|
||||
"pivot": [0, 48.75, -3],
|
||||
"rotation": [-90, 0, 0],
|
||||
"locators": {
|
||||
"special_attack": [0, 48.75, -3]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_jaw_upper",
|
||||
"parent": "head_angle",
|
||||
"pivot": [0, 51.75, -2.25],
|
||||
"rotation": [-90, 0, 0],
|
||||
"locators": {
|
||||
"jaw1": [0, 51.75, -2.25]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "brow",
|
||||
"parent": "eyes",
|
||||
"parent": "head_angle",
|
||||
"pivot": [0, 48.275, 1.765],
|
||||
"cubes": [
|
||||
{"origin": [-2.5, 48.55, 1], "size": [5, 0, 1], "uv": [0, 14]},
|
||||
{"origin": [-3.5, 47.5, 1.53], "size": [7, 1, 1], "inflate": 0.04, "uv": [0, 12]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "eye",
|
||||
"parent": "head_angle",
|
||||
"pivot": [0, -3.5, 1.5]
|
||||
},
|
||||
{
|
||||
"name": "locator_face",
|
||||
"parent": "eye",
|
||||
"pivot": [0, 53.75, -0.25],
|
||||
"rotation": [-90, 0, 0],
|
||||
"locators": {
|
||||
"face": [0, 53.75, -0.25]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "eye_right",
|
||||
"parent": "eyes",
|
||||
"parent": "eye",
|
||||
"pivot": [-2.75, 47.75, 0.875],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 47.5, 0], "size": [2, 1, 2], "inflate": 0.01, "uv": [106, 36]},
|
||||
@@ -222,7 +226,7 @@
|
||||
},
|
||||
{
|
||||
"name": "eye_left",
|
||||
"parent": "eyes",
|
||||
"parent": "eye",
|
||||
"pivot": [2.75, 47.75, 0.875],
|
||||
"cubes": [
|
||||
{"origin": [1.5, 47.5, 0], "size": [2, 1, 2], "inflate": 0.01, "uv": [44, 82]},
|
||||
@@ -251,91 +255,6 @@
|
||||
{"origin": [2.41, 46.4, 0.5], "size": [1, 1, 1], "inflate": 0.03, "uv": [39, 86]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "muzzle",
|
||||
"parent": "head_angle",
|
||||
"pivot": [-3, 48.5, -2],
|
||||
"cubes": [
|
||||
{"origin": [-2.5, 48, -2], "size": [5, 5, 2], "inflate": 0.02, "uv": [37, 31]},
|
||||
{"origin": [-2, 51.48744, -3.03033], "size": [0, 1, 1], "inflate": 0.01, "pivot": [-2, 52.51777, -2], "rotation": [45, 0, 15], "uv": [43, 42]},
|
||||
{"origin": [2, 51.48744, -3.03033], "size": [0, 1, 1], "inflate": 0.01, "pivot": [2, 52.51777, -2], "rotation": [45, 0, -15], "uv": [41, 42]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_jaw_upper",
|
||||
"parent": "muzzle",
|
||||
"pivot": [0, 51.75, -2.25],
|
||||
"rotation": [-90, 0, 0],
|
||||
"locators": {
|
||||
"jaw1": [0, 51.75, -2.25]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "nose",
|
||||
"parent": "muzzle",
|
||||
"pivot": [0, 53, 0],
|
||||
"rotation": [-40, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2.5, 51.36698, -1.67861], "size": [5, 2, 2], "inflate": 0.03, "uv": [48, 12]},
|
||||
{"origin": [-2, 53.40698, -1.17861], "size": [4, 0, 1], "uv": [0, 15]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jaw",
|
||||
"parent": "head_angle",
|
||||
"pivot": [0, 45.5, -2],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 45.5, -4], "size": [7, 3, 2], "inflate": 0.01, "uv": [76, 20]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jaw2",
|
||||
"parent": "jaw",
|
||||
"pivot": [0, 48.5, -4],
|
||||
"rotation": [-7.5, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2, 48.49, -4], "size": [4, 4, 2], "uv": [0, 51]},
|
||||
{"origin": [-1.5, 48.5, -2], "size": [3, 4, 0], "uv": [90, 0]},
|
||||
{"origin": [1.5, 51.49749, -3.03033], "size": [0, 1, 1], "pivot": [1.5, 52.52782, -2], "rotation": [45, 0, -7.5], "uv": [37, 39]},
|
||||
{"origin": [-1.5, 51.49749, -3.03033], "size": [0, 1, 1], "pivot": [-1.5, 52.52782, -2], "rotation": [45, 0, 7.5], "uv": [37, 38]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_jaw_lower",
|
||||
"parent": "jaw2",
|
||||
"pivot": [0, 51.75, -1.75],
|
||||
"locators": {
|
||||
"jaw2": {
|
||||
"offset": [0, 51.75, -1.75],
|
||||
"rotation": [-90, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tongue",
|
||||
"parent": "jaw",
|
||||
"pivot": [0, 45.5, -2],
|
||||
"cubes": [
|
||||
{"origin": [-1.5, 45.5, -2], "size": [3, 3, 0], "uv": [87, 29]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tongue2",
|
||||
"parent": "tongue",
|
||||
"pivot": [0, 48.5, -2],
|
||||
"cubes": [
|
||||
{"origin": [-1, 48.5, -2], "size": [2, 2, 0], "inflate": 0.01, "uv": [46, 38]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tongue3",
|
||||
"parent": "tongue2",
|
||||
"pivot": [0, 50.5, -2],
|
||||
"cubes": [
|
||||
{"origin": [-1, 50.5, -2], "size": [2, 1, 0], "uv": [37, 43]},
|
||||
{"origin": [-0.5, 51.5, -2], "size": [1, 1, 0], "uv": [45, 43]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "horns",
|
||||
"parent": "head_angle",
|
||||
@@ -381,6 +300,72 @@
|
||||
"horn2": [2.5, 39, 2]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "nose",
|
||||
"parent": "head_angle",
|
||||
"pivot": [0, 53, 0],
|
||||
"rotation": [-40, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2.5, 51.36698, -1.67861], "size": [5, 2, 2], "inflate": 0.03, "uv": [48, 12]},
|
||||
{"origin": [-2, 53.40698, -1.17861], "size": [4, 0, 1], "uv": [0, 15]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jaw",
|
||||
"parent": "head_angle",
|
||||
"pivot": [0, 45.5, -2],
|
||||
"cubes": [
|
||||
{"origin": [-3.5, 45.5, -4], "size": [7, 3, 2], "inflate": 0.01, "uv": [76, 20]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "jaw2",
|
||||
"parent": "jaw",
|
||||
"pivot": [0, 48.5, -4],
|
||||
"rotation": [-7.5, 0, 0],
|
||||
"cubes": [
|
||||
{"origin": [-2, 48.49, -4], "size": [4, 4, 2], "uv": [0, 51]},
|
||||
{"origin": [-1.5, 48.5, -1.99], "size": [3, 4, 0], "uv": [90, 0]},
|
||||
{"origin": [1.5, 51.49749, -3.03033], "size": [0, 1, 1], "pivot": [1.5, 52.52782, -2], "rotation": [45, 0, -7.5], "uv": [37, 39]},
|
||||
{"origin": [-1.5, 51.49749, -3.03033], "size": [0, 1, 1], "pivot": [-1.5, 52.52782, -2], "rotation": [45, 0, 7.5], "uv": [37, 38]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_jaw_lower",
|
||||
"parent": "jaw2",
|
||||
"pivot": [0, 51.75, -1.75],
|
||||
"locators": {
|
||||
"jaw2": {
|
||||
"offset": [0, 51.75, -1.75],
|
||||
"rotation": [-90, 0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tongue",
|
||||
"parent": "jaw",
|
||||
"pivot": [0, 45.5, -2],
|
||||
"cubes": [
|
||||
{"origin": [-1.5, 45.5, -2], "size": [3, 3, 0], "uv": [87, 29]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tongue2",
|
||||
"parent": "tongue",
|
||||
"pivot": [0, 48.5, -2],
|
||||
"cubes": [
|
||||
{"origin": [-1, 48.5, -2], "size": [2, 2, 0], "inflate": 0.01, "uv": [46, 38]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tongue3",
|
||||
"parent": "tongue2",
|
||||
"pivot": [0, 50.5, -2],
|
||||
"cubes": [
|
||||
{"origin": [-1, 50.5, -2], "size": [2, 1, 0], "uv": [37, 43]},
|
||||
{"origin": [-0.5, 51.5, -2], "size": [1, 1, 0], "uv": [45, 43]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arms",
|
||||
"parent": "torso2",
|
||||
@@ -418,14 +403,6 @@
|
||||
{"origin": [-21.5, 22, -1.5], "size": [3, 2, 3], "inflate": 0.1, "uv": [0, 58]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_item",
|
||||
"parent": "hand_right",
|
||||
"pivot": [-21.6, 23, 0],
|
||||
"locators": {
|
||||
"item": [-21.6, 23, 0]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "locator_hand_primary",
|
||||
"parent": "hand_right",
|
||||
@@ -936,14 +913,6 @@
|
||||
{"origin": [0, 9.5, 58.5], "size": [0, 13, 7], "pivot": [0, 14.45, 62], "rotation": [0, 90, 0], "uv": [0, 92]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "locator_fire",
|
||||
"parent": "fire2",
|
||||
"pivot": [0, 11, 62],
|
||||
"locators": {
|
||||
"fire": [0, 11, 62]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "legs",
|
||||
"parent": "body",
|
||||
@@ -976,10 +945,9 @@
|
||||
{
|
||||
"name": "locator_foot_primary",
|
||||
"parent": "foot_right",
|
||||
"pivot": [-9, 1.5, -5],
|
||||
"rotation": [90, 0, 0],
|
||||
"pivot": [-9, 1.5, -7],
|
||||
"locators": {
|
||||
"foot_primary": [-9, 1.5, -5]
|
||||
"foot_primary": [-9, 1.5, -7]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -1041,10 +1009,9 @@
|
||||
{
|
||||
"name": "locator_foot_secondary",
|
||||
"parent": "foot_left",
|
||||
"pivot": [9, 1.5, -5],
|
||||
"rotation": [90, 0, 0],
|
||||
"pivot": [9, 1.5, -7],
|
||||
"locators": {
|
||||
"foot_secondary": [9, 1.5, -5]
|
||||
"foot_secondary": [9, 1.5, -7]
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
-10
@@ -86,22 +86,12 @@
|
||||
"fly": {
|
||||
"poseTypes": ["FLY"],
|
||||
"isBattle": false,
|
||||
"condition": "!q.is_gliding",
|
||||
"animations": ["q.look('head_ai')", "q.bedrock('charizard', 'air_fly')"],
|
||||
"quirks": [
|
||||
"q.bedrock_quirk('charizard', 'blink')",
|
||||
"q.bedrock_quirk('charizard', 'air_fly_quirk')"
|
||||
]
|
||||
},
|
||||
"glide": {
|
||||
"poseTypes": ["FLY"],
|
||||
"isBattle": false,
|
||||
"condition": "q.is_gliding",
|
||||
"animations": ["q.look('head_ai')", "q.bedrock('charizard', 'air_glide_TEST')"],
|
||||
"quirks": [
|
||||
"q.bedrock_quirk('charizard', 'blink')"
|
||||
]
|
||||
},
|
||||
"surface_idle": {
|
||||
"poseTypes": ["STAND", "FLOAT"],
|
||||
"isTouchingWater": true,
|
||||
|
||||
@@ -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",
|
||||
@@ -7212,4 +7211,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": [
|
||||
@@ -97,12 +97,12 @@
|
||||
"party": {
|
||||
"type": "simple",
|
||||
"pokemon": [
|
||||
"spiritomb level=100 pokeball=master_ball held_item=cobblemon:sitrus_berry ability=pressure nature=jolly moves=darkpulse,shadowball,willowisp,suckerpunch hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=252 special_attack_ev=252 speed_ev=6",
|
||||
"porygonz level=100 pokeball=great_ball held_item=cobblemon:life_orb ability=adaptability nature=timid moves=hyperbeam,shadowball,icebeam,thunderbolt hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=6 special_attack_ev=252 speed_ev=252",
|
||||
"togekiss level=100 pokeball=ultra_ball held_item=cobblemon:leftovers ability=serenegrace nature=timid moves=airslash,dazzlinggleam,aurasphere,thunderwave hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=6 special_attack_ev=252 speed_ev=252",
|
||||
"lucario level=100 pokeball=dive_ball held_item=cobblemon:focus_sash ability=innerfocus nature=jolly moves=closecombat,meteormash,earthquake,extremespeed 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",
|
||||
"milotic level=100 pokeball=love_ball held_item=cobblemon:flame_orb ability=marvelscale nature=bold moves=recover,mirrorcoat,icebeam,scald hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=252 defence_ev=252 special_attack_ev=6",
|
||||
"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"
|
||||
"spiritomb level=100 held_item=cobblemon:sitrus_berry ability=pressure nature=jolly moves=darkpulse,shadowball,willowisp,suckerpunch hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=252 special_attack_ev=252 speed_ev=6",
|
||||
"porygonz level=100 held_item=cobblemon:life_orb ability=adaptability nature=timid moves=hyperbeam,shadowball,icebeam,thunderbolt hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=6 special_attack_ev=252 speed_ev=252",
|
||||
"togekiss level=100 held_item=cobblemon:leftovers ability=serenegrace nature=timid moves=airslash,dazzlinggleam,aurasphere,thunderwave hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=6 special_attack_ev=252 speed_ev=252",
|
||||
"lucario level=100 held_item=cobblemon:focus_sash ability=innerfocus nature=jolly moves=closecombat,meteormash,earthquake,extremespeed 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",
|
||||
"milotic level=100 held_item=cobblemon:flame_orb ability=marvelscale nature=bold moves=recover,mirrorcoat,icebeam,scald hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=252 defence_ev=252 special_attack_ev=6",
|
||||
"garchomp level=100 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