forked from mirrors/cobblemon
Compare commits
360
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c77963dec0 | ||
|
|
515986e7bd | ||
|
|
245f569c8a | ||
|
|
b7d8d85b1c | ||
|
|
66ffb21bbd | ||
|
|
9e4720b233 | ||
|
|
426cd50bb1 | ||
|
|
0d33618e83 | ||
|
|
0803906f30 | ||
|
|
2c26046a3c | ||
|
|
bdab3d04cb | ||
|
|
59488974d6 | ||
|
|
3f0561456f | ||
|
|
5279767829 | ||
|
|
e3f8aacd9e | ||
|
|
d381dd0d21 | ||
|
|
2dad3c3197 | ||
|
|
ea8e70d0fa | ||
|
|
64717be354 | ||
|
|
bc9314b9d9 | ||
|
|
541f2dac40 | ||
|
|
adba69c675 | ||
|
|
ab831a2d9d | ||
|
|
faa939986a | ||
|
|
b651bf1ded | ||
|
|
8eb77cfc97 | ||
|
|
e54b22409a | ||
|
|
e03a3abe1d | ||
|
|
497f832995 | ||
|
|
8ee0d904ed | ||
|
|
291196eda7 | ||
|
|
b94e6d65b9 | ||
|
|
60fdd3325e | ||
|
|
eedcdafcbe | ||
|
|
34595e01a9 | ||
|
|
60b9ca7b7f | ||
|
|
83aea117c6 | ||
|
|
78b348329c | ||
|
|
b64013c995 | ||
|
|
85003c9829 | ||
|
|
d9133e854a | ||
|
|
05afe191e6 | ||
|
|
e3b5067f31 | ||
|
|
bfa2d470f0 | ||
|
|
53c0b28deb | ||
|
|
5e9208d898 | ||
|
|
23a91ec2c9 | ||
|
|
9998a328e3 | ||
|
|
9f07747cac | ||
|
|
a7998bcec5 | ||
|
|
5983b29e9c | ||
|
|
749f441913 | ||
|
|
dabbcceb9c | ||
|
|
11f8313fc3 | ||
|
|
32b813620b | ||
|
|
8147067b2e | ||
|
|
f2d2ceb422 | ||
|
|
547ceae9ed | ||
|
|
24c6544e83 | ||
|
|
a921d55812 | ||
|
|
b422ee4d29 | ||
|
|
a83867e153 | ||
|
|
523973d2c0 | ||
|
|
746160fa18 | ||
|
|
837ba9679b | ||
|
|
db7a319441 | ||
|
|
e284032c7e | ||
|
|
3db9dd02f2 | ||
|
|
e680ec8790 | ||
|
|
dc4f831e02 | ||
|
|
b3a00c40b5 | ||
|
|
f56fb0f50a | ||
|
|
ec732bc03d | ||
|
|
229abd4a13 | ||
|
|
7e92c17178 | ||
|
|
77aa3208a0 | ||
|
|
79688ebdad | ||
|
|
16d8c8bb19 | ||
|
|
e5738fecb0 | ||
|
|
a46ac372a8 | ||
|
|
b2ea715195 | ||
|
|
e6d2430405 | ||
|
|
6c425a43c6 | ||
|
|
8b6c8d27b7 | ||
|
|
a505bad554 | ||
|
|
e27e5a1d92 | ||
|
|
5e0357101d | ||
|
|
5420f7419f | ||
|
|
43ed2d3c7d | ||
|
|
93500f59ed | ||
|
|
08912fd53f | ||
|
|
92c8fc0afc | ||
|
|
fae6bdb782 | ||
|
|
bba0100542 | ||
|
|
f9b66de742 | ||
|
|
3d0f1c826a | ||
|
|
2a59add285 | ||
|
|
35e4aafe99 | ||
|
|
0b83128aa0 | ||
|
|
e2b04ff770 | ||
|
|
4dabe50d43 | ||
|
|
3dd0795b88 | ||
|
|
70f1a8207f | ||
|
|
3ac18cce6e | ||
|
|
aa0a0672d0 | ||
|
|
ec2958400b | ||
|
|
fcb6bea78b | ||
|
|
5a76a39b77 | ||
|
|
b5a4acfb7a | ||
|
|
1248230e67 | ||
|
|
096db6cdcd | ||
|
|
72e416b9c7 | ||
|
|
9e8e80965b | ||
|
|
2325b436b1 | ||
|
|
57421a2884 | ||
|
|
4fdd3eba8e | ||
|
|
e6efe800c0 | ||
|
|
95aa99e18c | ||
|
|
270484e745 | ||
|
|
719addf633 | ||
|
|
31e386871f | ||
|
|
bfd7e596d0 | ||
|
|
eab6fa9b40 | ||
|
|
643545d54c | ||
|
|
fcb017f980 | ||
|
|
dd52da7c42 | ||
|
|
ff45def0e8 | ||
|
|
f078584e56 | ||
|
|
a11382b5a1 | ||
|
|
d1b8094539 | ||
|
|
b8d79af911 | ||
|
|
98de423f91 | ||
|
|
ef4fbecad6 | ||
|
|
96075ed34a | ||
|
|
e7cd9237f6 | ||
|
|
84e22c68c7 | ||
|
|
300f2fc53c | ||
|
|
2dfb88467b | ||
|
|
589aab3b1a | ||
|
|
e17ca3a422 | ||
|
|
0153846c7b | ||
|
|
155b4b2182 | ||
|
|
690ed845b2 | ||
|
|
f598f8d645 | ||
|
|
8e3e047646 | ||
|
|
551858df57 | ||
|
|
a7e3f4a7a1 | ||
|
|
94d64bb5c6 | ||
|
|
e569acc648 | ||
|
|
7c374b6e29 | ||
|
|
3c128d9009 | ||
|
|
f5321a34c3 | ||
|
|
2754bfc525 | ||
|
|
3d1aecce13 | ||
|
|
a7198fd324 | ||
|
|
bc69ce91f3 | ||
|
|
1de1d8cdc3 | ||
|
|
38cd081ad6 | ||
|
|
54a14ab805 | ||
|
|
633b45287c | ||
|
|
3619f42090 | ||
|
|
695453ffdb | ||
|
|
1f8171dd94 | ||
|
|
087bf75500 | ||
|
|
bac435ba7b | ||
|
|
74a6106eb6 | ||
|
|
5ec993ad3c | ||
|
|
73a03d90da | ||
|
|
3c2bf0e2b3 | ||
|
|
ac695b1092 | ||
|
|
dd7066e8fa | ||
|
|
416f003927 | ||
|
|
3be3dc3de2 | ||
|
|
246d00aa3f | ||
|
|
a8f273737d | ||
|
|
a185805b3f | ||
|
|
defab6eaff | ||
|
|
b3ed47fbca | ||
|
|
72fc1f1fb9 | ||
|
|
f96c69ab54 | ||
|
|
3a6ad70b76 | ||
|
|
d51039dbb8 | ||
|
|
8120270bf8 | ||
|
|
70fe1682b0 | ||
|
|
9058bde326 | ||
|
|
bf425efd40 | ||
|
|
cebff87656 | ||
|
|
d24fb13877 | ||
|
|
1d82df464a | ||
|
|
f450d9d2d6 | ||
|
|
0752ab3cac | ||
|
|
2a1d1e4474 | ||
|
|
fe35a63769 | ||
|
|
17cab186d9 | ||
|
|
e00136052d | ||
|
|
b23237ca9a | ||
|
|
95d238ada0 | ||
|
|
36456bfb0d | ||
|
|
d9661c356f | ||
|
|
a66fad69cf | ||
|
|
95488ac54b | ||
|
|
19f2b71273 | ||
|
|
ba6e136a0b | ||
|
|
28ac437987 | ||
|
|
a4935f19bb | ||
|
|
c3e470f1de | ||
|
|
4139337f85 | ||
|
|
25bc2dea93 | ||
|
|
9dc3884ea6 | ||
|
|
89d95c6ab8 | ||
|
|
7b70aa5a1e | ||
|
|
8191b4d9b6 | ||
|
|
84534fd041 | ||
|
|
e6e1446eb1 | ||
|
|
39fbadc4fb | ||
|
|
3c2a9c1dfd | ||
|
|
921fcbe477 | ||
|
|
c90b6c86ab | ||
|
|
3ecf25502b | ||
|
|
b808112e17 | ||
|
|
1b2d31db03 | ||
|
|
c9f8445d14 | ||
|
|
acd879ebef | ||
|
|
f6889fd8ea | ||
|
|
21ccbef6c9 | ||
|
|
9349e6a668 | ||
|
|
28a7fe6a04 | ||
|
|
18e1ed79ef | ||
|
|
aafdb7688a | ||
|
|
73b53f9702 | ||
|
|
33317a57c7 | ||
|
|
72d8f26cdb | ||
|
|
aea1afe145 | ||
|
|
0fec45b7ea | ||
|
|
af98b823aa | ||
|
|
07d9da2ca9 | ||
|
|
c2c86b2ef1 | ||
|
|
22733e7117 | ||
|
|
117afcea32 | ||
|
|
3f89750ee5 | ||
|
|
d88be7ae05 | ||
|
|
24245331db | ||
|
|
d6d10d33e3 | ||
|
|
4d88c9b383 | ||
|
|
a3f597c589 | ||
|
|
2a3d88c12e | ||
|
|
9561f47337 | ||
|
|
f02c92db22 | ||
|
|
181ef5d50e | ||
|
|
870397ebb9 | ||
|
|
0ed413070d | ||
|
|
f66f5ffdf9 | ||
|
|
ca280a7b3e | ||
|
|
85284bff9d | ||
|
|
fa4d33f725 | ||
|
|
4ce42cdf6d | ||
|
|
1b8562ead8 | ||
|
|
b9d9b906fb | ||
|
|
e9d079a223 | ||
|
|
e2e42ee91c | ||
|
|
4b0208201b | ||
|
|
bec3f97733 | ||
|
|
16cfe37dac | ||
|
|
7381da863c | ||
|
|
b0902825fa | ||
|
|
31c489cba0 | ||
|
|
6a2f21d75a | ||
|
|
e2c42a57c7 | ||
|
|
8592b53216 | ||
|
|
49dcf0049f | ||
|
|
076bee923f | ||
|
|
aacdb33827 | ||
|
|
53e9ece930 | ||
|
|
87f08d9bab | ||
|
|
a50a6326af | ||
|
|
3c0eec833e | ||
|
|
5b0cfdffcb | ||
|
|
d788e0402e | ||
|
|
a347dc5e2a | ||
|
|
b2fe158a7b | ||
|
|
faab7e8df0 | ||
|
|
9070e198f1 | ||
|
|
45b2a0db4d | ||
|
|
65d39050ea | ||
|
|
5da16df5ae | ||
|
|
24fc4b05ab | ||
|
|
173902feb1 | ||
|
|
fd5f08fe84 | ||
|
|
d430ad6ca1 | ||
|
|
1309c18292 | ||
|
|
b6deebac97 | ||
|
|
83711f2dcb | ||
|
|
18e1a767af | ||
|
|
6ae3efb461 | ||
|
|
84e1934134 | ||
|
|
9aed9342ee | ||
|
|
e4d0bf39ca | ||
|
|
847fa6842e | ||
|
|
08aa4f29bd | ||
|
|
539218381b | ||
|
|
9dff39b4d7 | ||
|
|
1e578e94e5 | ||
|
|
ec35c9b9bb | ||
|
|
0deaeaeb7d | ||
|
|
ec3d9e1a56 | ||
|
|
814f84ae42 | ||
|
|
904be475ef | ||
|
|
5edecbe81b | ||
|
|
f357033404 | ||
|
|
4800ae6649 | ||
|
|
60b0690c1e | ||
|
|
82e31dd766 | ||
|
|
130e767e75 | ||
|
|
b1a1d509ec | ||
|
|
0698cf495e | ||
|
|
6047660405 | ||
|
|
aaf3495f83 | ||
|
|
ae6529c9fd | ||
|
|
45a96b2a42 | ||
|
|
4212903c67 | ||
|
|
be47dbcede | ||
|
|
9556c57c69 | ||
|
|
f72b65bfac | ||
|
|
6121700c9b | ||
|
|
91c968e743 | ||
|
|
8fed6f4823 | ||
|
|
34e4b39d75 | ||
|
|
a21c70ce46 | ||
|
|
8865a71f4c | ||
|
|
142cadc187 | ||
|
|
66f4c2531d | ||
|
|
3f8ddd5f7d | ||
|
|
c076e14cbd | ||
|
|
d724eb068b | ||
|
|
d3ac263e29 | ||
|
|
a6b7fe1f17 | ||
|
|
ec714ccb84 | ||
|
|
ab0a129ba1 | ||
|
|
a3f272b2aa | ||
|
|
77d91ed979 | ||
|
|
9907ba17c1 | ||
|
|
a6f0e19a44 | ||
|
|
7ee064b04d | ||
|
|
f2e4c735f7 | ||
|
|
5345bd321f | ||
|
|
eab8d0063b | ||
|
|
6877977234 | ||
|
|
60cb81362e | ||
|
|
088acb8e86 | ||
|
|
77f59f06cb | ||
|
|
a0882c6105 | ||
|
|
d50eb52172 | ||
|
|
5e5cadd314 | ||
|
|
75b06f82f0 | ||
|
|
2ab050aa60 | ||
|
|
6d2f645e4a | ||
|
|
9f937e29f2 | ||
|
|
abc61a96c8 | ||
|
|
b1d10822d4 | ||
|
|
09d984456d |
@@ -36,6 +36,7 @@ common/src/main/resources/assets/cobblemon/showdown
|
||||
# Generated files
|
||||
*/generated/resources/*
|
||||
/fabric/src/generated/resources/cobblemon-common.accesswidener
|
||||
/fabric/src/main/generated/*
|
||||
/.ignored/
|
||||
common/.fabric
|
||||
.kotlin/
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Changelog
|
||||
## [1.8.0 (MONTH Xth, 2026)](#1-8-0)
|
||||
|
||||
### Additions
|
||||
- Added a new `cobblemon:structure`, which is a feature without any restrictions allowing the Builders to place certain structures in much more specific manner.
|
||||
- Added Configs for Infinite Use TMs and Unlocking all TMs
|
||||
- Added Type Gem blocks
|
||||
- Added Type Gem Clusters
|
||||
- Added TMs
|
||||
- Added TM Machine
|
||||
- Added Deepslate Core Block
|
||||
- Added /givetm Command
|
||||
- Added Move Dex to Pokedex
|
||||
- Added Disk Rack for storing disks and being an audio sequencer for Noteblocks
|
||||
|
||||
|
||||
### Changes
|
||||
- Data Monitor can accept Disks (TMs, Music Disks, Upgrade, Dubious Disk, etc) to change look
|
||||
|
||||
### Fixes
|
||||
- Fixed rare Pokémon being far too likely to spawn multiple times in a single spawn cycle. The dev told me that the exact bug is too complicated to explain.
|
||||
- Resolved the niche issue where Structures would spawn in an incorrect location, creating very funky world gen.
|
||||
|
||||
### Developer
|
||||
- Tweaked the spawning selector interface to take a bucket function rather than a bucket directly.
|
||||
- Added a new `cobblemon:height_range` processor type to allow developers to restrict structure spawning ranges.
|
||||
|
||||
### Molang & Datapacks
|
||||
|
||||
### Particles
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
@@ -0,0 +1,16 @@
|
||||
### Additions
|
||||
|
||||
### Changes
|
||||
- Wild Pokémon now reliably have at least one offensive move, if they can have offensive moves at that level.
|
||||
|
||||
### Fixes
|
||||
|
||||
### Developer
|
||||
|
||||
### Molang & Datapacks
|
||||
- Added `moveset_builders` datapack folder for different styles of movesets.
|
||||
- Added `party_pools` datapack folder for defining groups of Pokémon for the purposes of complex NPC party definitions.
|
||||
- Added `party_compositions` datapack folder for arranging parties on NPCs that rely on party pools.
|
||||
- Added `composed_pool` NPC party provider type which combines a party pool with a party composition to create more complex NPC parties.
|
||||
|
||||
### Particles
|
||||
+43
-13
@@ -1,23 +1,53 @@
|
||||
# Changelog
|
||||
## [1.7.3 (January 31st, 2026)](#1-7-3)
|
||||
## [1.7.4 (MONTH Xth, 2026)](#1-7-4)
|
||||
|
||||
### Additions
|
||||
- Party Overlay Exp Gained Animation will now display the amount of EXP the Pokémon gained
|
||||
- Added a toast to notify players of how they can scroll through their party. Did you know you can hold R and scroll instead of using arrow keys??!
|
||||
- Added new layer property that makes a layer scroll in a direction. "scrolling": {"speedU": 0.1, "speedV": 0.1}
|
||||
- Added missing crops to Botany Pots integration
|
||||
|
||||
### New rideable Pokémon
|
||||
|
||||
### Pokémon Added
|
||||
|
||||
### Model updates for the following Pokémon
|
||||
|
||||
### Animation updates for the following Pokémon
|
||||
|
||||
### Changes
|
||||
- Restructured Botany Pots integration folder structure
|
||||
- Decrease default pastured mon wander radius to 32.
|
||||
- Factor in the weight of pokemon when calculating pushing forces between hitboxes.
|
||||
- Remade the Starter Selection Screen with new assets
|
||||
- Refactored berry trees to be less impactful on TPS (more performant)
|
||||
|
||||
### Fixes
|
||||
- Fixed mounted Pokémon not playing sounds.
|
||||
- Fixed players failing to join servers randomly.
|
||||
- Fixed some Pokémon having no stamina for flying.
|
||||
- Fixed `/pctake` not respecting the actual size of the PC.
|
||||
- Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943
|
||||
- Fix occasional crash on retrieving revived fossils
|
||||
- Fixed a compatibility issue with World Game Rules which caused some log spam in specific cases.
|
||||
- Fixed improper validation of Pokémon when performing a trade.
|
||||
- Fixed issue where particle like beam can disappear when you don't see the beginning of the beam or you look away
|
||||
- Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943
|
||||
- Fix occasional crash on retrieving revived fossils
|
||||
- Fix brushing not working on Neoforge
|
||||
- [Various Submarine Ride Fixes](https://gitlab.com/cable-mc/cobblemon/-/merge_requests/2157)
|
||||
- Fix Pastured mons despawning and running away.
|
||||
- Fix Apricorn trees crashing when other mods bonemeal it
|
||||
- [Various UFO Ride Improvements](https://gitlab.com/cable-mc/cobblemon/-/merge_requests/2156)
|
||||
- Fixed NeoForge brewing stand unexpectedly crashing users when shift clicking items within it's menu
|
||||
- Fixed NPCs switching pokemon very often during battles
|
||||
- Fixed Campfire pot causing crashes under certain circumstances
|
||||
|
||||
### Developer
|
||||
- Poké Ball GUI textures now respect the ball's namespace, enabling proper texture loading for custom balls from other mods.
|
||||
- Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters
|
||||
- Added `FishingSpawnerFactory` which allows influence registration on fishing encounters
|
||||
|
||||
### Molang & Datapacks
|
||||
- Added a `chance` requirement type for Pokémon interactions.
|
||||
- q.item.is_enchanted()
|
||||
- q.item.has_enchantment(minecraft:sharpness, 3)
|
||||
- Added `has_chosen_starter` & `get_starter_uuid` as available Molang functions
|
||||
- Added `marks`, `has_mark`, & `remove_marks` as available Molang functions for pokemon
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- French
|
||||
- Canadian French
|
||||
- Korean
|
||||
- Portuguese
|
||||
- Simplified Chinese
|
||||
- Spanish
|
||||
@@ -0,0 +1,22 @@
|
||||
# Changelog
|
||||
## [1.7.3 (January 31st, 2026)](#1-7-3)
|
||||
|
||||
### Fixes
|
||||
- Fixed mounted Pokémon not playing sounds.
|
||||
- Fixed players failing to join servers randomly.
|
||||
- Fixed some Pokémon having no stamina for flying.
|
||||
- Fixed `/pctake` not respecting the actual size of the PC.
|
||||
### Developer
|
||||
- Poké Ball GUI textures now respect the ball's namespace, enabling proper texture loading for custom balls from other mods.
|
||||
|
||||
### Molang & Datapacks
|
||||
- Added a `chance` requirement type for Pokémon interactions.
|
||||
|
||||
### Localization
|
||||
- Updated translations for:
|
||||
- French
|
||||
- Canadian French
|
||||
- Korean
|
||||
- Portuguese
|
||||
- Simplified Chinese
|
||||
- Spanish
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.mixin;
|
||||
|
||||
import com.cobblemon.mod.common.block.entity.TMShelfBlockEntity;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.NoteBlock;
|
||||
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(NoteBlock.class)
|
||||
public abstract class NoteBlockMixin {
|
||||
@Inject(method = "triggerEvent", at = @At("HEAD"), cancellable = true)
|
||||
private void cobblemon$triggerTMShelfSequencer(BlockState state, Level level, BlockPos pos, int type, int data, CallbackInfoReturnable<Boolean> cir) {
|
||||
final BlockPos shelfPos = pos.below();
|
||||
final BlockEntity blockEntity = level.getBlockEntity(shelfPos);
|
||||
if (!(blockEntity instanceof TMShelfBlockEntity tmShelfBlockEntity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!level.isClientSide) {
|
||||
final BlockPos instrumentPos = shelfPos.below();
|
||||
final SoundEvent soundEvent;
|
||||
if (level.getBlockState(instrumentPos).isAir()) {
|
||||
soundEvent = SoundEvents.NOTE_BLOCK_BIT.value();
|
||||
} else {
|
||||
final NoteBlockInstrument instrument = level.getBlockState(instrumentPos).instrument();
|
||||
final Holder<SoundEvent> holder = instrument.getSoundEvent();
|
||||
soundEvent = holder.value();
|
||||
}
|
||||
tmShelfBlockEntity.onNoteBlockPulse(level, pos, soundEvent);
|
||||
}
|
||||
|
||||
// Suppress vanilla note-block playback while it is on top of a TM Shelf.
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents;
|
||||
import com.cobblemon.mod.common.api.events.item.LeftoversCreatedEvent;
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController;
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies;
|
||||
import com.cobblemon.mod.common.api.riding.RidingStyle;
|
||||
import com.cobblemon.mod.common.api.riding.behaviour.types.air.HoverBehaviour;
|
||||
import com.cobblemon.mod.common.api.storage.party.PartyStore;
|
||||
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore;
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags;
|
||||
@@ -59,6 +61,7 @@ import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@@ -169,6 +172,32 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "getDestroySpeed",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/entity/player/Player;onGround()Z"
|
||||
)
|
||||
)
|
||||
private boolean cobblemon$considerHoverOnGround(Player player) {
|
||||
// If riding a Hover Mount, return true to bypass the 5x slowdown
|
||||
if (player.getVehicle() instanceof PokemonEntity vehicle) {
|
||||
|
||||
var ridingController = vehicle.getRidingController();
|
||||
if (ridingController == null) player.onGround();
|
||||
|
||||
var activeContext = ridingController.getContext();
|
||||
if (activeContext == null) player.onGround();
|
||||
|
||||
if (activeContext.getStyle() == RidingStyle.AIR &&
|
||||
activeContext.getBehaviour().equals(HoverBehaviour.Companion.getKEY())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Otherwise, return the actual value
|
||||
return player.onGround();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "eat",
|
||||
at = @At(
|
||||
|
||||
@@ -23,6 +23,7 @@ import net.minecraft.client.sounds.ChannelAccess;
|
||||
import net.minecraft.client.sounds.SoundEngine;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
@@ -32,6 +33,7 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
import java.util.Iterator;
|
||||
@@ -108,6 +110,17 @@ public abstract class SoundEngineMixin implements SoundEngineDuck {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TM Shelf can intentionally push note-block pitches above vanilla's 2.0f ceiling for octave-up playback.
|
||||
* Keep vanilla clamping for all other sounds.
|
||||
*/
|
||||
@Inject(method = "calculatePitch", at = @At("HEAD"), cancellable = true)
|
||||
private void cobblemon$allowExtendedNoteblockPitch(SoundInstance sound, CallbackInfoReturnable<Float> cir) {
|
||||
if (sound.getLocation().getPath().startsWith("block.note_block.")) {
|
||||
cir.setReturnValue(Mth.clamp(sound.getPitch(), 0.5F, 4.0F));
|
||||
}
|
||||
}
|
||||
|
||||
/** Different behavior for resuming SoundInstances while a BattleMusicInstance is being played. We do not want to resume filtered sounds. */
|
||||
@Inject(method = "resume()V", at = @At("HEAD"), cancellable = true)
|
||||
public void resume(CallbackInfo cb) {
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* 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.mixin;
|
||||
|
||||
import com.cobblemon.mod.common.world.CobblemonStructureIDs;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructurePiece;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureStart;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Mixin(Structure.class)
|
||||
public abstract class StructureMixin {
|
||||
// Define a set of structures that should not spawn below Sea Level
|
||||
@Unique
|
||||
private static final Set<ResourceLocation> RESTRICTED_STRUCTURES = new HashSet<>(Arrays.asList(
|
||||
CobblemonStructureIDs.STONJOURNER_HENGE,
|
||||
CobblemonStructureIDs.LUNA_HENGE,
|
||||
CobblemonStructureIDs.SOL_HENGE
|
||||
));
|
||||
|
||||
@Inject(method = "generate", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/structure/StructureStart;isValid()Z"),
|
||||
cancellable = true)
|
||||
public void cobblemon$isValid(RegistryAccess registryAccess, ChunkGenerator chunkGenerator, BiomeSource biomeSource,
|
||||
RandomState randomState, StructureTemplateManager structureTemplateManager, long seed,
|
||||
ChunkPos chunkPos, int references, LevelHeightAccessor heightAccessor,
|
||||
Predicate<Holder<Biome>> validBiome,
|
||||
CallbackInfoReturnable<StructureStart> cir,
|
||||
@Local StructureStart structureStart) {
|
||||
ResourceLocation structureKey = registryAccess.registryOrThrow(Registries.STRUCTURE).getKey((Structure) (Object) this);
|
||||
if(!RESTRICTED_STRUCTURES.contains(structureKey)) {
|
||||
return;
|
||||
}
|
||||
for (StructurePiece piece : structureStart.getPieces()) {
|
||||
if (piece.getBoundingBox().minY() < chunkGenerator.getSeaLevel()) {
|
||||
cir.setReturnValue(StructureStart.INVALID_START);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import com.cobblemon.mod.common.client.gui.party.PartyTutorialToasts;
|
||||
|
||||
import static com.google.common.primitives.Floats.min;
|
||||
import static net.minecraft.util.Mth.lerp;
|
||||
@@ -85,11 +86,13 @@ public abstract class MouseHandlerMixin {
|
||||
CobblemonClient.INSTANCE.getStorage().shiftSelected(false);
|
||||
ci.cancel();
|
||||
PartySendBinding.INSTANCE.actioned();
|
||||
PartyTutorialToasts.INSTANCE.onHoldKeyScrollSwitchedSlot();
|
||||
} else if (i < 0) {
|
||||
accumulatedScrollY -= i;
|
||||
CobblemonClient.INSTANCE.getStorage().shiftSelected(true);
|
||||
ci.cancel();
|
||||
PartySendBinding.INSTANCE.actioned();
|
||||
PartyTutorialToasts.INSTANCE.onHoldKeyScrollSwitchedSlot();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,7 +313,7 @@ public abstract class MouseHandlerMixin {
|
||||
var playerRotater = (RidePassenger)player;
|
||||
playerRotater.cobblemon$setRideXRot(playerRotater.cobblemon$getRideXRot() + f);
|
||||
playerRotater.cobblemon$setRideYRot(playerRotater.cobblemon$getRideYRot() + g);
|
||||
playerRotater.cobblemon$setRideXRot(Mth.clamp(playerRotater.cobblemon$getRideXRot(), -90.0F, 90.0F));
|
||||
playerRotater.cobblemon$setRideXRot(Mth.clamp(playerRotater.cobblemon$getRideXRot(), -89.0F, 89.0F));
|
||||
playerRotater.cobblemon$setRideYRot(Mth.clamp(playerRotater.cobblemon$getRideYRot(), -105.0F, 105.0F));
|
||||
if (player.getVehicle() != null) {
|
||||
player.getVehicle().onPassengerTurned(player);
|
||||
|
||||
@@ -14,9 +14,14 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalFloatRef;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
@@ -89,4 +94,61 @@ public abstract class EntityMixin {
|
||||
cir.setReturnValue(customEyePos);
|
||||
}
|
||||
|
||||
|
||||
@WrapOperation(
|
||||
method = "push(Lnet/minecraft/world/entity/Entity;)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;push(DDD)V", ordinal = 0)
|
||||
)
|
||||
public void cobblemon$customPusherLogic(Entity pusher, double x, double y, double z,
|
||||
Operation<Void> original,
|
||||
@Local(argsOnly = true) Entity pushee,
|
||||
@Local(name = "e") double e, @Local(name = "d") double d,
|
||||
@Share("pusherIsPlayerOrPokemon") LocalBooleanRef pusherIsPlayerOrPokemon,
|
||||
@Share("pusheeIsPlayerOrPokemon") LocalBooleanRef pusheeIsPlayerOrPokemon,
|
||||
@Share("pusheeReceivedForce")LocalFloatRef pusheeReceivedForce) {
|
||||
pusherIsPlayerOrPokemon.set(pusher instanceof PokemonEntity || pusher instanceof Player);
|
||||
pusheeIsPlayerOrPokemon.set(pushee instanceof PokemonEntity || pushee instanceof Player);
|
||||
|
||||
if (!pusheeIsPlayerOrPokemon.get() || !pusherIsPlayerOrPokemon.get()) {
|
||||
original.call(pusher, x, y, z);
|
||||
return;
|
||||
}
|
||||
|
||||
/**********************************************
|
||||
* Custom portion of the code:
|
||||
* Handle collisions with weight in mind. Use
|
||||
* the weight value of pokemon to determine
|
||||
* which side gets what portion of the total
|
||||
* force.
|
||||
*********************************************/
|
||||
// Weights in hectograms
|
||||
// Check to see if the pusher or pushee is a player. 200ish pounds. Steve's like a 6'6 miner
|
||||
var pusherWeight = pusher instanceof PokemonEntity pusherPokemon ? pusherPokemon.getPokemon().getSpecies().getWeight() : 900F;
|
||||
var pusheeWeight = pushee instanceof PokemonEntity pusheePokemon ? pusheePokemon.getPokemon().getSpecies().getWeight() : 900F;
|
||||
|
||||
// Calculate the portion of force received for both entities. Bigger receives less from the
|
||||
// collision than the smaller one.
|
||||
var totalWeight = pusherWeight + pusheeWeight;
|
||||
var pusherReceivedForce = pusheeWeight / totalWeight * 2F;
|
||||
pusheeReceivedForce.set((pusherWeight / totalWeight) * 2F);
|
||||
|
||||
original.call(pusher, -d * pusherReceivedForce, 0.0, -e * pusherReceivedForce);
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
method = "push(Lnet/minecraft/world/entity/Entity;)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;push(DDD)V", ordinal = 1)
|
||||
)
|
||||
public void cobblemon$customPusheeLogic(Entity pusher, double x, double y, double z, Operation<Void> original,
|
||||
@Local(name = "e") double e, @Local(name = "d") double d,
|
||||
@Share("pusherIsPlayerOrPokemon") LocalBooleanRef pusherIsPlayerOrPokemon,
|
||||
@Share("pusheeIsPlayerOrPokemon") LocalBooleanRef pusheeIsPlayerOrPokemon,
|
||||
@Share("pusheeReceivedForce")LocalFloatRef pusheeReceivedForce) {
|
||||
if (!pusheeIsPlayerOrPokemon.get() || !pusherIsPlayerOrPokemon.get()) {
|
||||
original.call(pusher, x, y, z);
|
||||
return;
|
||||
}
|
||||
|
||||
original.call(pusher, d * pusheeReceivedForce.get(), 0.0, e * pusheeReceivedForce.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +62,16 @@ import com.cobblemon.mod.common.api.storage.player.adapter.DexDataMongoBackend
|
||||
import com.cobblemon.mod.common.api.storage.player.adapter.DexDataNbtBackend
|
||||
import com.cobblemon.mod.common.api.storage.player.adapter.PlayerDataJsonBackend
|
||||
import com.cobblemon.mod.common.api.storage.player.adapter.PlayerDataMongoBackend
|
||||
import com.cobblemon.mod.common.api.storage.player.adapter.TMMoveMongoBackend
|
||||
import com.cobblemon.mod.common.api.storage.player.adapter.TMMoveNbtBackend
|
||||
import com.cobblemon.mod.common.api.storage.player.factory.CachedPlayerDataStoreFactory
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonEntityTypeTags
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
|
||||
import com.cobblemon.mod.common.battles.*
|
||||
import com.cobblemon.mod.common.battles.BagItems
|
||||
import com.cobblemon.mod.common.battles.BattleFormat
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
import com.cobblemon.mod.common.battles.BattleSide
|
||||
import com.cobblemon.mod.common.battles.ShowdownThread
|
||||
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
|
||||
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
|
||||
import com.cobblemon.mod.common.block.dispenser.DispenserBehaviorRegistry
|
||||
@@ -76,7 +82,12 @@ import com.cobblemon.mod.common.config.LastChangedVersion
|
||||
import com.cobblemon.mod.common.config.constraint.IntConstraint
|
||||
import com.cobblemon.mod.common.config.starter.StarterConfig
|
||||
import com.cobblemon.mod.common.data.CobblemonDataProvider
|
||||
import com.cobblemon.mod.common.events.*
|
||||
import com.cobblemon.mod.common.events.AdvancementHandler
|
||||
import com.cobblemon.mod.common.events.CallbackHandler
|
||||
import com.cobblemon.mod.common.events.EntityCallbackHandler
|
||||
import com.cobblemon.mod.common.events.PokedexHandler
|
||||
import com.cobblemon.mod.common.events.ServerTickHandler
|
||||
import com.cobblemon.mod.common.events.StatHandler
|
||||
import com.cobblemon.mod.common.net.messages.client.settings.ServerSettingsPacket
|
||||
import com.cobblemon.mod.common.permission.LaxPermissionValidator
|
||||
import com.cobblemon.mod.common.platform.events.PlatformEvents
|
||||
@@ -90,8 +101,14 @@ import com.cobblemon.mod.common.pokemon.feature.SlowpokeTailRegrowthSpeciesFeatu
|
||||
import com.cobblemon.mod.common.pokemon.feature.SlowpokeTailRegrowthSpeciesFeatureProvider
|
||||
import com.cobblemon.mod.common.pokemon.feature.TagSeasonResolver
|
||||
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
|
||||
import com.cobblemon.mod.common.pokemon.properties.*
|
||||
import com.cobblemon.mod.common.pokemon.properties.AspectPropertyType
|
||||
import com.cobblemon.mod.common.pokemon.properties.BattleCloneProperty
|
||||
import com.cobblemon.mod.common.pokemon.properties.FreezeFrameProperty
|
||||
import com.cobblemon.mod.common.pokemon.properties.HiddenAbilityPropertyType
|
||||
import com.cobblemon.mod.common.pokemon.properties.LabelProperty
|
||||
import com.cobblemon.mod.common.pokemon.properties.NoAIProperty
|
||||
import com.cobblemon.mod.common.pokemon.properties.UnaspectPropertyType
|
||||
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
|
||||
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
|
||||
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
|
||||
import com.cobblemon.mod.common.util.*
|
||||
@@ -102,6 +119,17 @@ import com.mongodb.ConnectionString
|
||||
import com.mongodb.MongoClientSettings
|
||||
import com.mongodb.client.MongoClient
|
||||
import com.mongodb.client.MongoClients
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
import java.io.FileWriter
|
||||
import java.io.PrintWriter
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.properties.Delegates
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.jvm.isAccessible
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.commands.synchronization.SingletonArgumentInfo
|
||||
import net.minecraft.resources.ResourceKey
|
||||
@@ -114,17 +142,6 @@ import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.storage.LevelResource
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.apache.logging.log4j.Logger
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
import java.io.FileWriter
|
||||
import java.io.PrintWriter
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.properties.Delegates
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.jvm.isAccessible
|
||||
import kotlin.reflect.jvm.javaField
|
||||
|
||||
object Cobblemon {
|
||||
const val MODID = CobblemonBuildDetails.MOD_ID
|
||||
@@ -223,6 +240,8 @@ object Cobblemon {
|
||||
|
||||
DATA_SYNCHRONIZED.subscribe {
|
||||
storage.onPlayerDataSync(it)
|
||||
it.tmList().scheduleFullSyncFromStores(it.party(), it.pc())
|
||||
it.pokedex().scheduleFullSyncFromStores(it.party(), it.pc())
|
||||
playerDataManager.syncAllToPlayer(it)
|
||||
starterHandler.handleJoin(it)
|
||||
it.requestWallpapers()
|
||||
@@ -285,6 +304,7 @@ object Cobblemon {
|
||||
this.config.maxPokemonLevel,
|
||||
this.config.maxPokemonFriendship,
|
||||
this.config.maxDynamaxLevel,
|
||||
this.config.unlockAllMoveDexMovesByDefault
|
||||
).sendToPlayer(player)
|
||||
}
|
||||
|
||||
@@ -354,8 +374,12 @@ object Cobblemon {
|
||||
val pokedexNbtFactory = CachedPlayerDataStoreFactory(DexDataNbtBackend())
|
||||
pokedexNbtFactory.setup(server)
|
||||
|
||||
val tmMoveFactory = CachedPlayerDataStoreFactory(TMMoveNbtBackend())
|
||||
tmMoveFactory.setup(server)
|
||||
|
||||
playerDataManager.setFactory(generalJsonFactory, PlayerInstancedDataStoreTypes.GENERAL)
|
||||
playerDataManager.setFactory(pokedexNbtFactory, PlayerInstancedDataStoreTypes.POKEDEX)
|
||||
playerDataManager.setFactory(tmMoveFactory, PlayerInstancedDataStoreTypes.TM_MOVES)
|
||||
|
||||
if (config.storageFormat == "nbt") {
|
||||
NBTStoreAdapter(pokemonStoreRoot.absolutePath, useNestedFolders = true, folderPerClass = true)
|
||||
@@ -382,8 +406,14 @@ object Cobblemon {
|
||||
val pokedexMongoFactory = CachedPlayerDataStoreFactory(DexDataMongoBackend(mongoClient, config.mongoDBDatabaseName, "PokeDexCollection"))
|
||||
pokedexMongoFactory.setup(server)
|
||||
|
||||
val tmMovesMongoFactory = CachedPlayerDataStoreFactory(
|
||||
TMMoveMongoBackend(mongoClient, config.mongoDBDatabaseName, "TMMovesCollection")
|
||||
)
|
||||
tmMovesMongoFactory.setup(server)
|
||||
|
||||
playerDataManager.setFactory(generalMongoFactory, PlayerInstancedDataStoreTypes.GENERAL)
|
||||
playerDataManager.setFactory(pokedexMongoFactory, PlayerInstancedDataStoreTypes.POKEDEX)
|
||||
playerDataManager.setFactory(tmMovesMongoFactory, PlayerInstancedDataStoreTypes.TM_MOVES)
|
||||
MongoDBStoreAdapter(mongoClient, config.mongoDBDatabaseName)
|
||||
} catch (e: ClassNotFoundException) {
|
||||
LOGGER.error("MongoDB driver not found.")
|
||||
@@ -451,7 +481,7 @@ object Cobblemon {
|
||||
server()?.getLevel(dimension)
|
||||
} else {
|
||||
val mc = Minecraft.getInstance()
|
||||
return mc.singleplayerServer?.getLevel(dimension) ?: mc.level
|
||||
mc.singleplayerServer?.getLevel(dimension) ?: mc.level
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,6 +597,7 @@ object Cobblemon {
|
||||
this.implementation.registerCommandArgument(cobblemonResource("mark"), MarkArgumentType::class, SingletonArgumentInfo.contextFree(MarkArgumentType::mark))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("transform_type"), TransformTypeArgumentType::class, SingletonArgumentInfo.contextFree(TransformTypeArgumentType::transformType))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("model_part"), ModelPartArgumentType::class, SingletonArgumentInfo.contextFree(ModelPartArgumentType::modelPart))
|
||||
this.implementation.registerCommandArgument(cobblemonResource("tm"), TmArgumentType::class, SingletonArgumentInfo.contextFree(TmArgumentType::tm))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,11 +34,17 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
|
||||
val HEALING_MACHINE: BlockEntityType<HealingMachineBlockEntity> = this.create("healing_machine", BlockEntityType.Builder.of(::HealingMachineBlockEntity, CobblemonBlocks.HEALING_MACHINE).build(null))
|
||||
|
||||
@JvmField
|
||||
val PC: BlockEntityType<PCBlockEntity> = this.create("pc", BlockEntityType.Builder.of(::PCBlockEntity, CobblemonBlocks.PC).build(null))
|
||||
val PC: BlockEntityType<PCBlockEntity> = this.create(
|
||||
"pc",
|
||||
BlockEntityType.Builder.of(::PCBlockEntity, CobblemonBlocks.PC, *CobblemonModificationBlocks.pcVariantBlocks().toTypedArray()).build(null)
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val LECTERN: BlockEntityType<LecternBlockEntity> = this.create("lectern", BlockEntityType.Builder.of(::LecternBlockEntity, CobblemonBlocks.LECTERN).build(null))
|
||||
|
||||
@JvmField
|
||||
val TM_SHELF: BlockEntityType<TMShelfBlockEntity> = this.create("tm_shelf", BlockEntityType.Builder.of(::TMShelfBlockEntity, CobblemonBlocks.TM_SHELF).build(null))
|
||||
|
||||
@JvmField
|
||||
val BERRY = this.create("berry", BlockEntityType.Builder.of(::BerryBlockEntity, *CobblemonBlocks.berries().values.toTypedArray()).build(null))
|
||||
|
||||
@@ -48,6 +54,8 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
|
||||
val SIGN: BlockEntityType<CobblemonSignBlockEntity> = this.create("sign", BlockEntityType.Builder.of(::CobblemonSignBlockEntity, CobblemonBlocks.APRICORN_SIGN, CobblemonBlocks.APRICORN_WALL_SIGN, CobblemonBlocks.SACCHARINE_SIGN, CobblemonBlocks.SACCHARINE_WALL_SIGN).build(null))
|
||||
@JvmField
|
||||
val HANGING_SIGN: BlockEntityType<CobblemonHangingSignBlockEntity> = this.create("hanging_sign", BlockEntityType.Builder.of(::CobblemonHangingSignBlockEntity, CobblemonBlocks.APRICORN_HANGING_SIGN, CobblemonBlocks.APRICORN_WALL_HANGING_SIGN, CobblemonBlocks.SACCHARINE_HANGING_SIGN, CobblemonBlocks.SACCHARINE_WALL_HANGING_SIGN).build(null))
|
||||
@JvmField
|
||||
val TM_MACHINE: BlockEntityType<TMMachineBlockEntity> = this.create("tm_machine", BlockEntityType.Builder.of(::TMMachineBlockEntity, CobblemonBlocks.TM_MACHINE).build(null))
|
||||
|
||||
@JvmField
|
||||
val GILDED_CHEST: BlockEntityType<GildedChestBlockEntity> = this.create("chest", BlockEntityType.Builder.of(::GildedChestBlockEntity,
|
||||
@@ -84,7 +92,11 @@ object CobblemonBlockEntities : PlatformRegistry<Registry<BlockEntityType<*>>, R
|
||||
|
||||
@JvmField
|
||||
val DISPLAY_CASE: BlockEntityType<DisplayCaseBlockEntity> = this.create("display_case",
|
||||
BlockEntityType.Builder.of(::DisplayCaseBlockEntity, CobblemonBlocks.DISPLAY_CASE).build(null)
|
||||
BlockEntityType.Builder.of(
|
||||
::DisplayCaseBlockEntity,
|
||||
CobblemonBlocks.DISPLAY_CASE,
|
||||
*CobblemonModificationBlocks.displayCaseVariantBlocks().toTypedArray()
|
||||
).build(null)
|
||||
)
|
||||
|
||||
// TODO after 1.7
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.block.sign.CobblemonHangingSignBlock
|
||||
import com.cobblemon.mod.common.block.sign.CobblemonSignBlock
|
||||
import com.cobblemon.mod.common.block.sign.CobblemonWallHangingSignBlock
|
||||
import com.cobblemon.mod.common.block.sign.CobblemonWallSignBlock
|
||||
import com.cobblemon.mod.common.block.tmmachine.TMMachineBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationCarpetBlock
|
||||
import com.cobblemon.mod.common.block.general.HorizontalRotationalBlock
|
||||
import com.cobblemon.mod.common.mixin.invoker.*
|
||||
@@ -121,19 +122,19 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val APRICORN_PLANKS = this.create("apricorn_planks",
|
||||
Block(
|
||||
BlockBehaviour.Properties.of().mapColor(MapColor.DIRT).instrument(NoteBlockInstrument.BASS)
|
||||
.strength(2.0f, 3.0f).sound(SoundType.WOOD)
|
||||
.strength(2.0F, 3.0F).sound(SoundType.WOOD)
|
||||
)
|
||||
)
|
||||
@JvmField
|
||||
val APRICORN_LEAVES = leaves("apricorn_leaves")
|
||||
@JvmField
|
||||
val APRICORN_FENCE = this.create("apricorn_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
|
||||
val APRICORN_FENCE = this.create("apricorn_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
|
||||
@JvmField
|
||||
val APRICORN_FENCE_GATE = this.create("apricorn_fence_gate", FenceGateBlock(APRICORN_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).strength(2.0f, 3.0f).sound(SoundType.WOOD).ignitedByLava().forceSolidOn()))
|
||||
val APRICORN_FENCE_GATE = this.create("apricorn_fence_gate", FenceGateBlock(APRICORN_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).strength(2.0F, 3.0F).sound(SoundType.WOOD).ignitedByLava().forceSolidOn()))
|
||||
@JvmField
|
||||
val APRICORN_BUTTON = this.create("apricorn_button", BlocksInvoker.createWoodenButtonBlock(APRICORN_BLOCK_SET_TYPE))
|
||||
@JvmField
|
||||
val APRICORN_PRESSURE_PLATE = this.create("apricorn_pressure_plate", PressurePlateBlockInvoker.`cobblemon$create`(APRICORN_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).noCollission().strength(0.5f).sound(SoundType.WOOD)))
|
||||
val APRICORN_PRESSURE_PLATE = this.create("apricorn_pressure_plate", PressurePlateBlockInvoker.`cobblemon$create`(APRICORN_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(APRICORN_PLANKS.defaultMapColor()).noCollission().strength(0.5F).sound(SoundType.WOOD)))
|
||||
@JvmField
|
||||
val APRICORN_SIGN = this.create("apricorn_sign", CobblemonSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN), APRICORN_WOOD_TYPE))
|
||||
|
||||
@@ -150,7 +151,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val APRICORN_WALL_HANGING_SIGN = this.create("apricorn_wall_hanging_sign", CobblemonWallHangingSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_HANGING_SIGN), APRICORN_WOOD_TYPE))
|
||||
@JvmField
|
||||
val APRICORN_SLAB = this.create("apricorn_slab", SlabBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
|
||||
val APRICORN_SLAB = this.create("apricorn_slab", SlabBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
|
||||
@JvmField
|
||||
val APRICORN_STAIRS = this.create("apricorn_stairs", StairsBlockInvoker.`cobblemon$create`(APRICORN_PLANKS.defaultBlockState(), BlockBehaviour.Properties.ofFullCopy(APRICORN_PLANKS)))
|
||||
@JvmField
|
||||
@@ -187,12 +188,12 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val STRIPPED_SACCHARINE_WOOD = this.create("stripped_saccharine_wood", RotatedPillarBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_LIGHT_BLUE).instrument(NoteBlockInstrument.BASS).strength(2.0F).sound(SoundType.WOOD).ignitedByLava())) //log("stripped_saccharine_log")
|
||||
@JvmField
|
||||
val SACCHARINE_PLANKS = this.create("saccharine_planks", Block(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_LIGHT_BLUE).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
|
||||
val SACCHARINE_PLANKS = this.create("saccharine_planks", Block(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_LIGHT_BLUE).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
|
||||
@JvmField
|
||||
val SACCHARINE_LEAVES = this.create("saccharine_leaves", SaccharineLeafBlock(
|
||||
BlockBehaviour.Properties.of().mapColor(
|
||||
MapColor.PLANT
|
||||
).strength(0.2f).randomTicks().sound(SoundType.GRASS).noOcclusion()
|
||||
).strength(0.2F).randomTicks().sound(SoundType.GRASS).noOcclusion()
|
||||
.isValidSpawn { _: BlockState?, _: BlockGetter?, _: BlockPos?, entity: EntityType<*>? ->
|
||||
entity === EntityType.OCELOT || entity === EntityType.PARROT
|
||||
}.isSuffocating { _: BlockState?, _: BlockGetter?, _: BlockPos? ->
|
||||
@@ -204,13 +205,13 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
false
|
||||
}))
|
||||
@JvmField
|
||||
val SACCHARINE_FENCE = this.create("saccharine_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
|
||||
val SACCHARINE_FENCE = this.create("saccharine_fence", FenceBlock(BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
|
||||
@JvmField
|
||||
val SACCHARINE_FENCE_GATE = this.create("saccharine_fence_gate", FenceGateBlock(SACCHARINE_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD).ignitedByLava().forceSolidOn()))
|
||||
val SACCHARINE_FENCE_GATE = this.create("saccharine_fence_gate", FenceGateBlock(SACCHARINE_WOOD_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD).ignitedByLava().forceSolidOn()))
|
||||
@JvmField
|
||||
val SACCHARINE_BUTTON = this.create("saccharine_button", BlocksInvoker.createWoodenButtonBlock(BlockSetType.OAK))
|
||||
@JvmField
|
||||
val SACCHARINE_PRESSURE_PLATE = this.create("saccharine_pressure_plate", PressurePlateBlockInvoker.`cobblemon$create`(SACCHARINE_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).noCollission().strength(0.5f).sound(SoundType.WOOD)))
|
||||
val SACCHARINE_PRESSURE_PLATE = this.create("saccharine_pressure_plate", PressurePlateBlockInvoker.`cobblemon$create`(SACCHARINE_BLOCK_SET_TYPE, BlockBehaviour.Properties.of().mapColor(SACCHARINE_PLANKS.defaultMapColor()).instrument(NoteBlockInstrument.BASS).noCollission().strength(0.5F).sound(SoundType.WOOD)))
|
||||
@JvmField
|
||||
val SACCHARINE_SIGN = this.create("saccharine_sign", CobblemonSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN), SACCHARINE_WOOD_TYPE))
|
||||
@JvmField
|
||||
@@ -220,7 +221,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val SACCHARINE_WALL_HANGING_SIGN = this.create("saccharine_wall_hanging_sign", CobblemonWallHangingSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_HANGING_SIGN), SACCHARINE_WOOD_TYPE))
|
||||
@JvmField
|
||||
val SACCHARINE_SLAB = this.create("saccharine_slab", SlabBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.0f, 3.0f).sound(SoundType.WOOD)))
|
||||
val SACCHARINE_SLAB = this.create("saccharine_slab", SlabBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.0F, 3.0F).sound(SoundType.WOOD)))
|
||||
@JvmField
|
||||
val SACCHARINE_STAIRS = this.create("saccharine_stairs", StairsBlockInvoker.`cobblemon$create`(SACCHARINE_PLANKS.defaultBlockState(), BlockBehaviour.Properties.ofFullCopy(SACCHARINE_PLANKS)))
|
||||
@JvmField
|
||||
@@ -249,31 +250,31 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val POKE_SNACK = this.create("poke_snack", PokeSnackBlock(BlockBehaviour.Properties.of().forceSolidOn().strength(0.5F).sound(SoundType.WOOL).pushReaction(PushReaction.DESTROY).lightLevel { if (it.getValue(BlockStateProperties.LIT)) 3 else 0 }, true))
|
||||
|
||||
@JvmField
|
||||
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
|
||||
val TUMBLESTONE_CLUSTER = tumblestoneBlock("tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
|
||||
@JvmField
|
||||
val LARGE_BUDDING_TUMBLESTONE = tumblestoneBlock("large_budding_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, TUMBLESTONE_CLUSTER)
|
||||
val LARGE_BUDDING_TUMBLESTONE = tumblestoneBlock("large_budding_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, TUMBLESTONE_CLUSTER)
|
||||
@JvmField
|
||||
val MEDIUM_BUDDING_TUMBLESTONE = tumblestoneBlock("medium_budding_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_TUMBLESTONE)
|
||||
val MEDIUM_BUDDING_TUMBLESTONE = tumblestoneBlock("medium_budding_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_TUMBLESTONE)
|
||||
@JvmField
|
||||
val SMALL_BUDDING_TUMBLESTONE = tumblestoneBlock("small_budding_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_TUMBLESTONE)
|
||||
val SMALL_BUDDING_TUMBLESTONE = tumblestoneBlock("small_budding_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_TUMBLESTONE)
|
||||
|
||||
@JvmField
|
||||
val SKY_TUMBLESTONE_CLUSTER = tumblestoneBlock("sky_tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
|
||||
val SKY_TUMBLESTONE_CLUSTER = tumblestoneBlock("sky_tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
|
||||
@JvmField
|
||||
val LARGE_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("large_budding_sky_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, SKY_TUMBLESTONE_CLUSTER)
|
||||
val LARGE_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("large_budding_sky_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, SKY_TUMBLESTONE_CLUSTER)
|
||||
@JvmField
|
||||
val MEDIUM_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("medium_budding_sky_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_SKY_TUMBLESTONE)
|
||||
val MEDIUM_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("medium_budding_sky_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_SKY_TUMBLESTONE)
|
||||
@JvmField
|
||||
val SMALL_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("small_budding_sky_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_SKY_TUMBLESTONE)
|
||||
val SMALL_BUDDING_SKY_TUMBLESTONE = tumblestoneBlock("small_budding_sky_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_SKY_TUMBLESTONE)
|
||||
|
||||
@JvmField
|
||||
val BLACK_TUMBLESTONE_CLUSTER = tumblestoneBlock("black_tumblestone_cluster", GrowableStoneBlock.STAGE_3, 7, 3, null)
|
||||
val BLACK_TUMBLESTONE_CLUSTER = tumblestoneBlock("black_tumblestone_cluster", TumblestoneBlock.STAGE_3, 7, 3, null)
|
||||
@JvmField
|
||||
val LARGE_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("large_budding_black_tumblestone", GrowableStoneBlock.STAGE_2, 5, 3, BLACK_TUMBLESTONE_CLUSTER)
|
||||
val LARGE_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("large_budding_black_tumblestone", TumblestoneBlock.STAGE_2, 5, 3, BLACK_TUMBLESTONE_CLUSTER)
|
||||
@JvmField
|
||||
val MEDIUM_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("medium_budding_black_tumblestone", GrowableStoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_BLACK_TUMBLESTONE)
|
||||
val MEDIUM_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("medium_budding_black_tumblestone", TumblestoneBlock.STAGE_1, 4, 3, LARGE_BUDDING_BLACK_TUMBLESTONE)
|
||||
@JvmField
|
||||
val SMALL_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("small_budding_black_tumblestone", GrowableStoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_BLACK_TUMBLESTONE)
|
||||
val SMALL_BUDDING_BLACK_TUMBLESTONE = tumblestoneBlock("small_budding_black_tumblestone", TumblestoneBlock.STAGE_0, 3, 4, MEDIUM_BUDDING_BLACK_TUMBLESTONE)
|
||||
|
||||
@JvmField
|
||||
val TUMBLESTONE_BLOCK = this.create("tumblestone_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_ORANGE).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
|
||||
@@ -282,6 +283,87 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val BLACK_TUMBLESTONE_BLOCK = this.create("black_tumblestone_block", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_BLACK).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_CORE = this.create("type_gem_core", TypeGemCoreBlock(BlockBehaviour.Properties.of().strength(4.0F, 10.0F).requiresCorrectToolForDrops()))
|
||||
|
||||
private val typeGemBlocks = mutableMapOf<ResourceLocation, Block>()
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_NORMAL = typeGemBlock("type_gem_block_normal")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FIRE = typeGemBlock("type_gem_block_fire")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_WATER = typeGemBlock("type_gem_block_water")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_ELECTRIC = typeGemBlock("type_gem_block_electric")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_GRASS = typeGemBlock("type_gem_block_grass")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_ICE = typeGemBlock("type_gem_block_ice")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FIGHTING = typeGemBlock("type_gem_block_fighting")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_POISON = typeGemBlock("type_gem_block_poison")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_GROUND = typeGemBlock("type_gem_block_ground")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FLYING = typeGemBlock("type_gem_block_flying")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_PSYCHIC = typeGemBlock("type_gem_block_psychic")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_BUG = typeGemBlock("type_gem_block_bug")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_ROCK = typeGemBlock("type_gem_block_rock")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_GHOST = typeGemBlock("type_gem_block_ghost")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_DRAGON = typeGemBlock("type_gem_block_dragon")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_DARK = typeGemBlock("type_gem_block_dark")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_STEEL = typeGemBlock("type_gem_block_steel")
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FAIRY = typeGemBlock("type_gem_block_fairy")
|
||||
|
||||
private val typeGemClusters = mutableMapOf<ResourceLocation, TypeGemClusterBlock>()
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_NORMAL = typeGemCluster("type_gem_cluster_normal", TYPE_GEM_BLOCK_NORMAL, cobblemonResource("normal_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FIRE = typeGemCluster("type_gem_cluster_fire", TYPE_GEM_BLOCK_FIRE, cobblemonResource("fire_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_WATER = typeGemCluster("type_gem_cluster_water", TYPE_GEM_BLOCK_WATER, cobblemonResource("water_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_ELECTRIC = typeGemCluster("type_gem_cluster_electric", TYPE_GEM_BLOCK_ELECTRIC, cobblemonResource("electric_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_GRASS = typeGemCluster("type_gem_cluster_grass", TYPE_GEM_BLOCK_GRASS, cobblemonResource("grass_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_ICE = typeGemCluster("type_gem_cluster_ice", TYPE_GEM_BLOCK_ICE, cobblemonResource("ice_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FIGHTING = typeGemCluster("type_gem_cluster_fighting", TYPE_GEM_BLOCK_FIGHTING, cobblemonResource("fighting_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_POISON = typeGemCluster("type_gem_cluster_poison", TYPE_GEM_BLOCK_POISON, cobblemonResource("poison_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_GROUND = typeGemCluster("type_gem_cluster_ground", TYPE_GEM_BLOCK_GROUND, cobblemonResource("ground_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FLYING = typeGemCluster("type_gem_cluster_flying", TYPE_GEM_BLOCK_FLYING, cobblemonResource("flying_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_PSYCHIC = typeGemCluster("type_gem_cluster_psychic", TYPE_GEM_BLOCK_PSYCHIC, cobblemonResource("psychic_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_BUG = typeGemCluster("type_gem_cluster_bug", TYPE_GEM_BLOCK_BUG, cobblemonResource("bug_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_ROCK = typeGemCluster("type_gem_cluster_rock", TYPE_GEM_BLOCK_ROCK, cobblemonResource("rock_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_GHOST = typeGemCluster("type_gem_cluster_ghost", TYPE_GEM_BLOCK_GHOST, cobblemonResource("ghost_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_DRAGON = typeGemCluster("type_gem_cluster_dragon", TYPE_GEM_BLOCK_DRAGON, cobblemonResource("dragon_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_DARK = typeGemCluster("type_gem_cluster_dark", TYPE_GEM_BLOCK_DARK, cobblemonResource("dark_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_STEEL = typeGemCluster("type_gem_cluster_steel", TYPE_GEM_BLOCK_STEEL, cobblemonResource("steel_gem"))
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FAIRY = typeGemCluster("type_gem_cluster_fairy", TYPE_GEM_BLOCK_FAIRY, cobblemonResource("fairy_gem"))
|
||||
|
||||
@JvmField
|
||||
val POLISHED_TUMBLESTONE = this.create("polished_tumblestone", Block(BlockBehaviour.Properties.of().mapColor(MapColor.TERRACOTTA_ORANGE).strength(1.0F).sound(CobblemonSounds.TUMBLESTONE_BLOCK_SOUNDS).requiresCorrectToolForDrops().instrument(NoteBlockInstrument.BASEDRUM)))
|
||||
@JvmField
|
||||
@@ -459,7 +541,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
@JvmField
|
||||
val RELIC_COIN_POUCH = create("relic_coin_pouch", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_POUCH_SOUNDS).pushReaction(PushReaction.DESTROY).noOcclusion(), true))
|
||||
@JvmField
|
||||
val RELIC_COIN_SACK = create("relic_coin_sack", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_SACK_SOUNDS).pushReaction(PushReaction.DESTROY).strength(0.4f), false))
|
||||
val RELIC_COIN_SACK = create("relic_coin_sack", CoinPouchBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.RELIC_COIN_SACK_SOUNDS).pushReaction(PushReaction.DESTROY).strength(0.4F), false))
|
||||
|
||||
@JvmField
|
||||
val GILDED_CHEST = create("gilded_chest", GildedChestBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.CHEST).noOcclusion().sound(CobblemonSounds.GILDED_CHEST_SOUNDS), GildedChestBlock.Type.RED))
|
||||
@@ -579,20 +661,48 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val RESTORATION_TANK = create("restoration_tank", RestorationTankBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.GLASS).pushReaction(PushReaction.BLOCK).requiresCorrectToolForDrops().strength(5.0F, 6.0F).noOcclusion()))
|
||||
|
||||
@JvmField
|
||||
val HEALING_MACHINE = create("healing_machine", HealingMachineBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2f).noOcclusion().lightLevel { if (it.getValue(HealingMachineBlock.CHARGE_LEVEL) >= HealingMachineBlock.MAX_CHARGE_LEVEL) 12 else 5 }))
|
||||
val HEALING_MACHINE = create("healing_machine", HealingMachineBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if (it.getValue(HealingMachineBlock.CHARGE_LEVEL) >= HealingMachineBlock.MAX_CHARGE_LEVEL) 12 else 5 }))
|
||||
|
||||
@JvmField
|
||||
val PC = create("pc", PCBlock(BlockBehaviour.Properties.of().mapColor(MapColor.METAL).sound(SoundType.METAL).pushReaction(PushReaction.BLOCK).strength(2F).noOcclusion().lightLevel { if ((it.getValue(PCBlock.ON) as Boolean) && (it.getValue(PCBlock.PART) == PCBlock.PCPart.TOP)) 13 else 0 }))
|
||||
|
||||
val TM_MACHINE = create(
|
||||
"tm_machine",
|
||||
TMMachineBlock(
|
||||
BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.COLOR_GREEN)
|
||||
.sound(SoundType.METAL)
|
||||
.pushReaction(PushReaction.BLOCK)
|
||||
.strength(2F)
|
||||
.noOcclusion()
|
||||
)
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val MODIFICATION_STATION = create(
|
||||
"modification_station",
|
||||
ModificationStationBlock(
|
||||
BlockBehaviour.Properties.of()
|
||||
.mapColor(MapColor.STONE)
|
||||
.sound(SoundType.STONE)
|
||||
.strength(3.5F)
|
||||
.requiresCorrectToolForDrops()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@JvmField
|
||||
val LECTERN = create("lectern", LecternBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).sound(SoundType.WOOD).strength(2.5F).ignitedByLava().lightLevel { if (it.getValue(LecternBlock.EMIT_LIGHT) as Boolean) 13 else 0 }))
|
||||
|
||||
@JvmField
|
||||
val TM_SHELF = create("tm_shelf", TMShelfBlock(BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).sound(SoundType.METAL).strength(2.5F)))
|
||||
|
||||
@JvmField
|
||||
val DISPLAY_CASE = create("display_case", DisplayCaseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3F).requiresCorrectToolForDrops()))
|
||||
|
||||
// TODO after 1.7
|
||||
// @JvmField
|
||||
// val INCENSE_SWEET = create("incense_sweet", SweetIncenseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3f)))
|
||||
// val INCENSE_SWEET = create("incense_sweet", SweetIncenseBlock(BlockBehaviour.Properties.of().sound(CobblemonSounds.DISPLAY_CASE_SOUNDS).noOcclusion().pushReaction(PushReaction.BLOCK).mapColor(MapColor.STONE).strength(0.3F)))
|
||||
|
||||
@JvmField
|
||||
val PASTURE = create("pasture", PastureBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_BROWN).sound(SoundType.WOOD).strength(2F).noOcclusion().pushReaction(PushReaction.BLOCK).lightLevel { if ((it.getValue(PastureBlock.ON) as Boolean) && (it.getValue(PastureBlock.PART) == PastureBlock.PasturePart.TOP)) 13 else 0 }))
|
||||
@@ -890,4 +1000,28 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
|
||||
val block = BlocksInvoker.createLeavesBlock(SoundType.GRASS)
|
||||
return this.create(name, block)
|
||||
}
|
||||
|
||||
private fun typeGemBlock(name: String): Block {
|
||||
val typeGemBlock = this.create(name, Block(BlockBehaviour.Properties.of().strength(3.0F, 6.0F).sound(SoundType.AMETHYST)))
|
||||
|
||||
val identifier = cobblemonResource(name)
|
||||
typeGemBlocks[identifier] = typeGemBlock
|
||||
|
||||
return typeGemBlock
|
||||
}
|
||||
|
||||
fun typeGemBlocks() = typeGemBlocks.toMap()
|
||||
|
||||
private fun typeGemCluster(name: String, gemBlock: Block, dropItemId: ResourceLocation): Block {
|
||||
val gemClusterBlock = this.create(name, TypeGemClusterBlock(BlockBehaviour.Properties.of().strength(2.0F, 3.0F).sound(SoundType.AMETHYST_CLUSTER).pushReaction(PushReaction.DESTROY), gemBlock, dropItemId))
|
||||
|
||||
TypeGemClusterBlock.gemToClusterMap[gemBlock] = gemClusterBlock
|
||||
|
||||
val identifier = cobblemonResource(name)
|
||||
typeGemClusters[identifier] = gemClusterBlock
|
||||
|
||||
return gemClusterBlock
|
||||
}
|
||||
|
||||
fun typeGemClusters() = typeGemClusters.toMap()
|
||||
}
|
||||
@@ -71,10 +71,12 @@ object CobblemonCommands {
|
||||
MarkGiveAllCommand.register(dispatcher)
|
||||
PcSearchCommand.register(dispatcher)
|
||||
PcTakeCommand.register(dispatcher)
|
||||
GiveTmCommand.register(dispatcher)
|
||||
TmCommand.register(dispatcher)
|
||||
|
||||
// Possibly lock down registration if and only if under dev environment or running in an environment
|
||||
// with a certain system environment variable set
|
||||
CobblemonInfoCommand.register(dispatcher)
|
||||
CalculateSeatPositionCommand.register(dispatcher)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
import net.minecraft.server.packs.resources.PreparableReloadListener
|
||||
import net.minecraft.tags.TagKey
|
||||
import net.minecraft.world.inventory.MenuType
|
||||
import net.minecraft.world.level.GameRules
|
||||
import net.minecraft.world.level.ItemLike
|
||||
import net.minecraft.world.level.biome.Biome
|
||||
|
||||
@@ -69,6 +69,12 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
|
||||
.networkSynchronized(FoodColourComponent.PACKET_CODEC)
|
||||
.build())
|
||||
|
||||
@JvmField
|
||||
val TM_MOVE: DataComponentType<TMMoveComponent> = create("tm_move", DataComponentType.builder<TMMoveComponent>()
|
||||
.persistent(TMMoveComponent.CODEC)
|
||||
.networkSynchronized(TMMoveComponent.PACKET_CODEC)
|
||||
.build())
|
||||
|
||||
@JvmField
|
||||
val INGREDIENT: DataComponentType<IngredientComponent> = create("ingredient", DataComponentType.builder<IngredientComponent>()
|
||||
.persistent(IngredientComponent.CODEC)
|
||||
@@ -93,6 +99,12 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
|
||||
.networkSynchronized(ByteBufCodecs.BOOL)
|
||||
.build())
|
||||
|
||||
@JvmField
|
||||
val MODIFICATION_VARIANT: DataComponentType<ModificationVariantComponent> = create("modification_variant", DataComponentType.builder<ModificationVariantComponent>()
|
||||
.persistent(ModificationVariantComponent.CODEC)
|
||||
.networkSynchronized(ModificationVariantComponent.PACKET_CODEC)
|
||||
.build())
|
||||
|
||||
fun register() {
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:pokemon_item"), POKEMON_ITEM)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:bait"), BAIT)
|
||||
@@ -101,10 +113,12 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:flavour"), FLAVOUR)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ride_boosts"), RIDE_BOOST)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food_colour"), FOOD_COLOUR)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:tm_move"), TM_MOVE)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ingredient"), INGREDIENT)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food"), FOOD)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:mob_effects"), MOB_EFFECTS)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:held_item_effect"), HELD_ITEM_EFFECT)
|
||||
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:modification_variant"), MODIFICATION_VARIANT)
|
||||
}
|
||||
|
||||
override val registry = BuiltInRegistries.DATA_COMPONENT_TYPE
|
||||
|
||||
@@ -457,6 +457,12 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val PC = blockItem("pc", CobblemonBlocks.PC)
|
||||
@JvmField
|
||||
val TM_MACHINE = blockItem("tm_machine", CobblemonBlocks.TM_MACHINE)
|
||||
@JvmField
|
||||
val MODIFICATION_STATION = blockItem("modification_station", CobblemonBlocks.MODIFICATION_STATION)
|
||||
@JvmField
|
||||
val TM_SHELF = blockItem("tm_shelf", CobblemonBlocks.TM_SHELF)
|
||||
@JvmField
|
||||
val PASTURE = blockItem("pasture", CobblemonBlocks.PASTURE)
|
||||
@JvmField
|
||||
val DISPLAY_CASE = blockItem("display_case", CobblemonBlocks.DISPLAY_CASE)
|
||||
@@ -1208,6 +1214,83 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val BLACK_TUMBLESTONE_BLOCK = blockItem("black_tumblestone_block", CobblemonBlocks.BLACK_TUMBLESTONE_BLOCK)
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_CORE = blockItem("type_gem_core", CobblemonBlocks.TYPE_GEM_CORE)
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_NORMAL = blockItem("type_gem_cluster_normal", CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FIRE = blockItem("type_gem_cluster_fire", CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_WATER = blockItem("type_gem_cluster_water", CobblemonBlocks.TYPE_GEM_CLUSTER_WATER)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_ELECTRIC = blockItem("type_gem_cluster_electric", CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_GRASS = blockItem("type_gem_cluster_grass", CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_ICE = blockItem("type_gem_cluster_ice", CobblemonBlocks.TYPE_GEM_CLUSTER_ICE)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FIGHTING = blockItem("type_gem_cluster_fighting", CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_POISON = blockItem("type_gem_cluster_poison", CobblemonBlocks.TYPE_GEM_CLUSTER_POISON)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_GROUND = blockItem("type_gem_cluster_ground", CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FLYING = blockItem("type_gem_cluster_flying", CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_PSYCHIC = blockItem("type_gem_cluster_psychic", CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_BUG = blockItem("type_gem_cluster_bug", CobblemonBlocks.TYPE_GEM_CLUSTER_BUG)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_ROCK = blockItem("type_gem_cluster_rock", CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_GHOST = blockItem("type_gem_cluster_ghost", CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_DRAGON = blockItem("type_gem_cluster_dragon", CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_DARK = blockItem("type_gem_cluster_dark", CobblemonBlocks.TYPE_GEM_CLUSTER_DARK)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_STEEL = blockItem("type_gem_cluster_steel", CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL)
|
||||
@JvmField
|
||||
val TYPE_GEM_CLUSTER_FAIRY = blockItem("type_gem_cluster_fairy", CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY)
|
||||
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_NORMAL = blockItem("type_gem_block_normal", CobblemonBlocks.TYPE_GEM_BLOCK_NORMAL)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FIRE = blockItem("type_gem_block_fire", CobblemonBlocks.TYPE_GEM_BLOCK_FIRE)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_WATER = blockItem("type_gem_block_water", CobblemonBlocks.TYPE_GEM_BLOCK_WATER)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_ELECTRIC = blockItem("type_gem_block_electric", CobblemonBlocks.TYPE_GEM_BLOCK_ELECTRIC)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_GRASS = blockItem("type_gem_block_grass", CobblemonBlocks.TYPE_GEM_BLOCK_GRASS)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_ICE = blockItem("type_gem_block_ice", CobblemonBlocks.TYPE_GEM_BLOCK_ICE)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FIGHTING = blockItem("type_gem_block_fighting", CobblemonBlocks.TYPE_GEM_BLOCK_FIGHTING)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_POISON = blockItem("type_gem_block_poison", CobblemonBlocks.TYPE_GEM_BLOCK_POISON)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_GROUND = blockItem("type_gem_block_ground", CobblemonBlocks.TYPE_GEM_BLOCK_GROUND)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FLYING = blockItem("type_gem_block_flying", CobblemonBlocks.TYPE_GEM_BLOCK_FLYING)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_PSYCHIC = blockItem("type_gem_block_psychic", CobblemonBlocks.TYPE_GEM_BLOCK_PSYCHIC)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_BUG = blockItem("type_gem_block_bug", CobblemonBlocks.TYPE_GEM_BLOCK_BUG)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_ROCK = blockItem("type_gem_block_rock", CobblemonBlocks.TYPE_GEM_BLOCK_ROCK)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_GHOST = blockItem("type_gem_block_ghost", CobblemonBlocks.TYPE_GEM_BLOCK_GHOST)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_DRAGON = blockItem("type_gem_block_dragon", CobblemonBlocks.TYPE_GEM_BLOCK_DRAGON)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_DARK = blockItem("type_gem_block_dark", CobblemonBlocks.TYPE_GEM_BLOCK_DARK)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_STEEL = blockItem("type_gem_block_steel", CobblemonBlocks.TYPE_GEM_BLOCK_STEEL)
|
||||
@JvmField
|
||||
val TYPE_GEM_BLOCK_FAIRY = blockItem("type_gem_block_fairy", CobblemonBlocks.TYPE_GEM_BLOCK_FAIRY)
|
||||
|
||||
@JvmField
|
||||
val POKEROD_SMITHING_TEMPLATE = create("pokerod_smithing_template", SmithingTemplateItem(
|
||||
Component.translatable("item.minecraft.fishing_rod").blue(),
|
||||
@@ -1432,44 +1515,49 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
val RELIC_COIN_POUCH = blockItem("relic_coin_pouch", CobblemonBlocks.RELIC_COIN_POUCH)
|
||||
@JvmField
|
||||
val RELIC_COIN_SACK = blockItem("relic_coin_sack", CobblemonBlocks.RELIC_COIN_SACK)
|
||||
@JvmField
|
||||
val TECHNICAL_MACHINE = this.create("technical_machine", TechnicalMachineItem(Properties()))
|
||||
@JvmField
|
||||
val BLANK_TM = this.create("blank_tm", CobblemonItem(Item.Properties()))
|
||||
|
||||
// Type Gems
|
||||
@JvmField
|
||||
val NORMAL_GEM = noSettingsItem("normal_gem")
|
||||
val NORMAL_GEM = this.create("normal_gem", itemNameBlockItem("normal_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL))
|
||||
@JvmField
|
||||
val FIRE_GEM = noSettingsItem("fire_gem")
|
||||
val FIRE_GEM = this.create("fire_gem", itemNameBlockItem("fire_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_FIRE))
|
||||
@JvmField
|
||||
val WATER_GEM = noSettingsItem("water_gem")
|
||||
val WATER_GEM = this.create("water_gem", itemNameBlockItem("water_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_WATER))
|
||||
@JvmField
|
||||
val GRASS_GEM = noSettingsItem("grass_gem")
|
||||
val GRASS_GEM = this.create("grass_gem", itemNameBlockItem("grass_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_GRASS))
|
||||
@JvmField
|
||||
val ELECTRIC_GEM = noSettingsItem("electric_gem")
|
||||
val ELECTRIC_GEM = this.create("electric_gem", itemNameBlockItem("electric_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_ELECTRIC))
|
||||
@JvmField
|
||||
val ICE_GEM = noSettingsItem("ice_gem")
|
||||
val ICE_GEM = this.create("ice_gem", itemNameBlockItem("ice_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_ICE))
|
||||
@JvmField
|
||||
val FIGHTING_GEM = noSettingsItem("fighting_gem")
|
||||
val FIGHTING_GEM = this.create("fighting_gem", itemNameBlockItem("fighting_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_FIGHTING))
|
||||
@JvmField
|
||||
val POISON_GEM = noSettingsItem("poison_gem")
|
||||
val POISON_GEM = this.create("poison_gem", itemNameBlockItem("poison_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_POISON))
|
||||
@JvmField
|
||||
val GROUND_GEM = noSettingsItem("ground_gem")
|
||||
val GROUND_GEM = this.create("ground_gem", itemNameBlockItem("ground_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_GROUND))
|
||||
@JvmField
|
||||
val FLYING_GEM = noSettingsItem("flying_gem")
|
||||
val FLYING_GEM = this.create("flying_gem", itemNameBlockItem("flying_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_FLYING))
|
||||
@JvmField
|
||||
val PSYCHIC_GEM = noSettingsItem("psychic_gem")
|
||||
val PSYCHIC_GEM = this.create("psychic_gem", itemNameBlockItem("psychic_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_PSYCHIC))
|
||||
@JvmField
|
||||
val BUG_GEM = noSettingsItem("bug_gem")
|
||||
val BUG_GEM = this.create("bug_gem", itemNameBlockItem("bug_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_BUG))
|
||||
@JvmField
|
||||
val ROCK_GEM = noSettingsItem("rock_gem")
|
||||
val ROCK_GEM = this.create("rock_gem", itemNameBlockItem("rock_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_ROCK))
|
||||
@JvmField
|
||||
val GHOST_GEM = noSettingsItem("ghost_gem")
|
||||
val GHOST_GEM = this.create("ghost_gem", itemNameBlockItem("ghost_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_GHOST))
|
||||
@JvmField
|
||||
val DRAGON_GEM = noSettingsItem("dragon_gem")
|
||||
val DRAGON_GEM = this.create("dragon_gem", itemNameBlockItem("dragon_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_DRAGON))
|
||||
@JvmField
|
||||
val DARK_GEM = noSettingsItem("dark_gem")
|
||||
val DARK_GEM = this.create("dark_gem", itemNameBlockItem("dark_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_DARK))
|
||||
@JvmField
|
||||
val STEEL_GEM = noSettingsItem("steel_gem")
|
||||
val STEEL_GEM = this.create("steel_gem", itemNameBlockItem("steel_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_STEEL))
|
||||
@JvmField
|
||||
val FAIRY_GEM = noSettingsItem("fairy_gem")
|
||||
val FAIRY_GEM = this.create("fairy_gem", itemNameBlockItem("fairy_gem", CobblemonBlocks.TYPE_GEM_CLUSTER_FAIRY))
|
||||
|
||||
//@JvmField
|
||||
//val BINDING_SOIL = blockItem("binding_soil", CobblemonBlocks.BINDING_SOIL)
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common
|
||||
|
||||
|
||||
import com.cobblemon.mod.common.block.campfirepot.CookingPotMenu
|
||||
import com.cobblemon.mod.common.block.modificationstation.ModificationStationMenu
|
||||
import com.cobblemon.mod.common.block.tmmachine.TMMachineMenu
|
||||
import com.cobblemon.mod.common.platform.PlatformRegistry
|
||||
import net.minecraft.core.Registry
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
@@ -19,7 +21,9 @@ import net.minecraft.world.inventory.MenuType
|
||||
|
||||
object CobblemonMenuType : PlatformRegistry<Registry<MenuType<*>>, ResourceKey<Registry<MenuType<*>>>, MenuType<*>>() {
|
||||
|
||||
val COOKING_POT = MenuType.register("cooking_pot", ::CookingPotMenu)
|
||||
val COOKING_POT = MenuType.register("cooking_pot_menu", ::CookingPotMenu)
|
||||
val TM_MACHINE = MenuType.register("tm_machine_menu", ::TMMachineMenu)
|
||||
val MODIFICATION_STATION = MenuType.register("modification_station_menu", ::ModificationStationMenu)
|
||||
|
||||
override val registry: Registry<MenuType<*>>
|
||||
get() = BuiltInRegistries.MENU
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.cobblemon.mod.common.modification_station.DisplayCaseVariantBlocks
|
||||
import com.cobblemon.mod.common.modification_station.DomeFossilVariantBlocks
|
||||
import com.cobblemon.mod.common.modification_station.ModificationVariantBlockRegistrar
|
||||
import com.cobblemon.mod.common.modification_station.PCVariantBlocks
|
||||
import com.cobblemon.mod.common.platform.PlatformRegistry
|
||||
import net.minecraft.core.Registry
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.resources.ResourceKey
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.level.block.Block
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
object CobblemonModificationBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<Block>>, Block>() {
|
||||
override val registry: Registry<Block> = BuiltInRegistries.BLOCK
|
||||
override val resourceKey: ResourceKey<Registry<Block>> = Registries.BLOCK
|
||||
|
||||
private val variantRegistrars: List<ModificationVariantBlockRegistrar> = listOf(
|
||||
DisplayCaseVariantBlocks,
|
||||
DomeFossilVariantBlocks,
|
||||
PCVariantBlocks
|
||||
)
|
||||
|
||||
override fun register(consumer: (ResourceLocation, Block) -> Unit) {
|
||||
variantRegistrars.forEach { registrar -> registrar.registerAll(consumer) }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun allVariantEntries(): Map<ResourceLocation, Block> = linkedMapOf<ResourceLocation, Block>().also { result ->
|
||||
variantRegistrars.forEach { registrar -> result.putAll(registrar.allEntries()) }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun variantIdentifier(block: Block): ResourceLocation? {
|
||||
variantRegistrars.forEach { registrar ->
|
||||
val identifier = registrar.variantIdentifier(block)
|
||||
if (identifier != null) {
|
||||
return identifier
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isVariant(block: Block): Boolean = variantIdentifier(block) != null
|
||||
|
||||
@JvmStatic
|
||||
fun displayCaseVariantBlocks(): Collection<Block> = DisplayCaseVariantBlocks.allBlocks()
|
||||
|
||||
@JvmStatic
|
||||
fun displayCaseTintedVariantBlocks(): Collection<Block> = DisplayCaseVariantBlocks.tintedVariantBlocks()
|
||||
|
||||
@JvmStatic
|
||||
fun displayCaseNonTintedVariantBlocks(): Collection<Block> = DisplayCaseVariantBlocks.nonTintedVariantBlocks()
|
||||
|
||||
@JvmStatic
|
||||
fun displayCaseVariantEntries(): Map<ResourceLocation, Block> = DisplayCaseVariantBlocks.allEntries()
|
||||
|
||||
@JvmStatic
|
||||
fun displayCaseVariantIdentifier(block: Block): ResourceLocation? = DisplayCaseVariantBlocks.variantIdentifier(block)
|
||||
|
||||
@JvmStatic
|
||||
fun domeFossilVariantBlocks(): Collection<Block> = DomeFossilVariantBlocks.allBlocks()
|
||||
|
||||
@JvmStatic
|
||||
fun pcVariantBlocks(): Collection<Block> = PCVariantBlocks.allBlocks()
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.cobblemon.mod.common.platform.PlatformRegistry
|
||||
import net.minecraft.core.Registry
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.resources.ResourceKey
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.item.BlockItem
|
||||
import net.minecraft.world.item.Item
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
object CobblemonModificationItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<Item>>, Item>() {
|
||||
override val registry: Registry<Item> = BuiltInRegistries.ITEM
|
||||
override val resourceKey: ResourceKey<Registry<Item>> = Registries.ITEM
|
||||
|
||||
private val variantItemsById = linkedMapOf<ResourceLocation, BlockItem>()
|
||||
|
||||
init {
|
||||
CobblemonModificationBlocks.allVariantEntries().forEach { (identifier, block) ->
|
||||
variantItemsById[identifier] = create(identifier, BlockItem(block, Item.Properties()))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun allVariantItems(): Collection<Item> = variantItemsById.values
|
||||
|
||||
@JvmStatic
|
||||
fun displayCaseVariantItems(): Collection<Item> = variantItemsById.values
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.moves.MoveSelector
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.MovesetBuilderAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.StringToObjectAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
|
||||
object CobblemonMovesetBuilders : JsonDataRegistry<MovesetBuilder> {
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderAdapter)
|
||||
.registerTypeAdapter(MoveSelector::class.java, StringToObjectAdapter(MoveSelector.selectors))
|
||||
.create()
|
||||
|
||||
override val typeToken = TypeToken.get(MovesetBuilder::class.java)
|
||||
override val resourcePath = "moveset_builders"
|
||||
override val id: ResourceLocation = cobblemonResource("moveset_builders")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override val observable = SimpleObservable<CobblemonMovesetBuilders>()
|
||||
|
||||
val movesetBuilders = mutableMapOf<ResourceLocation, MovesetBuilder>()
|
||||
|
||||
fun getOrThrow(id: ResourceLocation): MovesetBuilder {
|
||||
return movesetBuilders[id]
|
||||
?: throw IllegalArgumentException("Unknown MovesetBuilder id: $id")
|
||||
}
|
||||
|
||||
override fun sync(player: ServerPlayer) { /* These don't sync. But what if they did? Nah probably shouldn't sync. */ }
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, MovesetBuilder>) {
|
||||
movesetBuilders.clear()
|
||||
data.forEach { (id, value) -> value.id = id }
|
||||
movesetBuilders.putAll(data)
|
||||
observable.emit(this)
|
||||
}
|
||||
}
|
||||
@@ -25,15 +25,15 @@ import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
|
||||
import com.cobblemon.mod.common.client.net.debug.OpenRidingStatsDebugGUIHandler
|
||||
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.*
|
||||
import com.cobblemon.mod.common.client.net.gui.*
|
||||
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.RunPosableMoLangHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.SaccharineLogBlockParticlesHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormEntityParticleHandler
|
||||
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.ExpGainedDataPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.PokedexUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
|
||||
import com.cobblemon.mod.common.client.net.gui.*
|
||||
import com.cobblemon.mod.common.client.net.npc.CloseNPCEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.npc.OpenNPCEditorHandler
|
||||
import com.cobblemon.mod.common.client.net.orientation.ClientboundUpdateOrientationHandler
|
||||
@@ -137,16 +137,8 @@ import com.cobblemon.mod.common.net.messages.client.trade.TradeProcessStartedPac
|
||||
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.ExpGainedDataPacket
|
||||
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.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.client.ui.*
|
||||
import com.cobblemon.mod.common.net.messages.server.*
|
||||
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
|
||||
@@ -156,6 +148,7 @@ import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.behaviour.DamageOnCollisionPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.behaviour.SetEntityBehaviourPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.block.TMMachineTeachMovePacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonSelectedPacket
|
||||
@@ -189,6 +182,7 @@ import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateRide
|
||||
import com.cobblemon.mod.common.net.messages.server.starter.RequestStarterScreenPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.storage.SwapPCPartyPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokemonPacket
|
||||
import com.cobblemon.mod.common.net.messages.server.storage.party.PartyTutorialPacket
|
||||
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.*
|
||||
@@ -210,6 +204,7 @@ import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestResponseHan
|
||||
import com.cobblemon.mod.common.net.serverhandling.behaviour.DamageOnCollisionPacketHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.behaviour.SetEntityBehaviourHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.block.TMMachineTeachMoveHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectedHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartyPokemonSelectedHandler
|
||||
@@ -225,6 +220,7 @@ import com.cobblemon.mod.common.net.serverhandling.dialogue.InputToDialogueHandl
|
||||
import com.cobblemon.mod.common.net.serverhandling.evolution.AcceptEvolutionHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.npc.SaveNPCHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.orientation.ServerboundUpdateOrientationHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.storage.party.PartyTutorialPacketHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.PasturePokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.SetPastureConflictHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pasture.UnpastureAllPokemonHandler
|
||||
@@ -232,6 +228,8 @@ import com.cobblemon.mod.common.net.serverhandling.pasture.UnpasturePokemonHandl
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.interact.InteractPokemonHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.*
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.*
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkHandler
|
||||
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetItemHiddenHandler
|
||||
@@ -418,6 +416,7 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(DexEntrySyncPacket.ID, DexEntrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(SpawnBaitRegistrySyncPacket.ID, SpawnBaitRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(SeasoningRegistrySyncPacket.ID, SeasoningRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(ModificationVariantRegistrySyncPacket.ID, ModificationVariantRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(CallbackRegistrySyncPacket.ID, CallbackRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(CosmeticItemAssignmentSyncPacket.ID, CosmeticItemAssignmentSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
list.add(PacketRegisterInfo(BehaviourSyncPacket.ID, BehaviourSyncPacket::decode, DataRegistrySyncPacketHandler()))
|
||||
@@ -529,7 +528,7 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(ReleasePCPokemonPacket.ID, ReleasePCPokemonPacket::decode, ReleasePCPokemonHandler))
|
||||
list.add(PacketRegisterInfo(UnlinkPlayerFromPCPacket.ID, UnlinkPlayerFromPCPacket::decode, UnlinkPlayerFromPCHandler))
|
||||
|
||||
list.add(PacketRegisterInfo(RequestRenamePCBoxPacket.Companion.ID, RequestRenamePCBoxPacket.Companion::decode, RequestRenamePCBoxHandler))
|
||||
list.add(PacketRegisterInfo(RequestRenamePCBoxPacket.ID, RequestRenamePCBoxPacket::decode, RequestRenamePCBoxHandler))
|
||||
list.add(PacketRegisterInfo(PCBoxWallpapersPacket.ID, PCBoxWallpapersPacket::decode, PCBoxWallpapersHandler))
|
||||
list.add(PacketRegisterInfo(RequestChangePCBoxWallpaperPacket.ID, RequestChangePCBoxWallpaperPacket::decode, RequestChangePCBoxWallpaperHandler))
|
||||
list.add(PacketRegisterInfo(MarkPCBoxWallpapersSeenPacket.ID, MarkPCBoxWallpapersSeenPacket::decode, MarkPCBoxWallpapersSeenHandler))
|
||||
@@ -574,6 +573,7 @@ object CobblemonNetwork {
|
||||
|
||||
// Block entity
|
||||
list.add(PacketRegisterInfo(AdjustBlockEntityViewerCountPacket.ID, AdjustBlockEntityViewerCountPacket::decode, AdjustBlockEntityViewerCountHandler))
|
||||
list.add(PacketRegisterInfo(TMMachineTeachMovePacket.ID, TMMachineTeachMovePacket::decode, TMMachineTeachMoveHandler))
|
||||
|
||||
// Move select packets
|
||||
list.add(PacketRegisterInfo(MoveSelectedPacket.ID, MoveSelectedPacket::decode, MoveSelectedHandler))
|
||||
@@ -587,6 +587,9 @@ object CobblemonNetwork {
|
||||
list.add(PacketRegisterInfo(PartyPokemonMoveSelectedPacket.ID, PartyPokemonMoveSelectedPacket::decode, PartyPokemonMoveSelectedHandler))
|
||||
list.add(PacketRegisterInfo(PartyMoveSelectCancelledPacket.ID, PartyMoveSelectCancelledPacket::decode, PartyMoveSelectCancelledHandler))
|
||||
|
||||
//Party move tutorial toasts packets
|
||||
list.add(PacketRegisterInfo(PartyTutorialPacket.ID, PartyTutorialPacket::decode, PartyTutorialPacketHandler))
|
||||
|
||||
// Dialogue packets
|
||||
list.add(PacketRegisterInfo(EscapeDialoguePacket.ID, EscapeDialoguePacket::decode, EscapeDialogueHandler))
|
||||
list.add(PacketRegisterInfo(InputToDialoguePacket.ID, InputToDialoguePacket::decode, InputToDialogueHandler))
|
||||
@@ -607,6 +610,10 @@ object CobblemonNetwork {
|
||||
// Cooking
|
||||
list.add(PacketRegisterInfo(ToggleCookingPotLidPacket.ID, ToggleCookingPotLidPacket::decode, ToggleCookingPotLidHandler))
|
||||
|
||||
// TM Machine
|
||||
list.add(PacketRegisterInfo(SetActiveTMPacket.ID, SetActiveTMPacket::decode, SetActiveTMPacketHandler))
|
||||
list.add(PacketRegisterInfo(SetTMMachineContainerDataPacket.ID, SetTMMachineContainerDataPacket::decode, SetTMMachineContainerDataPacketHandler))
|
||||
|
||||
// Behaviour Packets
|
||||
list.add(PacketRegisterInfo(SetEntityBehaviourPacket.ID, SetEntityBehaviourPacket::decode, SetEntityBehaviourHandler))
|
||||
list.add(PacketRegisterInfo(DamageOnCollisionPacket.ID, DamageOnCollisionPacket::decode, DamageOnCollisionPacketHandler))
|
||||
@@ -620,4 +627,4 @@ object CobblemonNetwork {
|
||||
fun sendPacketToPlayer(player: ServerPlayer, packet: NetworkPacket<*>) {
|
||||
Cobblemon.implementation.networkManager.sendPacketToPlayer(player, packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.npc.PartyComposition
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
|
||||
object CobblemonPartyCompositions : JsonDataRegistry<PartyComposition> {
|
||||
override val id: ResourceLocation = cobblemonResource("party_compositions")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override val typeToken: TypeToken<PartyComposition> = TypeToken.get(PartyComposition::class.java)
|
||||
override val resourcePath: String = "party_compositions"
|
||||
override val observable = SimpleObservable<CobblemonPartyCompositions>()
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
|
||||
val partyCompositions = mutableMapOf<ResourceLocation, PartyComposition>()
|
||||
|
||||
override fun sync(player: ServerPlayer) {
|
||||
// probably worth syncing which exist, yeah. the deeper details probably not. I'll get to this eventually.
|
||||
}
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PartyComposition>) {
|
||||
data.entries.forEach { it.value.id = it.key }
|
||||
partyCompositions.clear()
|
||||
partyCompositions.putAll(data)
|
||||
observable.emit(this)
|
||||
Cobblemon.LOGGER.info("Loaded ${data.size} party compositions.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.npc.PartyPool
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.util.ScriptableIntRange
|
||||
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.MovesetBuilderReferenceAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.PokemonPropertiesAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.ScriptableIntRangeAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
|
||||
object CobblemonPartyPools : JsonDataRegistry<PartyPool> {
|
||||
override val id: ResourceLocation = cobblemonResource("party_pools")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override val typeToken: TypeToken<PartyPool> = TypeToken.get(PartyPool::class.java)
|
||||
override val resourcePath: String = "party_pools"
|
||||
override val observable = SimpleObservable<CobblemonPartyPools>()
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(PokemonProperties::class.java, PokemonPropertiesAdapter(saveLong = false))
|
||||
.registerTypeAdapter(IntRange::class.java, IntRangeAdapter)
|
||||
.registerTypeAdapter(ScriptableIntRange::class.java, ScriptableIntRangeAdapter)
|
||||
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderReferenceAdapter)
|
||||
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
|
||||
val partyPools = mutableMapOf<ResourceLocation, PartyPool>()
|
||||
|
||||
override fun sync(player: ServerPlayer) {
|
||||
// probably worth syncing. Knowing the details might help exploit the server? lot of effort for little though.
|
||||
}
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, PartyPool>) {
|
||||
data.entries.forEach { it.value.id = it.key }
|
||||
partyPools.clear()
|
||||
partyPools.putAll(data)
|
||||
observable.emit(this)
|
||||
}
|
||||
}
|
||||
@@ -200,6 +200,22 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
|
||||
@JvmField
|
||||
val TUMBLESTONE_STEP = this.create("block.tumblestone.step")
|
||||
|
||||
@JvmField
|
||||
val TM_USE = this.create("item.tm.use")
|
||||
@JvmField
|
||||
val TM_MACHINE_CRAFT = this.create("block.tm_machine.craft")
|
||||
@JvmField
|
||||
val TM_MACHINE_OFF = this.create("block.tm_machine.off")
|
||||
@JvmField
|
||||
val TM_MACHINE_ON = this.create("block.tm_machine.on")
|
||||
@JvmField
|
||||
val TM_MACHINE_OPEN = this.create("block.tm_machine.open")
|
||||
@JvmField
|
||||
val TM_MACHINE_CLOSE = this.create("block.tm_machine.close")
|
||||
@JvmField
|
||||
val TM_MACHINE_PLACE_DISC = this.create("block.tm_machine.place_disc")
|
||||
|
||||
|
||||
@JvmField
|
||||
val EVOLUTION_STONE_BLOCK_BREAK = this.create("block.evolution_stone_block.break")
|
||||
@JvmField
|
||||
|
||||
+3
-39
@@ -14,7 +14,7 @@ import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.advancements.critereon.ContextAwarePredicate
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import java.util.Optional
|
||||
import java.util.*
|
||||
|
||||
class PlantTumblestoneContext(val pos: BlockPos, val block: TumblestoneBlock)
|
||||
|
||||
@@ -29,42 +29,6 @@ class PlantTumblestoneCriterion(
|
||||
}
|
||||
|
||||
override fun matches(player: ServerPlayer, context: PlantTumblestoneContext): Boolean {
|
||||
return context.block.canGrow(context.pos, player.level())
|
||||
return context.block.canGrow(player.level().getBlockState(context.pos), context.pos, player.level())
|
||||
}
|
||||
}
|
||||
|
||||
///**
|
||||
// * A criterion condition for when a tumblestone block is planted
|
||||
// * @param id The identifier of the criterion
|
||||
// * @param predicate The predicate for the criterion
|
||||
// *
|
||||
// * @author Aethen
|
||||
// * @since 02/28/2024
|
||||
// */
|
||||
//class PlantTumblestoneCriterionCondition(id: Identifier, predicate: LootContextPredicate) :
|
||||
// SimpleCriterionCondition<PlantTumblestoneContext>(id, predicate) {
|
||||
// override fun toJson(json: JsonObject) {
|
||||
// // Add properties to json if needed. None needed for this criterion
|
||||
// }
|
||||
//
|
||||
// override fun fromJson(json: JsonObject) {
|
||||
// // Parse properties from json if needed. None needed for this criterion
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Checks if the tumblestone block can grow at the given position
|
||||
// * @param player The player that planted the tumblestone block
|
||||
// * @param context The context of the criterion
|
||||
// * @return True if the tumblestone block can grow at the given position, false otherwise
|
||||
// */
|
||||
// override fun matches(player: ServerPlayer, context: PlantTumblestoneContext): Boolean {
|
||||
// return context.tumbleStoneBlock.canGrow(context.pos, player.level())
|
||||
// }
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * The context of the [PlantTumblestoneCriterionCondition]
|
||||
// * @param pos The position of the tumblestone block
|
||||
// * @param tumbleStoneBlock The tumblestone block
|
||||
// */
|
||||
//open class PlantTumblestoneContext(var pos: BlockPos, var tumbleStoneBlock: TumblestoneBlock)
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangV
|
||||
import com.cobblemon.mod.common.entity.OmniPathingEntity
|
||||
import com.cobblemon.mod.common.entity.ai.CobblemonRandomSurfacePos
|
||||
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.mainThreadRuntime
|
||||
import com.cobblemon.mod.common.util.resolveFloat
|
||||
@@ -184,6 +185,12 @@ class WanderTaskConfig : SingleTaskConfig {
|
||||
|
||||
if (targetVec == null) continue
|
||||
|
||||
// Ignore pathing targets outside of the pasture zone if the pokemon is pastured.
|
||||
val tethering = (entity as? PokemonEntity)?.tethering
|
||||
if (tethering?.canRoamTo(BlockPos.containing(targetVec)) == false) {
|
||||
continue
|
||||
}
|
||||
|
||||
pos = applyHeightConstraints(
|
||||
pos = BlockPos.containing(targetVec),
|
||||
minimumHeight = minimumHeight,
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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.api.modification
|
||||
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
data class ModificationVariant(
|
||||
@Transient
|
||||
var identifier: ResourceLocation = ResourceLocation.withDefaultNamespace("missing"),
|
||||
val parent: ResourceLocation,
|
||||
val model: String,
|
||||
val id: Int = 0,
|
||||
val shape: List<List<Double>>? = emptyList(),
|
||||
val size: ModificationVariantSize? = null
|
||||
) {
|
||||
fun resolvedModelIdentifier(): ResourceLocation {
|
||||
return ResourceLocation.tryParse(model) ?: ResourceLocation.fromNamespaceAndPath(parent.namespace, model)
|
||||
}
|
||||
|
||||
fun resolvedBlockModelIdentifier(): ResourceLocation {
|
||||
val resolved = resolvedModelIdentifier()
|
||||
return if (resolved.path.startsWith("block/")) {
|
||||
resolved
|
||||
} else {
|
||||
ResourceLocation.fromNamespaceAndPath(resolved.namespace, "block/${resolved.path}")
|
||||
}
|
||||
}
|
||||
|
||||
fun createShape(): VoxelShape? {
|
||||
if (!shape.isNullOrEmpty()) {
|
||||
var combined = Shapes.empty()
|
||||
for (entry in shape) {
|
||||
if (entry.size != 6) {
|
||||
continue
|
||||
}
|
||||
|
||||
combined = Shapes.or(
|
||||
combined,
|
||||
Shapes.box(
|
||||
entry[0] / 16.0,
|
||||
entry[1] / 16.0,
|
||||
entry[2] / 16.0,
|
||||
entry[3] / 16.0,
|
||||
entry[4] / 16.0,
|
||||
entry[5] / 16.0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return if (combined.isEmpty) null else combined
|
||||
}
|
||||
|
||||
val sized = size ?: return null
|
||||
return Shapes.box(
|
||||
sized.offsetX / 16.0,
|
||||
sized.offsetY / 16.0,
|
||||
sized.offsetZ / 16.0,
|
||||
(sized.offsetX + sized.width) / 16.0,
|
||||
(sized.offsetY + sized.height) / 16.0,
|
||||
(sized.offsetZ + sized.depth) / 16.0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class ModificationVariantSize(
|
||||
val width: Double = 16.0,
|
||||
val height: Double = 16.0,
|
||||
val depth: Double = 16.0,
|
||||
val offsetX: Double = 0.0,
|
||||
val offsetY: Double = 0.0,
|
||||
val offsetZ: Double = 0.0
|
||||
)
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.api.modification
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.net.messages.client.data.ModificationVariantRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
|
||||
object ModificationVariants : JsonDataRegistry<ModificationVariant> {
|
||||
override val id: ResourceLocation = cobblemonResource("modification_variants")
|
||||
override val type: PackType = PackType.SERVER_DATA
|
||||
override val observable = SimpleObservable<ModificationVariants>()
|
||||
override val typeToken: TypeToken<ModificationVariant> = TypeToken.get(ModificationVariant::class.java)
|
||||
override val resourcePath = "modification_variants"
|
||||
override val gson: Gson = GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.setPrettyPrinting()
|
||||
.disableHtmlEscaping()
|
||||
.create()
|
||||
|
||||
private val variantsByIdentifier = linkedMapOf<ResourceLocation, ModificationVariant>()
|
||||
private val variantsByParent = hashMapOf<ResourceLocation, List<ModificationVariant>>()
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, ModificationVariant>) {
|
||||
val variants = data.mapNotNull { (identifier, variant) ->
|
||||
val parentExistsAsBlock = BuiltInRegistries.BLOCK.containsKey(variant.parent)
|
||||
val parentExistsAsItem = BuiltInRegistries.ITEM.containsKey(variant.parent)
|
||||
if (!parentExistsAsBlock && !parentExistsAsItem) {
|
||||
Cobblemon.LOGGER.warn(
|
||||
"Skipping modification variant {} because parent id {} does not exist as a block or item",
|
||||
identifier,
|
||||
variant.parent
|
||||
)
|
||||
return@mapNotNull null
|
||||
}
|
||||
if (!BuiltInRegistries.BLOCK.containsKey(identifier)) {
|
||||
Cobblemon.LOGGER.warn("Skipping modification variant {} because variant block {} does not exist", identifier, identifier)
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
variant.identifier = identifier
|
||||
variant
|
||||
}
|
||||
|
||||
reloadEntries(variants)
|
||||
}
|
||||
|
||||
fun reloadEntries(entries: Collection<ModificationVariant>) {
|
||||
val sorted = entries
|
||||
.sortedWith(compareBy<ModificationVariant> { it.id }.thenBy { it.identifier.toString() })
|
||||
|
||||
variantsByIdentifier.clear()
|
||||
variantsByParent.clear()
|
||||
|
||||
sorted.forEach { variant ->
|
||||
variantsByIdentifier[variant.identifier] = variant
|
||||
}
|
||||
|
||||
variantsByParent.putAll(sorted.groupBy { it.parent })
|
||||
|
||||
Cobblemon.LOGGER.info("Loaded {} modification variants", variantsByIdentifier.size)
|
||||
observable.emit(this)
|
||||
}
|
||||
|
||||
override fun sync(player: ServerPlayer) {
|
||||
ModificationVariantRegistrySyncPacket(all()).sendToPlayer(player)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun all(): List<ModificationVariant> = variantsByIdentifier.values.toList()
|
||||
|
||||
@JvmStatic
|
||||
fun forParent(parent: ResourceLocation): List<ModificationVariant> = variantsByParent[parent] ?: emptyList()
|
||||
|
||||
@JvmStatic
|
||||
fun getByIdentifier(identifier: ResourceLocation): ModificationVariant? = variantsByIdentifier[identifier]
|
||||
}
|
||||
@@ -145,6 +145,7 @@ import net.minecraft.world.entity.item.ItemEntity
|
||||
import net.minecraft.world.entity.monster.Monster
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.enchantment.EnchantmentHelper
|
||||
import net.minecraft.world.level.ClipContext
|
||||
import net.minecraft.world.level.GameType
|
||||
import net.minecraft.world.level.Level
|
||||
@@ -871,6 +872,8 @@ object MoLangFunctions {
|
||||
|
||||
DoubleValue(value)
|
||||
}
|
||||
map.put("get_starter_uuid") { _ -> Cobblemon.playerDataManager.getGenericData(player).starterUUID?.let{ StringValue(it.toString()) } ?: DoubleValue.ZERO }
|
||||
map.put("has_chosen_starter") { _ -> DoubleValue(Cobblemon.playerDataManager.getGenericData(player).starterSelected) }
|
||||
}
|
||||
map
|
||||
}
|
||||
@@ -891,6 +894,15 @@ object MoLangFunctions {
|
||||
map.put("is_of") { params -> DoubleValue(holder.`is`(params.getString(0).asIdentifierDefaultingNamespace())) }
|
||||
map.put("is_in") { params -> DoubleValue(holder.`is`(TagKey.create(Registries.ITEM, params.getString(0).replace("#", "").asIdentifierDefaultingNamespace()))) }
|
||||
map.put("is_food") { params -> DoubleValue(stack.has(DataComponents.FOOD)) }
|
||||
map.put("is_enchanted") { params -> DoubleValue(stack.hasFoil()) }
|
||||
map.put("has_enchantment") { params ->
|
||||
val enchantmentId = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
val minLevel = params.getIntOrNull(1) ?: 1
|
||||
val enchantmentRegistry = registryAccess.registryOrThrow(Registries.ENCHANTMENT)
|
||||
val holder = enchantmentRegistry.getHolder(enchantmentId).orElse(null) ?: return@put DoubleValue.ZERO
|
||||
val level = EnchantmentHelper.getItemEnchantmentLevel(holder, stack)
|
||||
return@put DoubleValue(level >= minLevel)
|
||||
}
|
||||
return@mutableListOf map
|
||||
}
|
||||
)
|
||||
@@ -1830,6 +1842,30 @@ object MoLangFunctions {
|
||||
map.put("remove_cosmetic_item") { _ ->
|
||||
pokemon.removeCosmeticItem()
|
||||
}
|
||||
map.put("marks") { _ -> pokemon.marks.asArrayValue { StringValue(it.identifier.toString()) } }
|
||||
map.put("has_mark") { params ->
|
||||
var hasMark = false
|
||||
val identifier = params.getString(0).asIdentifierDefaultingNamespace()
|
||||
val mark = Marks.getByIdentifier(identifier)
|
||||
if (mark != null) {
|
||||
hasMark = pokemon.marks.contains(mark)
|
||||
}
|
||||
|
||||
DoubleValue(hasMark)
|
||||
}
|
||||
map.put("remove_marks") { params ->
|
||||
var removedMark = false
|
||||
for (param in params.params) {
|
||||
val identifier = param.asString().asIdentifierDefaultingNamespace()
|
||||
val mark = Marks.getByIdentifier(identifier)
|
||||
if (mark != null) {
|
||||
pokemon.exchangeMark(mark, false)
|
||||
removedMark = true
|
||||
}
|
||||
}
|
||||
|
||||
DoubleValue(removedMark)
|
||||
}
|
||||
map.put("add_marks") { params ->
|
||||
var appliedMark = false
|
||||
for (param in params.params) {
|
||||
@@ -1929,8 +1965,7 @@ object MoLangFunctions {
|
||||
return@put DoubleValue(if (pokemon.addRideBoost(stat, value)) 1.0 else 0.0)
|
||||
}
|
||||
map.put("initialize_moveset") { params ->
|
||||
val preferLatest = params.getBooleanOrNull(0) ?: true
|
||||
pokemon.initializeMoveset(preferLatest)
|
||||
pokemon.initializeMovesetFromDefault()
|
||||
return@put DoubleValue.ONE
|
||||
}
|
||||
map.put("validate_moveset") { params ->
|
||||
@@ -2815,8 +2850,8 @@ object MoLangFunctions {
|
||||
}
|
||||
variable
|
||||
}
|
||||
else -> null
|
||||
} ?: throw IllegalArgumentException("Invalid NBT element type: ${nbt.type}")
|
||||
else -> VariableStruct()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
class BenchedMoves : Iterable<BenchedMove> {
|
||||
var changeFunction: ((BenchedMoves) -> Unit) = {}
|
||||
// var moveAddedFunction: (BenchedMove) -> Unit = {}
|
||||
private var emit = true
|
||||
private val benchedMoves = mutableListOf<BenchedMove>()
|
||||
|
||||
@@ -58,6 +59,9 @@ class BenchedMoves : Iterable<BenchedMove> {
|
||||
return false
|
||||
}
|
||||
|
||||
/*if (emit) {
|
||||
moveAddedFunction(benchedMove)
|
||||
}*/
|
||||
doThenEmit { benchedMoves.add(benchedMove) }
|
||||
return true
|
||||
}
|
||||
@@ -174,4 +178,4 @@ data class BenchedMove(val moveTemplate: MoveTemplate, val ppRaisedStages: Int)
|
||||
Codec.intRange(0, 3).fieldOf(DataKeys.POKEMON_MOVESET_RAISED_PP_STAGES).forGetter(BenchedMove::ppRaisedStages)
|
||||
).apply(it, ::BenchedMove) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.api.moves
|
||||
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
/**
|
||||
* This is all you need tbh. A moveset builder that picks moves from up to 4 slots of move selectors.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 5th, 2025
|
||||
*/
|
||||
class DefaultMovesetBuilder : MovesetBuilder {
|
||||
companion object {
|
||||
val TYPE = cobblemonResource("default")
|
||||
}
|
||||
|
||||
override val type: ResourceLocation = TYPE
|
||||
override var id: ResourceLocation = cobblemonResource("temp") // replaced on load.
|
||||
|
||||
val slot1: List<MoveSelector> = listOf()
|
||||
val slot2: List<MoveSelector> = listOf()
|
||||
val slot3: List<MoveSelector> = listOf()
|
||||
val slot4: List<MoveSelector> = listOf()
|
||||
|
||||
override fun build(form: FormData, level: Int): MoveSet {
|
||||
val chosenMoves = mutableSetOf<MoveTemplate>()
|
||||
tryAddMoveFromSlot(form, level, slot1, chosenMoves)
|
||||
tryAddMoveFromSlot(form, level, slot2, chosenMoves)
|
||||
tryAddMoveFromSlot(form, level, slot3, chosenMoves)
|
||||
tryAddMoveFromSlot(form, level, slot4, chosenMoves)
|
||||
if (chosenMoves.isEmpty()) {
|
||||
chosenMoves.add(Moves.getExceptional())
|
||||
}
|
||||
val moveset = MoveSet()
|
||||
chosenMoves.map(MoveTemplate::create).forEach(moveset::add)
|
||||
return moveset
|
||||
}
|
||||
|
||||
fun tryAddMoveFromSlot(form: FormData, level: Int, selectors: List<MoveSelector>, chosenMoves: MutableSet<MoveTemplate>) {
|
||||
for (selector in selectors) {
|
||||
val move = selector(form, form.moves, level, chosenMoves)
|
||||
if (move != null) {
|
||||
chosenMoves.add(move)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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.api.moves
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
|
||||
import com.cobblemon.mod.common.api.pokemon.moves.Learnset
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stats
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
|
||||
/**
|
||||
* A function that selects a move from a given [Learnset] for a particular [FormData] at a certain level,
|
||||
* taking into account any moves that have already been chosen for the moveset.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 5th, 2025
|
||||
*/
|
||||
fun interface MoveSelector {
|
||||
operator fun invoke(form: FormData, learnset: Learnset, level: Int, chosenMoves: Set<MoveTemplate>): MoveTemplate?
|
||||
|
||||
companion object {
|
||||
val NONE = MoveSelector { _, _, _, _ -> null }
|
||||
val LAST_LEVELUP = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
|
||||
levels.forEach {
|
||||
val moves = (learnset.levelUpMoves[it] ?: return@forEach) - chosenMoves
|
||||
if (moves.isNotEmpty()) {
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
}
|
||||
return@MoveSelector null
|
||||
}
|
||||
val LAST_OFFENSIVE = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
|
||||
levels.forEach {
|
||||
val moves = (learnset.levelUpMoves[it]?.filter { it.damageCategory != DamageCategories.STATUS } ?: return@forEach) - chosenMoves
|
||||
if (moves.isNotEmpty()) {
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
}
|
||||
return@MoveSelector null
|
||||
}
|
||||
val LAST_SUITABLE_OFFENSIVE = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
|
||||
val suitableCategories = if (form.baseStats[Stats.ATTACK]!! > form.baseStats[Stats.SPECIAL_ATTACK]!!) {
|
||||
setOf(DamageCategories.PHYSICAL)
|
||||
} else if (form.baseStats[Stats.SPECIAL_ATTACK]!! > form.baseStats[Stats.ATTACK]!!) {
|
||||
setOf(DamageCategories.SPECIAL)
|
||||
} else {
|
||||
setOf(DamageCategories.PHYSICAL, DamageCategories.SPECIAL)
|
||||
}
|
||||
levels.forEach {
|
||||
val moves = (learnset.levelUpMoves[it]?.filter { it.damageCategory in suitableCategories } ?: return@forEach) - chosenMoves
|
||||
if (moves.isNotEmpty()) {
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
}
|
||||
return@MoveSelector null
|
||||
}
|
||||
val LAST_STATUS = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val levels = learnset.levelUpMoves.keys.filter { it <= level }.sortedDescending()
|
||||
levels.forEach {
|
||||
val moves = (learnset.levelUpMoves[it]?.filter { it.damageCategory == DamageCategories.STATUS } ?: return@forEach) - chosenMoves
|
||||
if (moves.isNotEmpty()) {
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
}
|
||||
return@MoveSelector null
|
||||
}
|
||||
val STAB = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val stabMoves = moves.filter { it.elementalType in form.types && it.damageCategory != DamageCategories.STATUS }
|
||||
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val STAB_PHYSICAL = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val stabMoves = moves.filter { it.elementalType in form.types && it.damageCategory == DamageCategories.PHYSICAL }
|
||||
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val STAB_SPECIAL = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val stabMoves = moves.filter { it.elementalType in form.types && it.damageCategory == DamageCategories.SPECIAL }
|
||||
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val PHYSICAL = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val physicalMoves = moves.filter { it.damageCategory == DamageCategories.PHYSICAL }
|
||||
return@MoveSelector physicalMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val SPECIAL = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val specialMoves = moves.filter { it.damageCategory == DamageCategories.SPECIAL }
|
||||
return@MoveSelector specialMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val OFFENSIVE = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val offensiveMoves = moves.filter { it.damageCategory != DamageCategories.STATUS }
|
||||
return@MoveSelector offensiveMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val RANDOM_LEVELUP = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val STATUS = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.getLevelUpMovesUpTo(level) - chosenMoves
|
||||
val statusMoves = moves.filter { it.damageCategory == DamageCategories.STATUS }
|
||||
return@MoveSelector statusMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val TM = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.tmMoves - chosenMoves - learnset.getLevelUpMovesUpTo(level)
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val STAB_TM = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.tmMoves - chosenMoves - learnset.getLevelUpMovesUpTo(level)
|
||||
val stabMoves = moves.filter { it.elementalType in form.types }
|
||||
return@MoveSelector stabMoves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
val EGG = MoveSelector { form, learnset, level, chosenMoves ->
|
||||
val moves = learnset.eggMoves - chosenMoves - learnset.getLevelUpMovesUpTo(level)
|
||||
return@MoveSelector moves.weightedSelection { it.getSelectionWeight(form) }
|
||||
}
|
||||
|
||||
val selectors = mutableMapOf(
|
||||
"none" to NONE,
|
||||
"last_levelup" to LAST_LEVELUP,
|
||||
"last_offensive" to LAST_OFFENSIVE,
|
||||
"last_suitable_offensive" to LAST_SUITABLE_OFFENSIVE,
|
||||
"last_status" to LAST_STATUS,
|
||||
"levelup" to RANDOM_LEVELUP,
|
||||
"stab" to STAB,
|
||||
"stab_physical" to STAB_PHYSICAL,
|
||||
"stab_special" to STAB_SPECIAL,
|
||||
"physical" to PHYSICAL,
|
||||
"special" to SPECIAL,
|
||||
"offensive" to OFFENSIVE,
|
||||
"status" to STATUS,
|
||||
"tm" to TM,
|
||||
"stab_tm" to STAB_TM,
|
||||
"egg" to EGG
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ class MoveSet : Iterable<Move> {
|
||||
*/
|
||||
fun getMoves() = moves.filterNotNull()
|
||||
fun getMovesWithNulls() = moves.toList()
|
||||
fun getMoveTemplates() = getMoves().map { move -> move.template }
|
||||
fun hasSpace() = moves.any { it == null }
|
||||
|
||||
/**
|
||||
@@ -222,4 +223,4 @@ class MoveSet : Iterable<Move> {
|
||||
{ moveSet -> moveSet.toList() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@ import com.bedrockk.molang.runtime.struct.MoStruct
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategory
|
||||
import com.cobblemon.mod.common.api.types.ElementalType
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.battles.MoveTarget
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.codec.CodecUtils
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
@@ -53,7 +53,8 @@ open class MoveTemplate(
|
||||
val pp: Int,
|
||||
val priority: Int,
|
||||
val critRatio: Double,
|
||||
val effectChances: Array<Double>
|
||||
val effectChances: Array<Double>,
|
||||
val weight: Float
|
||||
) {
|
||||
val struct: MoStruct by lazy {
|
||||
QueryStruct(hashMapOf())
|
||||
@@ -69,9 +70,9 @@ open class MoveTemplate(
|
||||
.addFunction("max_pp") { DoubleValue(maxPp) }
|
||||
.addFunction("priority") { DoubleValue(priority) }
|
||||
.addFunction("crit_ratio") { DoubleValue(critRatio) }
|
||||
.addFunction("weight") { DoubleValue(weight.toDouble()) }
|
||||
}
|
||||
|
||||
|
||||
val displayName: MutableComponent
|
||||
get() = lang("move.$name")
|
||||
val description: MutableComponent
|
||||
@@ -89,7 +90,8 @@ open class MoveTemplate(
|
||||
pp = 5,
|
||||
priority = 0,
|
||||
critRatio = 0.0,
|
||||
effectChances = emptyArray()
|
||||
effectChances = emptyArray(),
|
||||
weight = 0F,
|
||||
)
|
||||
|
||||
companion object {
|
||||
@@ -160,4 +162,12 @@ open class MoveTemplate(
|
||||
}
|
||||
return this.elementalType
|
||||
}
|
||||
|
||||
fun getSelectionWeight(form: FormData): Float {
|
||||
return if (this in form.signatureMoves) {
|
||||
weight * MovesetBuilder.signatureMoveWeightMultiplier
|
||||
} else {
|
||||
weight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,15 @@ package com.cobblemon.mod.common.api.moves
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.data.DataRegistry
|
||||
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
|
||||
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.battles.MoveTarget
|
||||
import com.cobblemon.mod.common.battles.runner.ShowdownService
|
||||
import com.cobblemon.mod.common.net.messages.client.data.MovesRegistrySyncPacket
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.fromJson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
@@ -46,6 +46,14 @@ object Moves : DataRegistry {
|
||||
this.idMapping.clear()
|
||||
this.moveScripts.clear()
|
||||
|
||||
val weights = mutableMapOf<String, Number>()
|
||||
manager.getResourceStack(cobblemonResource("move_weights.json")).forEach {
|
||||
it.open().use {
|
||||
val map = GsonBuilder().create().fromJson<Map<String, Number>>(it.reader())
|
||||
weights.putAll(map)
|
||||
}
|
||||
}
|
||||
|
||||
ShowdownService.service.resetRegistryData("move")
|
||||
manager.listResources("moves") { it.path.endsWith(".js") }.forEach { (identifier, resource) ->
|
||||
resource.open().use { stream ->
|
||||
@@ -75,6 +83,7 @@ object Moves : DataRegistry {
|
||||
val priority = jsMove.get("priority").asInt
|
||||
val critRatio = jsMove.get("critRatio")?.asDouble ?: 1.0
|
||||
val effectChances = arrayListOf<Double>()
|
||||
val weight = weights[id]?.toFloat() ?: 50F
|
||||
val secondariesMember = jsMove.get("secondaries")
|
||||
val secondaryMember = jsMove.get("secondary")
|
||||
if (secondariesMember != null && secondariesMember is JsonArray) {
|
||||
@@ -92,7 +101,20 @@ object Moves : DataRegistry {
|
||||
effectChances += secondaryMember.get("chance").asDouble
|
||||
}
|
||||
}
|
||||
val move = MoveTemplate(id, num, elementalType, damageCategory, power, target, accuracy, pp, priority, critRatio, effectChances.toTypedArray())
|
||||
val move = MoveTemplate(
|
||||
name = id,
|
||||
num = num,
|
||||
elementalType = elementalType,
|
||||
damageCategory = damageCategory,
|
||||
power = power,
|
||||
target = target,
|
||||
accuracy = accuracy,
|
||||
pp = pp,
|
||||
priority = priority,
|
||||
critRatio = critRatio,
|
||||
effectChances = effectChances.toTypedArray(),
|
||||
weight = weight
|
||||
)
|
||||
this.register(move)
|
||||
} catch (e: Exception) {
|
||||
Cobblemon.LOGGER.error("Caught exception trying to resolve the move '{}'", id, e)
|
||||
@@ -113,7 +135,7 @@ object Moves : DataRegistry {
|
||||
@JvmStatic
|
||||
fun getByNameOrDummy(name: String) = allMoves[name.lowercase()] ?: MoveTemplate.dummy(name.lowercase())
|
||||
@JvmStatic
|
||||
fun getExceptional() = getByName("tackle") ?: allMoves.values.random()
|
||||
fun getExceptional() = getByName("tackle") ?: allMoves.values.first()
|
||||
@JvmStatic
|
||||
fun count() = allMoves.size
|
||||
@JvmStatic
|
||||
@@ -129,5 +151,4 @@ object Moves : DataRegistry {
|
||||
this.allMoves[move.name] = move
|
||||
this.idMapping[move.num] = move
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.api.moves
|
||||
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
/**
|
||||
* Builders a moveset for a particular [FormData] and level. Probably only really need [DefaultMovesetBuilder] though.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 5th, 2025
|
||||
*/
|
||||
interface MovesetBuilder {
|
||||
/** The ID of this builder. It's autofilled from the registry loader. */
|
||||
var id: ResourceLocation
|
||||
/** The subtype designation for this moveset builder. Generally unused once read from JSON. */
|
||||
val type: ResourceLocation
|
||||
|
||||
/** Builds a brand new moveset for the [form] and [level]. */
|
||||
fun build(form: FormData, level: Int): MoveSet
|
||||
|
||||
companion object {
|
||||
/** Weight multiplier for moves that are in the form's signatureMoves list. Makes them way more likely to be chosen. */
|
||||
var signatureMoveWeightMultiplier = 5F
|
||||
/** Subtypes of MovesetBuilder. I personally doubt you'll need any other subtypes, but hey. */
|
||||
val movesetBuilderTypes = mutableMapOf<ResourceLocation, Class<out MovesetBuilder>>(
|
||||
cobblemonResource("default") to DefaultMovesetBuilder::class.java
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,14 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.npc
|
||||
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.ComposedPoolPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.PoolPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.ScriptPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.partyproviders.SimplePartyProvider
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.google.gson.JsonElement
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
@@ -25,14 +27,15 @@ import net.minecraft.server.level.ServerPlayer
|
||||
*/
|
||||
interface NPCPartyProvider {
|
||||
companion object {
|
||||
val types = mutableMapOf<String, (String) -> NPCPartyProvider>(
|
||||
SimplePartyProvider.TYPE to { SimplePartyProvider() },
|
||||
PoolPartyProvider.TYPE to { PoolPartyProvider() },
|
||||
ScriptPartyProvider.TYPE to { ScriptPartyProvider() }
|
||||
val types = mutableMapOf(
|
||||
SimplePartyProvider.TYPE to ::SimplePartyProvider,
|
||||
PoolPartyProvider.TYPE to ::PoolPartyProvider,
|
||||
ScriptPartyProvider.TYPE to ::ScriptPartyProvider,
|
||||
ComposedPoolPartyProvider.TYPE to ::ComposedPoolPartyProvider
|
||||
)
|
||||
}
|
||||
|
||||
val type: String
|
||||
val type: ResourceLocation
|
||||
val isStatic: Boolean
|
||||
fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer> = emptyList()): NPCPartyStore
|
||||
// Why did I opt for manual JSON loading??? I must have had a reason but I can't remember. Maybe for S2C? Use a codec doofus
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.api.npc
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* A kind of definition for how to compose a party of Pokémon given a [PartyPool].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 7th, 2025
|
||||
*/
|
||||
class PartyComposition {
|
||||
var id = cobblemonResource("dummy_composition")
|
||||
// val displayName = "cobblemon.party_composition.$id" // dunno if i will use this yet
|
||||
val scrambleOrder: Boolean = false
|
||||
val slot1 = listOf<String>()
|
||||
val slot2 = listOf<String>()
|
||||
val slot3 = listOf<String>()
|
||||
val slot4 = listOf<String>()
|
||||
val slot5 = listOf<String>()
|
||||
val slot6 = listOf<String>()
|
||||
|
||||
fun compose(pool: PartyPool, level: Int, aspects: Set<String>, desiredPokemonCount: Int, runtime: MoLangRuntime, random: Random = Random.Default): List<Pokemon> {
|
||||
val chosenEntries = mutableListOf<PartyPool.PoolEntry>()
|
||||
val availableEntries = pool.entries.filter { level in it.npcLevels.resolve(runtime) && (it.npcAspects.isEmpty() || it.npcAspects.all(aspects::contains)) }
|
||||
|
||||
for (labelSet in listOf(slot1, slot2, slot3, slot4, slot5, slot6)) {
|
||||
pool.tryChoosingEntry(labelSet, runtime, random, availableEntries, chosenEntries)
|
||||
if (chosenEntries.size >= desiredPokemonCount) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
val finalEntries = if (scrambleOrder) {
|
||||
chosenEntries.shuffled(random)
|
||||
} else {
|
||||
chosenEntries
|
||||
}
|
||||
|
||||
val pokemon = finalEntries.map { entry ->
|
||||
val levelVariation = entry.levelVariation.resolve(runtime)
|
||||
val lvlVariation = if (levelVariation.first == levelVariation.last) {
|
||||
levelVariation.first
|
||||
} else {
|
||||
levelVariation.random(random)
|
||||
}
|
||||
|
||||
val finalLevel = level + lvlVariation
|
||||
val properties = entry.pokemon.copy()
|
||||
properties.level = properties.level ?: finalLevel
|
||||
val pokemon = properties.create()
|
||||
entry.movesetBuilders.randomOrNull()?.let(pokemon::initializeMovesetFrom)
|
||||
pokemon
|
||||
}
|
||||
|
||||
return pokemon
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.api.npc
|
||||
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.util.ScriptableIntRange
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.toProperties
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* A pool of Pokémon to choose from, typically for generating a party for an NPC. The entries
|
||||
* of the pool work on a weight and label system which is used as part of a [PartyComposition].
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 7th, 2025
|
||||
*/
|
||||
class PartyPool {
|
||||
var id = cobblemonResource("dummy")
|
||||
val displayName = "cobblemon.party_pool.$id"
|
||||
val entries = mutableListOf<PoolEntry>()
|
||||
|
||||
class PoolEntry {
|
||||
val pokemon: PokemonProperties = "pikachu".toProperties()
|
||||
val labels: List<String> = emptyList()
|
||||
val npcAspects: List<String> = emptyList()
|
||||
val required: List<String> = emptyList()
|
||||
val excluded: List<String> = emptyList()
|
||||
val maxSelectableTimes: ExpressionLike = "6".asExpressionLike()
|
||||
val levelVariation: ScriptableIntRange = ScriptableIntRange(0, 0)
|
||||
val npcLevels: ScriptableIntRange = ScriptableIntRange(1, 100)
|
||||
val movesetBuilders: List<MovesetBuilder> = emptyList()
|
||||
val weight: ExpressionLike = "50".asExpressionLike()
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes through each of the labels in order, trying to find a valid choice. Once it finds one, it skips the later
|
||||
* labels and adds the value to [chosenEntries] and returns. If none of the labels could find a valid entry, nothing is added.
|
||||
*/
|
||||
fun tryChoosingEntry(
|
||||
labels: List<String>,
|
||||
runtime: MoLangRuntime,
|
||||
random: Random,
|
||||
availableEntries: List<PoolEntry>,
|
||||
chosenEntries: MutableList<PoolEntry>,
|
||||
) {
|
||||
for (label in labels) {
|
||||
val entries = availableEntries
|
||||
.filter { label in it.labels || label.lowercase() == "any" }
|
||||
.filter { entry ->
|
||||
// Check required labels
|
||||
entry.required.all { it in labels } &&
|
||||
// Check excluded labels
|
||||
entry.excluded.none { it in labels } &&
|
||||
// Check max selectable times
|
||||
chosenEntries.count { it == entry } < entry.maxSelectableTimes.resolveFloat(runtime)
|
||||
}
|
||||
|
||||
if (entries.isNotEmpty()) {
|
||||
val selectedEntry = entries.weightedSelection(random) { it.weight.resolveFloat(runtime) } ?: return
|
||||
chosenEntries.add(selectedEntry)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.api.npc.partyproviders
|
||||
|
||||
import com.bedrockk.molang.Expression
|
||||
import com.bedrockk.molang.runtime.MoLangRuntime
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.CobblemonPartyCompositions
|
||||
import com.cobblemon.mod.common.CobblemonPartyPools
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
|
||||
import com.cobblemon.mod.common.api.npc.PartyComposition
|
||||
import com.cobblemon.mod.common.api.npc.PartyPool
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
import com.cobblemon.mod.common.util.asExpression
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.resolveInt
|
||||
import com.cobblemon.mod.common.util.withQueryValue
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import kotlin.random.Random
|
||||
import kotlin.ranges.random
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
/**
|
||||
* An [NPCPartyProvider] which uses a [PartyComposition] and [PartyPool] to generate a party.
|
||||
*
|
||||
* There are controls for minimum and maximum Pokémon count, as well as whether to use a fixed random seed for the NPC.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since December 8th, 2025
|
||||
*/
|
||||
class ComposedPoolPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
val TYPE = cobblemonResource("composed_pool")
|
||||
}
|
||||
|
||||
override val type = TYPE
|
||||
override var isStatic: Boolean = true
|
||||
var useFixedRandom: Boolean = false
|
||||
var minPokemon: Expression = "1".asExpression()
|
||||
var maxPokemon: Expression = "6".asExpression()
|
||||
lateinit var pool: PartyPool
|
||||
lateinit var composition: PartyComposition
|
||||
|
||||
override fun loadFromJSON(json: JsonElement) {
|
||||
json as JsonObject
|
||||
isStatic = json.getAsJsonPrimitive("isStatic")?.asBoolean ?: true
|
||||
useFixedRandom = json.getAsJsonPrimitive("useFixedRandom")?.asBoolean ?: false
|
||||
pool = CobblemonPartyPools.partyPools[json.get("pool").asString.asIdentifierDefaultingNamespace()]
|
||||
?: throw IllegalArgumentException("Unknown PartyPool id: ${json.get("pool").asString}")
|
||||
composition = CobblemonPartyCompositions.partyCompositions[json.get("composition").asString.asIdentifierDefaultingNamespace()]
|
||||
?: throw IllegalArgumentException("Unknown PartyComposition id: ${json.get("composition").asString}")
|
||||
minPokemon = json.getAsJsonPrimitive("minPokemon").asString?.asExpression() ?: "1".asExpression()
|
||||
maxPokemon = json.getAsJsonPrimitive("maxPokemon").asString?.asExpression() ?: "6".asExpression()
|
||||
}
|
||||
|
||||
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
|
||||
val runtime = MoLangRuntime().setup().withQueryValue("npc", npc.struct)
|
||||
val random = if (useFixedRandom) Random(npc.uuid.hashCode()) else Random.Default
|
||||
runtime.withQueryValue("level", DoubleValue(level))
|
||||
runtime.withQueryValue("players", players.asArrayValue { it.asMoLangValue() })
|
||||
if (players.size == 1) {
|
||||
// This is for the convenience, most cases will be pvn one player
|
||||
runtime.withQueryValue("player", players.first().asMoLangValue())
|
||||
}
|
||||
val minPokemon = runtime.resolveInt(this.minPokemon)
|
||||
val maxPokemon = runtime.resolveInt(this.maxPokemon)
|
||||
val desiredPokemonCount = (minPokemon..maxPokemon).random(random)
|
||||
|
||||
val pokemonList = composition.compose(
|
||||
pool = pool,
|
||||
level = level,
|
||||
aspects = npc.aspects,
|
||||
desiredPokemonCount = desiredPokemonCount,
|
||||
runtime = runtime,
|
||||
random = random
|
||||
)
|
||||
|
||||
val partyStore = NPCPartyStore(npc)
|
||||
pokemonList.forEach(partyStore::add)
|
||||
return partyStore
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -48,7 +48,7 @@ import kotlin.random.Random
|
||||
*/
|
||||
class PoolPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "pool"
|
||||
val TYPE = cobblemonResource("pool")
|
||||
}
|
||||
|
||||
@Transient
|
||||
@@ -150,10 +150,10 @@ class PoolPartyProvider : NPCPartyProvider {
|
||||
val instance = selected.pokemon.copy().also { it.level = it.level ?: dictatedLevel ?: randomLevel }.create()
|
||||
party.add(instance)
|
||||
}
|
||||
if (party.none()) {
|
||||
LOGGER.error("${npc} has no Pokemon on Party")
|
||||
}
|
||||
|
||||
if (party.none()) {
|
||||
LOGGER.error("$npc had no Pokémon produced by their party provider")
|
||||
}
|
||||
}
|
||||
|
||||
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
class ScriptPartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "script"
|
||||
val TYPE = cobblemonResource("script")
|
||||
}
|
||||
|
||||
override val type = TYPE
|
||||
|
||||
+2
-1
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
|
||||
import com.cobblemon.mod.common.entity.npc.NPCEntity
|
||||
import com.cobblemon.mod.common.util.asArrayValue
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.cobblemon.mod.common.util.getBooleanOrNull
|
||||
import com.cobblemon.mod.common.util.toProperties
|
||||
import com.google.gson.JsonElement
|
||||
@@ -29,7 +30,7 @@ import net.minecraft.server.level.ServerPlayer
|
||||
*/
|
||||
class SimplePartyProvider : NPCPartyProvider {
|
||||
companion object {
|
||||
const val TYPE = "simple"
|
||||
val TYPE = cobblemonResource("simple")
|
||||
}
|
||||
|
||||
@Transient
|
||||
|
||||
@@ -143,6 +143,8 @@ object CobblemonPermissions {
|
||||
val CHANGE_BOX_COUNT = this.create("${COMMAND_PREFIX}boxcount", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
@JvmStatic
|
||||
val SPECTATE_BATTLE = this.create("${COMMAND_PREFIX}spectatebattle", PermissionLevel.ALL_COMMANDS)
|
||||
@JvmStatic
|
||||
val TECHNICAL_MACHINE = this.create("${COMMAND_PREFIX}technicalmachine", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
|
||||
@JvmStatic
|
||||
val SEE_HIDDEN_NPCS = this.create("seehiddennpcs", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
|
||||
|
||||
@@ -29,7 +29,6 @@ abstract class AbstractPokedexManager {
|
||||
|
||||
fun deleteSpeciesRecord(speciesId: ResourceLocation) {
|
||||
speciesRecords.remove(speciesId)
|
||||
markDirty()
|
||||
}
|
||||
|
||||
fun deleteFormRecord(speciesId: ResourceLocation, formName: String) {
|
||||
@@ -40,8 +39,6 @@ abstract class AbstractPokedexManager {
|
||||
deleteSpeciesRecord(speciesId)
|
||||
return
|
||||
}
|
||||
|
||||
markDirty()
|
||||
}
|
||||
|
||||
fun getSpeciesRecord(speciesId: ResourceLocation): SpeciesDexRecord? {
|
||||
@@ -164,8 +161,4 @@ abstract class AbstractPokedexManager {
|
||||
return newValue
|
||||
}
|
||||
}
|
||||
|
||||
open fun markDirty() {
|
||||
// Save stuff
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,9 @@ package com.cobblemon.mod.common.api.pokedex
|
||||
|
||||
import com.bedrockk.molang.runtime.struct.QueryStruct
|
||||
import com.bedrockk.molang.runtime.struct.VariableStruct
|
||||
import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.pokemon.PokedexDataChangedEvent
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
@@ -25,6 +27,7 @@ import com.google.common.collect.Sets
|
||||
import com.mojang.datafixers.util.Either
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.codecs.ListCodec
|
||||
import com.mojang.serialization.codecs.PrimitiveCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
@@ -41,11 +44,13 @@ class FormDexRecord {
|
||||
instance.group(
|
||||
ListCodec(Codec.STRING, 0, 3).fieldOf("genders").forGetter { it.genders.map { it.name } },
|
||||
ListCodec(Codec.STRING, 0, 2).fieldOf("seenShinyStates").forGetter { it.seenShinyStates.toList() },
|
||||
PrimitiveCodec.INT.optionalFieldOf("highest_level", -1).forGetter { it.highestLevel },
|
||||
Codec.STRING.fieldOf("knowledge").forGetter { it.knowledge.name }
|
||||
).apply(instance) { genders, seenShinyStates, knowledge ->
|
||||
).apply(instance) { genders, seenShinyStates, highestLevel, knowledge ->
|
||||
FormDexRecord().also {
|
||||
it.genders.addAll(genders.map(Gender::valueOf))
|
||||
it.seenShinyStates.addAll(seenShinyStates)
|
||||
it.highestLevel = highestLevel
|
||||
it.knowledge = PokedexEntryProgress.valueOf(knowledge)
|
||||
}
|
||||
}
|
||||
@@ -59,6 +64,9 @@ class FormDexRecord {
|
||||
private val seenShinyStates =
|
||||
mutableSetOf<String>() // consider: radiants in the future (radiants should just be a resource pack tbh)
|
||||
|
||||
var highestLevel = -1
|
||||
private set
|
||||
|
||||
/** The current awareness of the form that the dex has. */
|
||||
var knowledge = PokedexEntryProgress.NONE
|
||||
private set
|
||||
@@ -80,11 +88,17 @@ class FormDexRecord {
|
||||
struct = QueryStruct(hashMapOf())
|
||||
.addFunction("data") { data }
|
||||
.addFunction("knowledge") { StringValue(knowledge.name) }
|
||||
.addFunction("has_seen_gender") { params -> genders.contains(Gender.valueOf(params.getString(0).uppercase())) }
|
||||
.addFunction("has_seen_gender") { params -> DoubleValue(genders.contains(Gender.valueOf(params.getString(0).uppercase()))) }
|
||||
.addFunction("highest_level") { _ -> DoubleValue(highestLevel) }
|
||||
.addFunction("set_highest_level") { params ->
|
||||
highestLevel = maxOf(highestLevel, params.getDouble(0).toInt())
|
||||
DoubleValue.ONE
|
||||
}
|
||||
}
|
||||
|
||||
fun clone() = FormDexRecord().also {
|
||||
it.genders.addAll(genders)
|
||||
it.highestLevel = highestLevel
|
||||
it.seenShinyStates.addAll(seenShinyStates)
|
||||
it.knowledge = knowledge
|
||||
}
|
||||
@@ -114,7 +128,7 @@ class FormDexRecord {
|
||||
fun addAllShinyStatesAndGenders() {
|
||||
val form = PokemonSpecies.getByIdentifier(speciesDexRecord.id)?.getFormByName(formName)
|
||||
genders.addAll(form?.possibleGenders ?: listOf(Gender.MALE, Gender.FEMALE))
|
||||
|
||||
highestLevel = Cobblemon.config.maxPokemonLevel
|
||||
seenShinyStates.addAll(listOf("shiny", "normal"))
|
||||
speciesDexRecord.onFormRecordUpdated(this)
|
||||
}
|
||||
@@ -135,6 +149,9 @@ class FormDexRecord {
|
||||
),
|
||||
ifSucceeded = {
|
||||
genders.add(pokedexEntityData.pokemon.gender)
|
||||
if (knowledge == PokedexEntryProgress.CAUGHT) {
|
||||
highestLevel = maxOf(highestLevel, pokedexEntityData.pokemon.level)
|
||||
}
|
||||
seenShinyStates.add(if (pokedexEntityData.pokemon.shiny) "shiny" else "normal")
|
||||
if (knowledge.ordinal > this.knowledge.ordinal) {
|
||||
this.knowledge = knowledge
|
||||
@@ -159,12 +176,14 @@ class FormDexRecord {
|
||||
|| (pokedexEntityData.pokemon.shiny && "shiny" !in seenShinyStates)
|
||||
|| (!pokedexEntityData.pokemon.shiny && "normal" !in seenShinyStates)
|
||||
|| knowledge.ordinal > this.knowledge.ordinal
|
||||
|| (highestLevel < pokedexEntityData.pokemon.level && knowledge == PokedexEntryProgress.CAUGHT)
|
||||
|| speciesDexRecord.wouldBeDifferent(pokedexEntityData)
|
||||
}
|
||||
|
||||
fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeCollection(genders) { _, it -> buffer.writeEnumConstant(it) }
|
||||
buffer.writeCollection(seenShinyStates) { _, it -> buffer.writeString(it) }
|
||||
buffer.writeInt(highestLevel)
|
||||
buffer.writeEnumConstant(knowledge)
|
||||
}
|
||||
|
||||
@@ -173,6 +192,7 @@ class FormDexRecord {
|
||||
seenShinyStates.clear()
|
||||
genders.addAll(buffer.readCollection(Sets::newHashSetWithExpectedSize) { buffer.readEnumConstant(Gender::class.java) })
|
||||
seenShinyStates.addAll(buffer.readCollection(Sets::newHashSetWithExpectedSize) { buffer.readString() })
|
||||
highestLevel = buffer.readInt()
|
||||
knowledge = buffer.readEnumConstant(PokedexEntryProgress::class.java)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@
|
||||
package com.cobblemon.mod.common.api.pokedex
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
|
||||
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
|
||||
import com.cobblemon.mod.common.api.storage.player.InstancedPlayerData
|
||||
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
|
||||
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
|
||||
@@ -48,10 +50,44 @@ class PokedexManager(
|
||||
fun obtain(pokemon: Pokemon) {
|
||||
val speciesId = pokemon.species.resourceIdentifier
|
||||
val formName = pokemon.form.name
|
||||
getOrCreateSpeciesRecord(speciesId).getOrCreateFormRecord(formName).caught(PokedexEntityData(pokemon = pokemon, disguise = null))
|
||||
getOrCreateSpeciesRecord(speciesId).getOrCreateFormRecord(formName).obtained(PokedexEntityData(pokemon = pokemon, disguise = null))
|
||||
}
|
||||
|
||||
override fun markDirty() {
|
||||
fun scheduleFullSyncFromStores(
|
||||
party: Iterable<Pokemon?>,
|
||||
pc: Iterable<Pokemon?>,
|
||||
batchSize: Int = 50
|
||||
) {
|
||||
val iterator = sequence {
|
||||
for (pokemon in party) {
|
||||
if (pokemon != null) yield(pokemon)
|
||||
}
|
||||
for (pokemon in pc) {
|
||||
if (pokemon != null) yield(pokemon)
|
||||
}
|
||||
}.iterator()
|
||||
|
||||
if (!iterator.hasNext()) {
|
||||
return
|
||||
}
|
||||
|
||||
ScheduledTask.Builder()
|
||||
.tracker(ServerTaskTracker)
|
||||
.interval(0F)
|
||||
.infiniteIterations()
|
||||
.execute { task ->
|
||||
var processed = 0
|
||||
while (processed < batchSize && iterator.hasNext()) {
|
||||
val pokemon = iterator.next()
|
||||
obtain(pokemon)
|
||||
processed++
|
||||
}
|
||||
|
||||
if (!iterator.hasNext()) {
|
||||
task.expire()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun initialize() {
|
||||
|
||||
@@ -26,7 +26,8 @@ import net.minecraft.resources.ResourceLocation
|
||||
/**
|
||||
* Dex recorded information about a particular Pokémon species. This includes all forms and their information.
|
||||
* Across all forms there is a single list of known aspects which is generally used to track cosmetic variation
|
||||
* though technically things like gender and form-motivating aspects will get collected inadvertently.
|
||||
* though technically things like gender and form-motivating aspects will get collected inadvertently. The highest
|
||||
* level of the species that the player has owned is also tracked.
|
||||
*
|
||||
* @author Hiroku
|
||||
* @since August 23rd, 2024
|
||||
@@ -36,10 +37,12 @@ class SpeciesDexRecord {
|
||||
val CODEC: Codec<SpeciesDexRecord> = RecordCodecBuilder.create { instance ->
|
||||
instance.group(
|
||||
ListCodec(PrimitiveCodec.STRING, 0, 512).fieldOf("aspects").forGetter { it.aspects.toList() },
|
||||
PrimitiveCodec.INT.optionalFieldOf("highestLevel", -1).forGetter { it.highestLevel },
|
||||
Codec.unboundedMap(Codec.STRING, FormDexRecord.CODEC).fieldOf("formRecords").forGetter { it.formRecords }
|
||||
).apply(instance) { aspects, formRecords ->
|
||||
).apply(instance) { aspects, highestLevel, formRecords ->
|
||||
SpeciesDexRecord().also {
|
||||
it.aspects.addAll(aspects)
|
||||
it.highestLevel = highestLevel
|
||||
it.formRecords.putAll(formRecords)
|
||||
}
|
||||
}
|
||||
@@ -48,13 +51,16 @@ class SpeciesDexRecord {
|
||||
|
||||
@Transient
|
||||
lateinit var id: ResourceLocation
|
||||
/** The highest level of the species that the player has owned. */
|
||||
var highestLevel = -1
|
||||
private set
|
||||
private val aspects: MutableSet<String> = mutableSetOf()
|
||||
private val formRecords: MutableMap<String, FormDexRecord> = mutableMapOf()
|
||||
val isFormRecordsEmpty: Boolean
|
||||
get() = formRecords.isEmpty()
|
||||
|
||||
fun describe(): String {
|
||||
return "SpeciesDexRecord(aspects=$aspects, formRecords=$formRecords)"
|
||||
return "SpeciesDexRecord(aspects=$aspects, highestLevel=$highestLevel, formRecords=$formRecords)"
|
||||
}
|
||||
|
||||
@Transient
|
||||
@@ -77,7 +83,14 @@ class SpeciesDexRecord {
|
||||
.addFunction("is_caught") { params ->
|
||||
DoubleValue(formRecords.values.any { it.knowledge.name.equals("CAUGHT", ignoreCase = true) })
|
||||
}
|
||||
// more, also worth moving to something like MoLangFunctions.kt so it's easier to API in more values
|
||||
.addFunction("highest_level") { params ->
|
||||
DoubleValue(highestLevel)
|
||||
}
|
||||
.addFunction("set_highest_level") { params ->
|
||||
highestLevel = params.getDouble(0).toInt()
|
||||
DoubleValue.ONE
|
||||
}
|
||||
// more, also worth moving to something like MoLangFunctions.kt so it's easier to API in more values
|
||||
|
||||
@Transient
|
||||
lateinit var pokedexManager: AbstractPokedexManager
|
||||
@@ -94,10 +107,16 @@ class SpeciesDexRecord {
|
||||
|
||||
fun addInformation(pokemon: Pokemon, knowledge: PokedexEntryProgress) {
|
||||
aspects.addAll(pokemon.aspects)
|
||||
if (knowledge == PokedexEntryProgress.CAUGHT) {
|
||||
highestLevel = maxOf(highestLevel, pokemon.level)
|
||||
}
|
||||
}
|
||||
|
||||
fun addInformation(pokedexEntityData: PokedexEntityData, knowledge: PokedexEntryProgress) {
|
||||
aspects.addAll(pokedexEntityData.pokemon.aspects)
|
||||
if (knowledge == PokedexEntryProgress.CAUGHT) {
|
||||
highestLevel = maxOf(highestLevel, pokedexEntityData.pokemon.level)
|
||||
}
|
||||
}
|
||||
|
||||
fun addAspects(addedAspects: Set<String>) {
|
||||
@@ -128,6 +147,7 @@ class SpeciesDexRecord {
|
||||
|
||||
fun clone() = SpeciesDexRecord().also {
|
||||
it.aspects.addAll(aspects)
|
||||
it.highestLevel = highestLevel
|
||||
it.formRecords.putAll(formRecords.mapValues { it.value.clone() })
|
||||
}
|
||||
|
||||
@@ -139,6 +159,7 @@ class SpeciesDexRecord {
|
||||
|
||||
fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeCollection(aspects) { _, it -> buffer.writeString(it) }
|
||||
buffer.writeInt(highestLevel)
|
||||
buffer.writeInt(formRecords.size)
|
||||
for ((formName, formRecord) in formRecords) {
|
||||
buffer.writeString(formName)
|
||||
@@ -149,6 +170,7 @@ class SpeciesDexRecord {
|
||||
fun decode(buffer: RegistryFriendlyByteBuf) {
|
||||
aspects.clear()
|
||||
aspects.addAll(buffer.readCollection(Sets::newHashSetWithExpectedSize) { buffer.readString() })
|
||||
highestLevel = buffer.readInt()
|
||||
formRecords.clear()
|
||||
val numForms = buffer.readInt()
|
||||
for (i in 0 until numForms) {
|
||||
|
||||
@@ -11,5 +11,6 @@ package com.cobblemon.mod.common.api.pokedex.filter
|
||||
enum class SearchByType {
|
||||
SPECIES,
|
||||
ABILITIES,
|
||||
MOVES,
|
||||
DROPS;
|
||||
}
|
||||
+154
-1
@@ -9,14 +9,22 @@
|
||||
package com.cobblemon.mod.common.api.pokedex.filter
|
||||
|
||||
import com.cobblemon.mod.common.api.drop.ItemDropEntry
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
|
||||
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
|
||||
import com.cobblemon.mod.common.api.tms.TechnicalMachines
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbility
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.asTranslated
|
||||
import com.cobblemon.mod.common.util.itemRegistry
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
/**
|
||||
@@ -47,6 +55,23 @@ class SearchFilter(val pokedexManager: AbstractPokedexManager, val searchString:
|
||||
}
|
||||
return abilityList.any { it.contains(searchString.trim().lowercase()) }
|
||||
}
|
||||
SearchByType.MOVES -> {
|
||||
if (pokedexManager.getHighestKnowledgeFor(entry) !== PokedexEntryProgress.CAUGHT) return false
|
||||
val search = searchString.trim().lowercase()
|
||||
val learnedTMs = CobblemonClient.clientTMMoveData.learnedTMs
|
||||
val forms = pokedexManager.getCaughtForms(entry)
|
||||
val formData = if (forms.isEmpty()) {
|
||||
listOf(species.standardForm)
|
||||
} else {
|
||||
forms.map { form ->
|
||||
species.forms.find { it.name.equals(form.displayForm, ignoreCase = true) } ?: species.standardForm
|
||||
}
|
||||
}
|
||||
return formData.any { form ->
|
||||
val highestLevel = pokedexManager.getSpeciesRecord(form.species.resourceIdentifier)?.getFormRecord(form.name)?.highestLevel ?: -1
|
||||
hasDiscoveredMove(form, search, highestLevel, pokedexManager, learnedTMs)
|
||||
}
|
||||
}
|
||||
SearchByType.DROPS -> {
|
||||
if (pokedexManager.getHighestKnowledgeFor(entry) !== PokedexEntryProgress.CAUGHT) return false
|
||||
val dropsList = mutableListOf<String>()
|
||||
@@ -68,4 +93,132 @@ class SearchFilter(val pokedexManager: AbstractPokedexManager, val searchString:
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private fun hasDiscoveredMove(
|
||||
form: FormData,
|
||||
search: String,
|
||||
highestLevel: Int,
|
||||
pokedex: AbstractPokedexManager,
|
||||
learnedTMs: Set<ResourceLocation>
|
||||
): Boolean {
|
||||
val highestEvolutionLevel = collectEvolutionForms(form).maxOfOrNull { evolutionForm ->
|
||||
val speciesRecord = pokedex.getSpeciesRecord(evolutionForm.species.resourceIdentifier)
|
||||
speciesRecord?.getFormRecord(evolutionForm.name)?.highestLevel ?: speciesRecord?.highestLevel ?: -1
|
||||
} ?: -1
|
||||
|
||||
fun matches(move: MoveTemplate): Boolean {
|
||||
return move.displayName.string.lowercase().contains(search)
|
||||
}
|
||||
|
||||
fun isLevelUpDiscovered(level: Int): Boolean {
|
||||
if (highestLevel >= level) return true
|
||||
return highestEvolutionLevel >= level
|
||||
}
|
||||
|
||||
form.moves.levelUpMoves.forEach { (level, moves) ->
|
||||
for (move in moves) {
|
||||
if (matches(move) && isLevelUpDiscovered(level)) return true
|
||||
}
|
||||
}
|
||||
|
||||
form.moves.tmMoves.forEach { move ->
|
||||
val tmId = TechnicalMachines.moveToTM[move]?.id
|
||||
val discovered = tmId == null
|
||||
|| tmId in learnedTMs
|
||||
|| TechnicalMachines.tmMap[tmId]?.isPassivelyObtained() == true
|
||||
if (discovered && matches(move)) return true
|
||||
}
|
||||
|
||||
val alwaysDiscovered = sequenceOf(
|
||||
form.moves.tutorMoves,
|
||||
form.moves.eggMoves,
|
||||
form.moves.evolutionMoves,
|
||||
form.moves.formChangeMoves,
|
||||
form.moves.specialMoves,
|
||||
form.moves.legacyMoves
|
||||
)
|
||||
|
||||
for (moves in alwaysDiscovered) {
|
||||
for (move in moves) {
|
||||
if (matches(move)) return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// todo remove this since it isn't needed anymore
|
||||
private fun buildEvolutionMoveLevelIndex(form: FormData): Map<ResourceLocation, Map<String, Int>> {
|
||||
val evolutionForms = collectEvolutionForms(form)
|
||||
val levelsBySpecies = mutableMapOf<ResourceLocation, MutableMap<String, Int>>()
|
||||
|
||||
for (evolutionForm in evolutionForms) {
|
||||
val speciesId = evolutionForm.species.resourceIdentifier
|
||||
val moveLevels = levelsBySpecies.getOrPut(speciesId) { mutableMapOf() }
|
||||
buildMoveLevelIndex(evolutionForm).forEach { (moveName, level) ->
|
||||
val current = moveLevels[moveName]
|
||||
if (current == null || level < current) {
|
||||
moveLevels[moveName] = level
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return levelsBySpecies
|
||||
}
|
||||
|
||||
private fun buildMoveLevelIndex(form: FormData): Map<String, Int> {
|
||||
val levels = mutableMapOf<String, Int>()
|
||||
form.moves.levelUpMoves.forEach { (level, moves) ->
|
||||
moves.forEach { move ->
|
||||
val current = levels[move.name]
|
||||
if (current == null || level < current) {
|
||||
levels[move.name] = level
|
||||
}
|
||||
}
|
||||
}
|
||||
return levels
|
||||
}
|
||||
|
||||
private fun collectEvolutionForms(rootForm: FormData): List<FormData> {
|
||||
val results = mutableListOf<FormData>()
|
||||
val visited = mutableSetOf<String>()
|
||||
|
||||
fun key(form: FormData): String {
|
||||
return "${form.species.resourceIdentifier}|${form.name.lowercase()}"
|
||||
}
|
||||
|
||||
fun nextEvolutions(current: FormData): Set<Evolution> {
|
||||
return if (current.evolutions.isNotEmpty()) {
|
||||
current.evolutions
|
||||
} else {
|
||||
current.species.evolutions
|
||||
}
|
||||
}
|
||||
|
||||
fun traverse(current: FormData) {
|
||||
for (evolution in nextEvolutions(current)) {
|
||||
val evolutionForm = resolveEvolutionForm(evolution) ?: continue
|
||||
val evolutionKey = key(evolutionForm)
|
||||
if (!visited.add(evolutionKey)) continue
|
||||
results.add(evolutionForm)
|
||||
traverse(evolutionForm)
|
||||
}
|
||||
}
|
||||
|
||||
traverse(rootForm)
|
||||
return results
|
||||
}
|
||||
|
||||
private fun resolveEvolutionForm(evolution: Evolution): FormData? {
|
||||
val speciesId = evolution.result.species?.asIdentifierDefaultingNamespace() ?: return null
|
||||
val species = PokemonSpecies.getByIdentifier(speciesId) ?: return null
|
||||
val formId = evolution.result.form
|
||||
return if (formId == null) {
|
||||
species.standardForm
|
||||
} else {
|
||||
species.forms.firstOrNull {
|
||||
it.formOnlyShowdownId().equals(formId, ignoreCase = true) || it.name.equals(formId, ignoreCase = true)
|
||||
} ?: species.standardForm
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.api.pokedex.filter
|
||||
|
||||
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
|
||||
import com.cobblemon.mod.common.api.pokedex.entry.PokedexEntry
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
|
||||
import com.cobblemon.mod.common.api.tms.TechnicalMachines
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.pokemon.FormData
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
enum class SecondaryPokedexFilterType {
|
||||
ALL,
|
||||
CAUGHT,
|
||||
SEEN,
|
||||
UNDISCOVERED,
|
||||
LEVEL_UP_TM_UNDISCOVERED
|
||||
}
|
||||
|
||||
class SecondaryPokedexFilter(
|
||||
private val pokedexManager: AbstractPokedexManager,
|
||||
private val filterType: SecondaryPokedexFilterType
|
||||
) : EntryFilter() {
|
||||
|
||||
override fun test(entry: PokedexEntry): Boolean {
|
||||
return when (filterType) {
|
||||
SecondaryPokedexFilterType.ALL -> true
|
||||
SecondaryPokedexFilterType.CAUGHT -> pokedexManager.getHighestKnowledgeFor(entry) >= PokedexEntryProgress.CAUGHT
|
||||
SecondaryPokedexFilterType.SEEN -> pokedexManager.getHighestKnowledgeFor(entry) == PokedexEntryProgress.ENCOUNTERED
|
||||
SecondaryPokedexFilterType.UNDISCOVERED -> pokedexManager.getHighestKnowledgeFor(entry) == PokedexEntryProgress.NONE
|
||||
SecondaryPokedexFilterType.LEVEL_UP_TM_UNDISCOVERED -> hasUndiscoveredLevelUpTM(entry)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasUndiscoveredLevelUpTM(entry: PokedexEntry): Boolean {
|
||||
if (pokedexManager.getHighestKnowledgeFor(entry) != PokedexEntryProgress.CAUGHT) return false
|
||||
val species = PokemonSpecies.getByIdentifier(entry.speciesId) ?: return false
|
||||
val forms = pokedexManager.getCaughtForms(entry)
|
||||
val formData = if (forms.isEmpty()) {
|
||||
listOf(species.standardForm)
|
||||
} else {
|
||||
forms.map { form ->
|
||||
species.forms.find { it.name.equals(form.displayForm, ignoreCase = true) } ?: species.standardForm
|
||||
}
|
||||
}
|
||||
|
||||
return formData.any { form ->
|
||||
val highestLevel = pokedexManager.getSpeciesRecord(form.species.resourceIdentifier)?.getFormRecord(form.name)?.highestLevel ?: -1
|
||||
hasUndiscoveredLevelUpTM(form, highestLevel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasUndiscoveredLevelUpTM(
|
||||
form: FormData,
|
||||
highestLevel: Int
|
||||
): Boolean {
|
||||
val learnedTMs = CobblemonClient.clientTMMoveData.learnedTMs
|
||||
val highestEvolutionLevel = collectEvolutionForms(form).maxOfOrNull { evolutionForm ->
|
||||
val speciesRecord = pokedexManager.getSpeciesRecord(evolutionForm.species.resourceIdentifier)
|
||||
speciesRecord?.getFormRecord(evolutionForm.name)?.highestLevel ?: speciesRecord?.highestLevel ?: -1
|
||||
} ?: -1
|
||||
|
||||
fun isLevelUpDiscovered(level: Int): Boolean {
|
||||
if (highestLevel >= level) return true
|
||||
return highestEvolutionLevel >= level
|
||||
}
|
||||
|
||||
form.moves.levelUpMoves.forEach { (level, moves) ->
|
||||
for (move in moves) {
|
||||
val tmId = TechnicalMachines.moveToTM[move]?.id ?: continue
|
||||
val tmUnlocked = tmId in learnedTMs || TechnicalMachines.tmMap[tmId]?.isPassivelyObtained() == true
|
||||
if (!tmUnlocked && !isLevelUpDiscovered(level)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun buildMoveLevelIndex(form: FormData): Map<String, Int> {
|
||||
val levels = mutableMapOf<String, Int>()
|
||||
form.moves.levelUpMoves.forEach { (level, moves) ->
|
||||
moves.forEach { move ->
|
||||
val current = levels[move.name]
|
||||
if (current == null || level < current) {
|
||||
levels[move.name] = level
|
||||
}
|
||||
}
|
||||
}
|
||||
return levels
|
||||
}
|
||||
|
||||
private fun collectEvolutionForms(rootForm: FormData): List<FormData> {
|
||||
val results = mutableListOf<FormData>()
|
||||
val visited = mutableSetOf<String>()
|
||||
|
||||
fun key(form: FormData): String {
|
||||
return "${form.species.resourceIdentifier}|${form.name.lowercase()}"
|
||||
}
|
||||
|
||||
fun nextEvolutions(current: FormData): Set<Evolution> {
|
||||
return if (current.evolutions.isNotEmpty()) {
|
||||
current.evolutions
|
||||
} else {
|
||||
current.species.evolutions
|
||||
}
|
||||
}
|
||||
|
||||
fun traverse(current: FormData) {
|
||||
for (evolution in nextEvolutions(current)) {
|
||||
val evolutionForm = resolveEvolutionForm(evolution) ?: continue
|
||||
val evolutionKey = key(evolutionForm)
|
||||
if (!visited.add(evolutionKey)) continue
|
||||
results.add(evolutionForm)
|
||||
traverse(evolutionForm)
|
||||
}
|
||||
}
|
||||
|
||||
traverse(rootForm)
|
||||
return results
|
||||
}
|
||||
|
||||
private fun resolveEvolutionForm(evolution: Evolution): FormData? {
|
||||
val speciesId = evolution.result.species?.asIdentifierDefaultingNamespace() ?: return null
|
||||
val species = PokemonSpecies.getByIdentifier(speciesId) ?: return null
|
||||
val formId = evolution.result.form
|
||||
return if (formId == null) {
|
||||
species.standardForm
|
||||
} else {
|
||||
species.forms.firstOrNull {
|
||||
it.formOnlyShowdownId().equals(formId, ignoreCase = true) || it.name.equals(formId, ignoreCase = true)
|
||||
} ?: species.standardForm
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.pokemon
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.Cobblemon.config
|
||||
import com.cobblemon.mod.common.CobblemonMovesetBuilders
|
||||
import com.cobblemon.mod.common.api.abilities.Abilities
|
||||
import com.cobblemon.mod.common.api.abilities.Ability
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
@@ -148,6 +149,13 @@ open class PokemonProperties {
|
||||
props.originalTrainerType = OriginalTrainerType.entries.toList().parsePropertyOfCollection(keyPairs, listOf("originaltrainertype", "ottype"), labelsOptional = true) { it.name.lowercase() }
|
||||
props.originalTrainer = parsePlayerProperty(keyPairs, listOf("originaltrainer", "ot"))
|
||||
props.moves = parseString(keyPairs, listOf("moves"))?.split(",")
|
||||
props.movesetBuilders = parseString(keyPairs, listOf("moveset_builders", "movesetbuilders", "moveset_builder", "movesetbuilder"))?.split(",")?.mapNotNull {
|
||||
try {
|
||||
it.asIdentifierDefaultingNamespace()
|
||||
} catch (_: ResourceLocationException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
props.heldItem = parseString(keyPairs, listOf("helditem", "held_item"))
|
||||
props.minPerfectIVs = parseIntProperty(keyPairs, listOf("min_perfect_ivs"))?.coerceIn(0, Stats.PERMANENT.size)
|
||||
props.scaleModifier = parseFloatProperty(keyPairs, listOf("scale_modifier"))
|
||||
@@ -356,6 +364,7 @@ open class PokemonProperties {
|
||||
var originalTrainerType: OriginalTrainerType? = null
|
||||
var originalTrainer: String? = null // Original Trainer by Username or UUID
|
||||
var moves: List<String>? = null
|
||||
var movesetBuilders: List<ResourceLocation>? = null
|
||||
var heldItem: String? = null
|
||||
var cosmeticItem: String? = null
|
||||
var scaleModifier: Float? = null
|
||||
@@ -462,7 +471,7 @@ open class PokemonProperties {
|
||||
}
|
||||
moves?.let { moves ->
|
||||
if (pokemon.moveSet.getMoves().isEmpty()) {
|
||||
pokemon.initializeMoveset(preferLatest = true)
|
||||
pokemon.initializeMovesetFromDefault()
|
||||
}
|
||||
val moveTemplates = moves.mapNotNull(Moves::getByName).shuffled()
|
||||
val replaceableIndices = (0..3).filterNot { pokemon.moveSet[it]?.template?.let(moveTemplates::contains) == true }.shuffled()
|
||||
@@ -475,6 +484,13 @@ open class PokemonProperties {
|
||||
pokemon.moveSet.update()
|
||||
}
|
||||
}
|
||||
movesetBuilders?.let { movesetBuilders ->
|
||||
val builders = movesetBuilders.mapNotNull { CobblemonMovesetBuilders.movesetBuilders[it] }
|
||||
if (builders.isNotEmpty()) {
|
||||
val movesetBuilder = builders.random()
|
||||
pokemon.initializeMovesetFrom(movesetBuilder)
|
||||
}
|
||||
}
|
||||
heldItem?.let { itemKey ->
|
||||
val server = server() ?: return@let
|
||||
val parser = ItemParser(server.registryAccess())
|
||||
@@ -678,6 +694,7 @@ open class PokemonProperties {
|
||||
originalTrainerType?.let { nbt.putInt(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, it.ordinal) }
|
||||
originalTrainer?.let { nbt.putString(DataKeys.POKEMON_ORIGINAL_TRAINER, it) }
|
||||
moves?.let { nbt.putString(DataKeys.POKEMON_PROPERTIES_MOVES, it.joinToString(separator = ",")) }
|
||||
movesetBuilders?.let { nbt.putString(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS, it.joinToString(separator = ",") { source -> source.toString() }) }
|
||||
heldItem?.let {nbt.putString(DataKeys.POKEMON_PROPERTIES_HELDITEM, it)}
|
||||
scaleModifier?.let { nbt.putFloat(DataKeys.POKEMON_SCALE_MODIFIER, it) }
|
||||
val custom = ListTag()
|
||||
@@ -712,6 +729,7 @@ open class PokemonProperties {
|
||||
originalTrainerType = if (tag.contains(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)) OriginalTrainerType.valueOf(tag.getString(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)) else null
|
||||
originalTrainer = if (tag.contains(DataKeys.POKEMON_ORIGINAL_TRAINER)) tag.getString(DataKeys.POKEMON_ORIGINAL_TRAINER) else null
|
||||
moves = if (tag.contains(DataKeys.POKEMON_PROPERTIES_MOVES)) tag.getString(DataKeys.POKEMON_PROPERTIES_MOVES).split(",") else null
|
||||
movesetBuilders = if (tag.contains(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS)) tag.getString(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS).split(",").map { it.asIdentifierDefaultingNamespace() } else null
|
||||
heldItem = if (tag.contains(DataKeys.POKEMON_PROPERTIES_HELDITEM)) tag.getString(DataKeys.POKEMON_PROPERTIES_HELDITEM) else null
|
||||
scaleModifier = if (tag.contains(DataKeys.POKEMON_SCALE_MODIFIER)) tag.getFloat(DataKeys.POKEMON_SCALE_MODIFIER) else null
|
||||
val custom = tag.getList(DataKeys.POKEMON_PROPERTIES_CUSTOM, Tag.TAG_STRING.toInt())
|
||||
@@ -747,6 +765,7 @@ open class PokemonProperties {
|
||||
originalTrainerType?.let { json.addProperty(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE, it.name) }
|
||||
originalTrainer?.let { json.addProperty(DataKeys.POKEMON_ORIGINAL_TRAINER, it) }
|
||||
moves?.let { json.addProperty(DataKeys.POKEMON_PROPERTIES_MOVES, it.joinToString(separator = ",")) }
|
||||
movesetBuilders?.let { json.addProperty(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS, it.joinToString(separator = ",") { source -> source.toString() }) }
|
||||
heldItem?.let {json.addProperty(DataKeys.POKEMON_PROPERTIES_HELDITEM, it)}
|
||||
scaleModifier?.let { json.addProperty(DataKeys.POKEMON_SCALE_MODIFIER, it) }
|
||||
val custom = JsonArray()
|
||||
@@ -781,6 +800,7 @@ open class PokemonProperties {
|
||||
originalTrainerType = json.get(DataKeys.POKEMON_ORIGINAL_TRAINER_TYPE)?.asString?.let { OriginalTrainerType.valueOf(it) }
|
||||
originalTrainer = json.get(DataKeys.POKEMON_ORIGINAL_TRAINER)?.asString
|
||||
moves = json.get(DataKeys.POKEMON_PROPERTIES_MOVES)?.asString?.split(",")
|
||||
movesetBuilders = json.get(DataKeys.POKEMON_PROPERTIES_MOVESET_BUILDERS)?.asString?.split(",")?.map { it.asIdentifierDefaultingNamespace() }
|
||||
heldItem = json.get(DataKeys.POKEMON_PROPERTIES_HELDITEM)?.asString
|
||||
scaleModifier = json.get(DataKeys.POKEMON_SCALE_MODIFIER)?.asFloat
|
||||
val custom = json.get(DataKeys.POKEMON_PROPERTIES_CUSTOM)?.asJsonArray
|
||||
@@ -820,6 +840,7 @@ open class PokemonProperties {
|
||||
originalTrainer?.let { pieces.add("originaltrainer=$it") }
|
||||
customProperties.forEach { pieces.add(it.asString()) }
|
||||
moves?.let { pieces.add("moves=${it.joinToString(separator = ",")}") }
|
||||
movesetBuilders?.let { pieces.add("movesetbuilders=${it.joinToString(separator = ",") { source -> source.toString() }}") }
|
||||
heldItem?.let {pieces.add("helditem=$it")}
|
||||
scaleModifier?.let { pieces.add("scale_modifier=$it") }
|
||||
return pieces.joinToString(separator)
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.cobblemon.mod.common.api.drop.ItemDropMethod
|
||||
import com.cobblemon.mod.common.api.entity.EntityDimensionsAdapter
|
||||
import com.cobblemon.mod.common.api.molang.ExpressionLike
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.moves.MovesetBuilder
|
||||
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
|
||||
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies.getByIdentifier
|
||||
@@ -131,6 +132,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
|
||||
.registerTypeAdapter(RideSoundSettingsList::class.java, RideSoundSettingsListAdapter)
|
||||
.registerTypeAdapter(ObtainableItemCondition::class.java, ObtainableItemConditionAdapter)
|
||||
.registerTypeAdapter(MovesetBuilder::class.java, MovesetBuilderReferenceAdapter)
|
||||
.disableHtmlEscaping()
|
||||
.enableComplexMapKeySerialization()
|
||||
.create()
|
||||
@@ -314,11 +316,11 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
val result = mutableMapOf<String, String>()
|
||||
this.species.forEach {species ->
|
||||
val baseSpecies = ShowdownSpecies(species, null)
|
||||
result[ShowdownIdentifiable.REGEX.replace(baseSpecies.name, "")] = this.gson.toJson(baseSpecies)
|
||||
result[ShowdownIdentifiable.REGEX.replace(baseSpecies.name.lowercase(), "")] = this.gson.toJson(baseSpecies)
|
||||
species.forms.forEach { form ->
|
||||
if (form != species.standardForm) {
|
||||
val formSpecies = ShowdownSpecies(species, form)
|
||||
result[ShowdownIdentifiable.REGEX.replace(formSpecies.name, "")] = this.gson.toJson(formSpecies)
|
||||
result[ShowdownIdentifiable.REGEX.replace(formSpecies.name.lowercase(), "")] = this.gson.toJson(formSpecies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.pokemon.moves
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.data.ClientDataSynchronizer
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.moves.Moves
|
||||
@@ -19,9 +20,7 @@ import com.google.gson.JsonElement
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
open class Learnset : ClientDataSynchronizer<Learnset> {
|
||||
class Interpreter(
|
||||
val loadMove: (JsonElement, Learnset) -> Boolean
|
||||
) {
|
||||
class Interpreter(val loadMove: (JsonElement, Learnset) -> Boolean) {
|
||||
companion object {
|
||||
fun parseFromPrefixIntoList(prefix: String, list: (Learnset) -> MutableList<MoveTemplate>): Interpreter {
|
||||
return Interpreter { element, learnset ->
|
||||
@@ -73,8 +72,7 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
|
||||
eggInterpreter,
|
||||
tutorInterpreter,
|
||||
legacyInterpreter,
|
||||
specialInterpreter,
|
||||
levelUpInterpreter,
|
||||
specialInterpreter, levelUpInterpreter,
|
||||
formChangeInterpreter
|
||||
)
|
||||
}
|
||||
@@ -99,6 +97,20 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
|
||||
.flatMap { it.value }
|
||||
.toSet()
|
||||
|
||||
fun tmLearnableMoves(): MutableList<MoveTemplate> {
|
||||
val moves = mutableListOf<MoveTemplate>()
|
||||
|
||||
moves.addAll(tmMoves)
|
||||
moves.addAll(tutorMoves)
|
||||
moves.addAll(eggMoves)
|
||||
moves.addAll(getLevelUpMovesUpTo(Cobblemon.config.maxPokemonLevel))
|
||||
moves.addAll(evolutionMoves)
|
||||
moves.addAll(formChangeMoves)
|
||||
|
||||
return moves
|
||||
}
|
||||
|
||||
|
||||
fun getAllLegalMoves(): Set<MoveTemplate> {
|
||||
return levelUpMoves.values.flatten().toSet() +
|
||||
eggMoves +
|
||||
|
||||
+4
-1
@@ -119,7 +119,10 @@ class RidingController(
|
||||
private fun canTransitionToLand(): Boolean {
|
||||
if ((entity.isUnderWater)) return false
|
||||
if ((entity.isEyeInFluid(FluidTags.WATER) || entity.isEyeInFluid(FluidTags.LAVA))) return false
|
||||
return entity.onGround()
|
||||
// If the currently in the air but has no land style then don't transition and render the ride useless.
|
||||
// Hover is a good example for this.
|
||||
val shouldStayInAir = entity.pokemon.riding.behaviours?.get(RidingStyle.LAND) == null && context?.style == RidingStyle.AIR
|
||||
return entity.onGround() && !shouldStayInAir
|
||||
}
|
||||
|
||||
private fun canTransitionToLiquid(): Boolean {
|
||||
|
||||
+15
-2
@@ -213,10 +213,23 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
|
||||
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalHover.accelerationExpr!!) * 20.0)
|
||||
|
||||
var newVelocity = state.rideVelocity.get()
|
||||
val unrotateVel = vehicle.deltaMovement.yRot(vehicle.yRot)
|
||||
|
||||
val vehicleFrontVec = Vec3(0.0, 0.0, 1.0)
|
||||
val faceVel = Vec3(vehicle.deltaMovement.x, 0.0, vehicle.deltaMovement.z)
|
||||
|
||||
val d = vehicle.deltaMovement.horizontalDistance()
|
||||
val v2 = Vec3(faceVel.dot(Vec3(1.0, 0.0, 0.0)) * d, newVelocity.y, faceVel.dot(Vec3(0.0, 0.0, 1.0)) * d)
|
||||
|
||||
// Check for collisions and if found, update the speed to reflect it
|
||||
if (vehicle.verticalCollision || vehicle.horizontalCollision) {
|
||||
state.speed.set(vehicle.deltaMovement.length())
|
||||
if (vehicle.horizontalCollision) {
|
||||
val smackingIntoWallSpeed = max( vehicle.deltaMovement.horizontalDistance(), 0.2)
|
||||
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z).normalize().scale(smackingIntoWallSpeed).add(0.0, newVelocity.y, 0.0)
|
||||
}
|
||||
|
||||
|
||||
if (vehicle.verticalCollision) {
|
||||
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
|
||||
}
|
||||
|
||||
// align the velocity vector to be in local vehicle space
|
||||
|
||||
+26
-83
@@ -60,7 +60,7 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
|
||||
val poseProvider = PoseProvider<SubmarineSettings, SubmarineState>(PoseType.FLOAT)
|
||||
.with(PoseOption(PoseType.SWIM) { _, state, entity -> state.rideVelocity.get().z > 0.05 && !state.onSurface.get()})
|
||||
.with(PoseOption(PoseType.STAND) { _, state, entity -> state.rideVelocity.get().z <= 0.05 && state.onSurface.get()})
|
||||
.with(PoseOption(PoseType.STAND) { _, state, entity -> state.rideVelocity.get().z > 0.05 && state.onSurface.get()})
|
||||
.with(PoseOption(PoseType.SWIM) { _, state, entity -> state.rideVelocity.get().z > 0.05 && state.onSurface.get()})
|
||||
|
||||
override fun isActive(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
|
||||
return vehicle.isInWater || !vehicle.onGround()
|
||||
@@ -191,11 +191,17 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
|
||||
* Calculate Forward Velocity
|
||||
*********************************************/
|
||||
val currZVel = state.rideVelocity.get().z
|
||||
val newZVel = when {
|
||||
var newZVel = when {
|
||||
driver.zza > 0.0 -> min(topSpeed, currZVel + accel)
|
||||
driver.zza < 0.0 -> max( -strafeSpeed, currZVel - strafeAccel) //TODO: Might need to be applied in addition to the normal braking force?
|
||||
else -> lerp(currZVel, 0.0, 0.05)
|
||||
driver.zza < 0.0 -> max( -strafeSpeed, currZVel - strafeAccel)
|
||||
else -> lerp(currZVel, 0.0, 0.02)
|
||||
}
|
||||
|
||||
// If trying to brake then add the friction to the braking force
|
||||
if (driver.zza != 0.0f && driver.zza.sign.toDouble() != newZVel.sign) {
|
||||
newZVel = lerp(newZVel, 0.0, 0.02)
|
||||
}
|
||||
|
||||
currVel = Vec3(currVel.x, currVel.y, newZVel)
|
||||
|
||||
/*********************************************
|
||||
@@ -206,14 +212,20 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
|
||||
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.4
|
||||
val terminalYVel = 2.0
|
||||
val driverYInput = if(driver.jumping) 1.0 else if (driver.isShiftKeyDown) -1.0 else 0.0
|
||||
val newYVel = when {
|
||||
var newYVel = when {
|
||||
(!vehicle.isInWater) -> max(-terminalYVel, currYVel - gravity)
|
||||
//(vehicle.isInWater && !vehicle.isUnderWater && currYVel > 0.001) -> lerp(currYVel, 0.0, 0.1) // Make sure that yVel is above a threshold before doing this otherwise it'll block submerging.
|
||||
//driverYInput != 0.0 && currYVel > 0.001 && driverYInput.sign != currYVel.sign -> lerp(currYVel, 0.0, 0.05)
|
||||
driverYInput > 0.0 -> min(vertTopSpeed, currYVel + strafeAccel)
|
||||
driverYInput < 0.0 -> max(-vertTopSpeed, currYVel - strafeAccel)
|
||||
else -> lerp(currYVel, 0.0, 0.05)
|
||||
else -> lerp(currYVel, 0.0, 0.02)
|
||||
}
|
||||
|
||||
// If trying to brake then add the friction to the braking force
|
||||
if (driverYInput != 0.0 && driverYInput != newYVel.sign) {
|
||||
newYVel = lerp(newYVel, 0.0, 0.02)
|
||||
}
|
||||
|
||||
currVel = Vec3(currVel.x, newYVel, currVel.z)
|
||||
|
||||
/*********************************************
|
||||
@@ -221,11 +233,17 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
|
||||
*********************************************/
|
||||
val currXVel = state.rideVelocity.get().x
|
||||
val horzTopSpeed = strafeSpeed
|
||||
val newXVel = when {
|
||||
var newXVel = when {
|
||||
driver.xxa > 0.0 -> min(horzTopSpeed, currXVel + strafeAccel)
|
||||
driver.xxa < 0.0 -> max( -horzTopSpeed, currXVel - strafeAccel)
|
||||
else -> lerp(currXVel, 0.0, 0.01)
|
||||
else -> lerp(currXVel, 0.0, 0.02)
|
||||
}
|
||||
|
||||
// If trying to brake then add the friction to the braking force
|
||||
if (driver.xxa != 0.0f && driver.xxa.sign.toDouble() != newZVel.sign) {
|
||||
newXVel = lerp(newXVel, 0.0, 0.02)
|
||||
}
|
||||
|
||||
currVel = Vec3(newXVel, currVel.y, currVel.z)
|
||||
|
||||
// Set the new velocity
|
||||
@@ -305,81 +323,6 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
|
||||
|
||||
}
|
||||
|
||||
private fun correctOrientation(
|
||||
settings: SubmarineSettings,
|
||||
state: SubmarineState,
|
||||
vehicle: PokemonEntity,
|
||||
controller: OrientationController,
|
||||
driver: Player,
|
||||
deltaTime: Double
|
||||
) {
|
||||
|
||||
// TODO: remove if not needed
|
||||
|
||||
// // Calculate correcting roll force.
|
||||
// // If the ride is moving then have the correction rate be an equal proportion of:
|
||||
// // - how rolled are you
|
||||
// // - how long has it been since last mouse input (higher is stronger correction)
|
||||
// // Also the correction is proportional as a whole to how NOT pitched are you so you don't spin endlessly when looking up or down
|
||||
// val howRolledAmI = sqrt(abs((cos(controller.roll.toRadians()) - 1.0f) * 0.5f)) // sqrt so it trends towards one pretty quickly
|
||||
// val maxRollCorrectionRate =
|
||||
// if (state.rideVelocity.get().length() < 0.2) 10.0f * abs(cos(controller.pitch.toRadians()))
|
||||
// else 10.0f * cos(controller.pitch.toRadians()).pow(2) * (
|
||||
// howRolledAmI*1.0f +
|
||||
// (state.noInputTime.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
|
||||
// )
|
||||
// val maxRollForce = maxRollCorrectionRate / 40.0f
|
||||
// val rollArrivalDeg = 60.0f
|
||||
// val desiredRoll = 0.0f
|
||||
//
|
||||
// // Calculate the signed error (how far and in what direction)
|
||||
// val rollError = Mth.wrapDegrees(desiredRoll.angleDifference(controller.roll))
|
||||
//
|
||||
// // Use the absolute error to calculate the magnitude of the influence
|
||||
// val influenceMagnitude = (min(rollArrivalDeg, abs(rollError)) / rollArrivalDeg)
|
||||
// val desiredRollForce = maxRollCorrectionRate * influenceMagnitude * -rollError.sign
|
||||
// var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
|
||||
//
|
||||
// // Ensure the 'steering' correction force doesn't exceed our maxForce
|
||||
// if (abs(steeredRollForce) > maxRollForce) {
|
||||
// steeredRollForce = steeredRollForce.sign * maxRollForce
|
||||
// }
|
||||
//
|
||||
// // Apply the 'steered' force (its really not a steering force I need a better name)
|
||||
// state.currRollCorrectionForce.set(
|
||||
// state.currRollCorrectionForce.get() + steeredRollForce
|
||||
// )
|
||||
|
||||
//Calculate correcting pitch force.
|
||||
// val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalSubmarine.timeToRollCorrect!!)
|
||||
// val maxPitchCorrectionRate = 5.0f * (state.noInputTime.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
|
||||
// val maxPitchForce = maxPitchCorrectionRate / 40.0f
|
||||
// val pitchArrivalDeg = 45.0f
|
||||
// val desiredPitch = 0.0f
|
||||
//
|
||||
// if(state.rideVelocity.get().length() < 0.1 && state.noInputTime.get() > rollCorrectionTimer) {
|
||||
// val pitchErrror = Mth.wrapDegrees(desiredPitch.angleDifference(controller.pitch))
|
||||
// val arrivalInfluence = (min(pitchArrivalDeg,abs(pitchErrror)) / pitchArrivalDeg)
|
||||
// val desiredPitchForce = maxPitchCorrectionRate * arrivalInfluence * -pitchErrror.sign
|
||||
// var steeredPitchForce = desiredPitchForce - state.currPitchCorrectionForce.get()
|
||||
//
|
||||
// // Ensure the 'steering' correction force doesn't exceed our maxForce
|
||||
// if (abs(steeredPitchForce) > maxPitchForce) {
|
||||
// steeredPitchForce = steeredPitchForce.sign * maxPitchForce
|
||||
// }
|
||||
//
|
||||
// // Apply the 'steered' force (its really not a steering force I need a better name)
|
||||
// state.currPitchCorrectionForce.set(
|
||||
// state.currPitchCorrectionForce.get() + steeredPitchForce
|
||||
// )
|
||||
// } else {
|
||||
// state.currPitchCorrectionForce.set(
|
||||
// //reduce rollCorrection gradually
|
||||
// lerp(state.currPitchCorrectionForce.get(), 0.0, deltaTime * 0.98)
|
||||
// )
|
||||
// }
|
||||
}
|
||||
|
||||
override fun rotationOnMouseXY(
|
||||
settings: SubmarineSettings,
|
||||
state: SubmarineState,
|
||||
|
||||
@@ -44,6 +44,7 @@ import com.cobblemon.mod.common.api.spawning.preset.BestSpawnerConfig
|
||||
import com.cobblemon.mod.common.api.spawning.preset.PokemonSpawnDetailPreset
|
||||
import com.cobblemon.mod.common.api.spawning.selection.SpawningSelector
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.BasicSpawner
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.FishingSpawnerFactory
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.FixedAreaSpawner
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner
|
||||
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory
|
||||
@@ -139,19 +140,6 @@ object BestSpawner {
|
||||
|
||||
fun onServerStarted(server: MinecraftServer) {
|
||||
CobblemonSpawnPools.onServerLoad(server)
|
||||
fishingSpawner = BasicSpawner(
|
||||
name = "fishing",
|
||||
spawnPool = CobblemonSpawnPools.WORLD_SPAWN_POOL
|
||||
).also {
|
||||
it.influences.add(
|
||||
BucketMultiplyingInfluence(
|
||||
multipliers = mapOf(
|
||||
"uncommon" to 2.25f,
|
||||
"rare" to 5.5f,
|
||||
"ultra-rare" to 5.5f,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
fishingSpawner = FishingSpawnerFactory.createSharedSpawner()
|
||||
}
|
||||
}
|
||||
+71
-51
@@ -44,7 +44,7 @@ import kotlin.random.Random
|
||||
* @since July 10th, 2022
|
||||
*/
|
||||
open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelectionData> {
|
||||
class SelectingSpawnInformation {
|
||||
class SelectingSpawnInformation(val spawnDetail: SpawnDetail) {
|
||||
val spawnablePositions = mutableMapOf<SpawnablePosition, Float>()
|
||||
var highestWeight = 0F
|
||||
|
||||
@@ -58,61 +58,81 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
|
||||
fun chooseSpawnablePosition() = spawnablePositions.entries.weightedSelection { it.value }!!.key
|
||||
}
|
||||
|
||||
class SpawnablePositionSelectionData(
|
||||
val spawnToSpawnablePosition: MutableMap<SpawnDetail, SelectingSpawnInformation>,
|
||||
var percentSum: Float
|
||||
): SpawnSelectionData {
|
||||
val size: Int
|
||||
get() = spawnToSpawnablePosition.size
|
||||
class SpawnablePositionBucketSelectionData(
|
||||
val selectingSpawnInformation: MutableList<SelectingSpawnInformation>
|
||||
) {
|
||||
var percentSum: Float = 0F
|
||||
val seenSpawns = mutableListOf<SpawnDetail>()
|
||||
|
||||
fun add(spawnDetail: SpawnDetail, spawnablePosition: SpawnablePosition) {
|
||||
// Only add to percentSum if this is the first time we've seen this SpawnDetail, otherwise
|
||||
// the percentage will get amplified for every spawnable position the thing was possible, completely
|
||||
// ruining the point of this pre-selection percentage.
|
||||
if (spawnDetail.percentage > 0 && spawnDetail !in seenSpawns) {
|
||||
seenSpawns.add(spawnDetail)
|
||||
percentSum += spawnDetail.percentage
|
||||
}
|
||||
val spawnInformation = selectingSpawnInformation.find { it.spawnDetail == spawnDetail }
|
||||
?: SelectingSpawnInformation(spawnDetail).also { selectingSpawnInformation.add(it) }
|
||||
spawnInformation.add(spawnablePosition, spawnablePosition.getWeight(spawnDetail))
|
||||
}
|
||||
|
||||
fun remove(spawnInformation: SelectingSpawnInformation) {
|
||||
selectingSpawnInformation -= spawnInformation
|
||||
if (spawnInformation.spawnDetail.percentage > 0) {
|
||||
percentSum -= spawnInformation.spawnDetail.percentage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SpawnablePositionSelectionData(
|
||||
val spawner: Spawner,
|
||||
val spawnablePositions: List<SpawnablePosition>,
|
||||
val selectingSpawnInformation: MutableMap<SpawnBucket, SpawnablePositionBucketSelectionData>,
|
||||
): SpawnSelectionData {
|
||||
override val spawnActions = mutableListOf<SpawnAction<*>>()
|
||||
override val context = mutableMapOf<String, Any>()
|
||||
|
||||
fun getDataForBucket(bucket: SpawnBucket): SpawnablePositionBucketSelectionData {
|
||||
val existing = selectingSpawnInformation[bucket]
|
||||
if (existing != null) {
|
||||
return existing
|
||||
}
|
||||
|
||||
val bucketSpawnInformation = SpawnablePositionBucketSelectionData(mutableListOf())
|
||||
spawnablePositions.forEach { spawnablePosition ->
|
||||
spawner.getMatchingSpawns(bucket, spawnablePosition).forEach {
|
||||
bucketSpawnInformation.add(it, spawnablePosition)
|
||||
}
|
||||
}
|
||||
|
||||
selectingSpawnInformation[bucket] = bucketSpawnInformation
|
||||
return bucketSpawnInformation
|
||||
}
|
||||
|
||||
override fun removeSpawnDetails(shouldRemove: (SpawnDetail) -> Boolean) {
|
||||
val toRemove = spawnToSpawnablePosition.entries.filter { shouldRemove(it.key) }
|
||||
toRemove.forEach { spawnToSpawnablePosition.remove(it.key) }
|
||||
percentSum -= toRemove.sumOf { it.key.percentage.toDouble().takeIf { it > 0 } ?: 0.0 }.toFloat()
|
||||
selectingSpawnInformation.flatMap { it.value.selectingSpawnInformation.filter { shouldRemove(it.spawnDetail) } }
|
||||
.forEach { selectingSpawnInformation[it.spawnDetail.bucket]?.remove(it) }
|
||||
}
|
||||
|
||||
override fun removeSpawnablePositions(shouldRemove: (SpawnDetail, SpawnablePosition) -> Boolean) {
|
||||
val toRemove = spawnToSpawnablePosition.entries.filter { (spawnDetail, positionData) ->
|
||||
positionData.spawnablePositions.removeIf { shouldRemove(spawnDetail, it.key) }
|
||||
val toRemove = selectingSpawnInformation.flatMap { it.value.selectingSpawnInformation }.filter { positionData ->
|
||||
positionData.spawnablePositions.removeIf { shouldRemove(positionData.spawnDetail, it.key) }
|
||||
positionData.highestWeight = positionData.spawnablePositions.maxOfOrNull { it.value } ?: 0F
|
||||
positionData.spawnablePositions.isEmpty()
|
||||
}
|
||||
|
||||
toRemove.forEach { spawnToSpawnablePosition.remove(it.key) }
|
||||
|
||||
percentSum -= toRemove.sumOf { it.key.percentage.toDouble().takeIf { it > 0 } ?: 0.0 }.toFloat()
|
||||
toRemove.forEach { selectingSpawnInformation[it.spawnDetail.bucket]?.remove(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSelectionData(
|
||||
spawner: Spawner,
|
||||
bucket: SpawnBucket,
|
||||
spawnablePositions: List<SpawnablePosition>
|
||||
): SpawnablePositionSelectionData {
|
||||
val spawnToSpawnablePosition: MutableMap<SpawnDetail, SelectingSpawnInformation> = mutableMapOf()
|
||||
var percentSum = 0F
|
||||
val selectingSpawnInformation: MutableMap<SpawnBucket, SpawnablePositionBucketSelectionData> = mutableMapOf()
|
||||
|
||||
spawnablePositions.forEach { spawnablePosition ->
|
||||
spawner.getMatchingSpawns(bucket, spawnablePosition).forEach {
|
||||
// Only add to percentSum if this is the first time we've seen this SpawnDetail, otherwise
|
||||
// the percentage will get amplified for every spawnable position the thing was possible, completely
|
||||
// ruining the point of this pre-selection percentage.
|
||||
if (it.percentage > 0 && !spawnToSpawnablePosition.containsKey(it)) {
|
||||
percentSum += it.percentage
|
||||
}
|
||||
|
||||
val selectingSpawnInformation = spawnToSpawnablePosition.getOrPut(
|
||||
it,
|
||||
::SelectingSpawnInformation
|
||||
)
|
||||
selectingSpawnInformation.add(spawnablePosition, spawnablePosition.getWeight(it))
|
||||
}
|
||||
}
|
||||
|
||||
return SpawnablePositionSelectionData(spawnToSpawnablePosition, percentSum)
|
||||
return SpawnablePositionSelectionData(spawner, spawnablePositions, selectingSpawnInformation)
|
||||
}
|
||||
|
||||
override fun selectSpawnAction(
|
||||
@@ -120,8 +140,8 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
|
||||
bucket: SpawnBucket,
|
||||
selectionData: SpawnablePositionSelectionData,
|
||||
): SpawnAction<*>? {
|
||||
val spawnToSpawnablePosition = selectionData.spawnToSpawnablePosition
|
||||
var percentSum = selectionData.percentSum
|
||||
val selectingSpawnInformation = selectionData.getDataForBucket(bucket) ?: return null
|
||||
var percentSum = selectingSpawnInformation.percentSum
|
||||
|
||||
// First pass is doing percentage checks.
|
||||
if (percentSum > 0) {
|
||||
@@ -129,7 +149,7 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
|
||||
if (percentSum > 100) {
|
||||
Cobblemon.LOGGER.warn(
|
||||
"""
|
||||
A spawn list for ${spawner.name} exceeded 100% on percentage sums...
|
||||
A spawn list for ${spawner.name} exceeded 100% on percentage sums in bucket ${bucket.name}...
|
||||
This means you don't understand how this option works.
|
||||
""".trimIndent()
|
||||
)
|
||||
@@ -140,13 +160,13 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
|
||||
* It's [0, 1) and I want (0, 1]
|
||||
* See half-open intervals here https://en.wikipedia.org/wiki/Interval_(mathematics)#Terminology
|
||||
*/
|
||||
val selectedPercentage = 100 - Random.Default.nextFloat() * 100
|
||||
val selectedPercentage = 100 - Random.nextFloat() * 100
|
||||
percentSum = 0F
|
||||
for ((spawnDetail, info) in spawnToSpawnablePosition) {
|
||||
if (spawnDetail.percentage > 0) {
|
||||
percentSum += spawnDetail.percentage
|
||||
for (info in selectingSpawnInformation.selectingSpawnInformation) {
|
||||
if (info.spawnDetail.percentage > 0) {
|
||||
percentSum += info.spawnDetail.percentage
|
||||
if (percentSum >= selectedPercentage) {
|
||||
return spawnDetail.choose(
|
||||
return info.spawnDetail.choose(
|
||||
spawnablePosition = info.chooseSpawnablePosition(),
|
||||
bucket = bucket,
|
||||
selectionData = selectionData
|
||||
@@ -156,9 +176,9 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
|
||||
}
|
||||
}
|
||||
|
||||
val selectedSpawn = spawnToSpawnablePosition.entries.toList().weightedSelection { it.value.highestWeight }!!
|
||||
return selectedSpawn.key.choose(
|
||||
spawnablePosition = selectedSpawn.value.chooseSpawnablePosition(),
|
||||
val selectedSpawn = selectingSpawnInformation.selectingSpawnInformation.toList().weightedSelection { it.highestWeight }!!
|
||||
return selectedSpawn.spawnDetail.choose(
|
||||
spawnablePosition = selectedSpawn.chooseSpawnablePosition(),
|
||||
bucket = bucket,
|
||||
selectionData = selectionData
|
||||
)
|
||||
@@ -169,16 +189,16 @@ open class FlatSelector : SpawningSelector<FlatSelector.SpawnablePositionSelecti
|
||||
bucket: SpawnBucket,
|
||||
spawnablePositions: List<SpawnablePosition>
|
||||
): Map<SpawnDetail, Float> {
|
||||
val selectionData = getSelectionData(spawner, bucket, spawnablePositions)
|
||||
val selectionData = getSelectionData(spawner, spawnablePositions).getDataForBucket(bucket)
|
||||
|
||||
if (selectionData.size == 0) {
|
||||
if (selectionData.selectingSpawnInformation.isEmpty()) {
|
||||
return emptyMap()
|
||||
}
|
||||
|
||||
val totalWeights = mutableMapOf<SpawnDetail, Float>()
|
||||
|
||||
for ((spawnDetail, info) in selectionData.spawnToSpawnablePosition.entries) {
|
||||
totalWeights[spawnDetail] = info.highestWeight
|
||||
for (info in selectionData.selectingSpawnInformation) {
|
||||
totalWeights[info.spawnDetail] = info.highestWeight
|
||||
}
|
||||
|
||||
return totalWeights
|
||||
|
||||
+67
-55
@@ -18,7 +18,6 @@ import com.cobblemon.mod.common.api.spawning.spawner.Spawner
|
||||
import com.cobblemon.mod.common.util.removeIf
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
import kotlin.random.Random
|
||||
import kotlin.reflect.full.createInstance
|
||||
|
||||
/**
|
||||
* A spawning selector that compiles a distinct list of all spawn details that
|
||||
@@ -33,9 +32,9 @@ import kotlin.reflect.full.createInstance
|
||||
* of an entire spawnable position type to favour whichever are the more populous.
|
||||
*
|
||||
* The weight for a spawn when doing primary selection is whichever spawnable position-influenced
|
||||
* weight is highest (as weight multipliers exist per spawnable position) and then the
|
||||
* selection of which spawnable position to spawn the primary selected spawn uses the spawnable
|
||||
* position-adjusted weight.
|
||||
* weight is highest (as weight multipliers exist per spawnable position). After the spawn has been
|
||||
* chosen, the selection of which spawnable position to spawn it at is selected via another weighted
|
||||
* selection using spawnable position-adjusted weights.
|
||||
*
|
||||
* At a glance:
|
||||
* - Spawn detail selection is flat across spawnable position quantity
|
||||
@@ -45,7 +44,6 @@ import kotlin.reflect.full.createInstance
|
||||
* @since July 10th, 2022
|
||||
*/
|
||||
open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnablePositionWeightedSelector.SeparatedSelectionData> {
|
||||
open fun getWeight(spawnablePositionType: SpawnablePositionType<*>) = spawnablePositionType.getWeight()
|
||||
|
||||
class SelectingSpawnInformation {
|
||||
val spawnablePositionWeights = mutableMapOf<SpawnablePosition, Float>()
|
||||
@@ -62,13 +60,25 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
|
||||
fun chooseSpawnablePosition() = spawnablePositionWeights.keys.toList().weightedSelection { spawnablePositionWeights[it]!! }!!
|
||||
}
|
||||
|
||||
class SpawnablePositionSelectionData(
|
||||
val spawnToSpawnablePosition: MutableMap<SpawnDetail, SelectingSpawnInformation>,
|
||||
var percentSum: Float
|
||||
) {
|
||||
class SpawnablePositionSelectionData {
|
||||
val spawnToSpawnablePosition = mutableMapOf<SpawnDetail, SelectingSpawnInformation>()
|
||||
var percentSum: Float = 0F
|
||||
val seenSpawns = mutableListOf<SpawnDetail>()
|
||||
val size: Int
|
||||
get() = spawnToSpawnablePosition.size
|
||||
|
||||
fun add(spawnDetail: SpawnDetail, spawnablePosition: SpawnablePosition) {
|
||||
// Only add to percentSum if this is the first time we've seen this SpawnDetail for this spawnable
|
||||
// position type, otherwise the percentage will get amplified for every spawnable position the thing
|
||||
// was possible, completely ruining the point of this pre-selection percentage.
|
||||
if (spawnDetail.percentage > 0 && spawnDetail !in seenSpawns) {
|
||||
percentSum += spawnDetail.percentage
|
||||
seenSpawns.add(spawnDetail)
|
||||
}
|
||||
val selectingSpawnInformation = spawnToSpawnablePosition.getOrPut(spawnDetail, ::SelectingSpawnInformation)
|
||||
selectingSpawnInformation.add(spawnDetail, spawnablePosition, 1F)
|
||||
}
|
||||
|
||||
fun removeSpawnDetails(shouldRemove: (SpawnDetail) -> Boolean) {
|
||||
val toRemove = spawnToSpawnablePosition.entries.filter { shouldRemove(it.key) }
|
||||
toRemove.forEach { spawnToSpawnablePosition.remove(it.key) }
|
||||
@@ -89,67 +99,68 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
|
||||
}
|
||||
|
||||
class SeparatedSelectionData(
|
||||
val spawnablePositionTypeToSpawns: MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData>
|
||||
): SpawnSelectionData {
|
||||
val spawner: Spawner,
|
||||
val spawnablePositions: List<SpawnablePosition>
|
||||
) : SpawnSelectionData {
|
||||
override val spawnActions = mutableListOf<SpawnAction<*>>()
|
||||
override val context = mutableMapOf<String, Any>()
|
||||
|
||||
val bucketData = mutableMapOf<SpawnBucket, MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData>>()
|
||||
|
||||
override fun removeSpawnDetails(shouldRemove: (SpawnDetail) -> Boolean) {
|
||||
spawnablePositionTypeToSpawns.values.forEach { it.removeSpawnDetails(shouldRemove) }
|
||||
spawnablePositionTypeToSpawns.removeIf { it.value.size == 0 }
|
||||
bucketData.values.forEach {
|
||||
it.values.forEach { it.removeSpawnDetails(shouldRemove) }
|
||||
it.removeIf { it.value.size == 0 }
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeSpawnablePositions(shouldRemove: (SpawnDetail, SpawnablePosition) -> Boolean) {
|
||||
spawnablePositionTypeToSpawns.values.forEach { it.removeSpawnablePositions(shouldRemove) }
|
||||
spawnablePositionTypeToSpawns.removeIf { it.value.size == 0 }
|
||||
bucketData.values.forEach {
|
||||
it.values.forEach { it.removeSpawnablePositions(shouldRemove) }
|
||||
it.removeIf { it.value.size == 0 }
|
||||
}
|
||||
}
|
||||
|
||||
fun getDataForBucket(bucket: SpawnBucket): MutableMap<SpawnablePositionType<*>, SpawnablePositionSelectionData> {
|
||||
bucketData[bucket]?.let { return it }
|
||||
|
||||
val spawnablePositionTypeToSpawns = mutableMapOf<SpawnablePositionType<*>, SpawnablePositionSelectionData>()
|
||||
|
||||
spawnablePositions.forEach { spawnablePosition ->
|
||||
val spawnablePositionType = SpawnablePosition.getByClass(spawnablePosition)!!
|
||||
|
||||
val possible = spawner.getMatchingSpawns(bucket, spawnablePosition)
|
||||
if (possible.isNotEmpty()) {
|
||||
val spawnablePositionSelectionData = spawnablePositionTypeToSpawns
|
||||
.getOrPut(spawnablePositionType, ::SpawnablePositionSelectionData)
|
||||
possible.forEach { spawnablePositionSelectionData.add(it, spawnablePosition) }
|
||||
}
|
||||
}
|
||||
|
||||
bucketData[bucket] = spawnablePositionTypeToSpawns
|
||||
return spawnablePositionTypeToSpawns
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSelectionData(
|
||||
spawner: Spawner,
|
||||
bucket: SpawnBucket,
|
||||
spawnablePositions: List<SpawnablePosition>
|
||||
): SeparatedSelectionData {
|
||||
val spawnablePositionTypeToSpawns = mutableMapOf<SpawnablePositionType<*>, SpawnablePositionSelectionData>()
|
||||
|
||||
spawnablePositions.forEach { spawnablePosition ->
|
||||
val spawnablePositionType = SpawnablePosition.getByClass(spawnablePosition)!!
|
||||
|
||||
val possible = spawner.getMatchingSpawns(bucket, spawnablePosition)
|
||||
if (possible.isNotEmpty()) {
|
||||
val spawnablePositionSelectionData = spawnablePositionTypeToSpawns.getOrPut(spawnablePositionType) { SpawnablePositionSelectionData(mutableMapOf(), 0F) }
|
||||
possible.forEach {
|
||||
// Only add to percentSum if this is the first time we've seen this SpawnDetail for this spawnable
|
||||
// position type, otherwise the percentage will get amplified for every spawnable position the thing
|
||||
// was possible, completely ruining the point of this pre-selection percentage.
|
||||
if (it.percentage > 0 && !spawnablePositionSelectionData.spawnToSpawnablePosition.containsKey(it)) {
|
||||
spawnablePositionSelectionData.percentSum += it.percentage
|
||||
}
|
||||
|
||||
val selectingSpawnInformation = spawnablePositionSelectionData.spawnToSpawnablePosition.getOrPut(
|
||||
it,
|
||||
SelectingSpawnInformation::class::createInstance
|
||||
)
|
||||
selectingSpawnInformation.add(it, spawnablePosition, getWeight(spawnablePositionType))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SeparatedSelectionData(spawnablePositionTypeToSpawns)
|
||||
}
|
||||
) = SeparatedSelectionData(spawner, spawnablePositions)
|
||||
|
||||
override fun selectSpawnAction(
|
||||
spawner: Spawner,
|
||||
bucket: SpawnBucket,
|
||||
selectionData: SeparatedSelectionData
|
||||
): SpawnAction<*>? {
|
||||
if (selectionData.spawnablePositionTypeToSpawns.isEmpty()) {
|
||||
val bucketData = selectionData.getDataForBucket(bucket)
|
||||
|
||||
if (bucketData.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Which spawnable position type should we use?
|
||||
val spawnablePositionSelectionData = selectionData.spawnablePositionTypeToSpawns.entries.toList()
|
||||
.weightedSelection { getWeight(it.key) * it.value.size }
|
||||
val spawnablePositionSelectionData = bucketData.entries.toList()
|
||||
.weightedSelection { it.key.getWeight() * it.value.size }
|
||||
?.value
|
||||
?: return null
|
||||
|
||||
@@ -161,7 +172,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
|
||||
if (percentSum > 100) {
|
||||
LOGGER.warn(
|
||||
"""
|
||||
A spawn list for ${spawner.name} exceeded 100% on percentage sums...
|
||||
A spawn list for ${spawner.name} exceeded 100% on percentage sums in bucket ${bucket.name}...
|
||||
This means you don't understand how this option works.
|
||||
""".trimIndent()
|
||||
)
|
||||
@@ -173,7 +184,7 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
|
||||
* It's [0, 1) and I want (0, 1]
|
||||
* See half-open intervals here https://en.wikipedia.org/wiki/Interval_(mathematics)#Terminology
|
||||
*/
|
||||
val selectedPercentage = 100 - Random.Default.nextFloat() * 100
|
||||
val selectedPercentage = 100 - Random.nextFloat() * 100
|
||||
percentSum = 0F
|
||||
for ((spawnDetail, info) in spawnToSpawnablePosition) {
|
||||
if (spawnDetail.percentage > 0) {
|
||||
@@ -224,7 +235,8 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
|
||||
val percentageWeight = (rescaledTotalWeight - totalWeight) / percentSum
|
||||
|
||||
for ((spawnDetail, info) in spawnToSpawnablePosition.entries) {
|
||||
totalWeights[spawnDetail] = info.highestWeight + if (spawnDetail.percentage > 0) spawnDetail.percentage * percentageWeight else 0F
|
||||
totalWeights[spawnDetail] =
|
||||
info.highestWeight + if (spawnDetail.percentage > 0) spawnDetail.percentage * percentageWeight else 0F
|
||||
}
|
||||
|
||||
return totalWeights
|
||||
@@ -235,18 +247,18 @@ open class FlatSpawnablePositionWeightedSelector : SpawningSelector<FlatSpawnabl
|
||||
bucket: SpawnBucket,
|
||||
spawnablePositions: List<SpawnablePosition>
|
||||
): Map<SpawnDetail, Float> {
|
||||
val selectionData = getSelectionData(spawner, bucket, spawnablePositions)
|
||||
val selectionData = getSelectionData(spawner, spawnablePositions).getDataForBucket(bucket)
|
||||
|
||||
if (selectionData.spawnablePositionTypeToSpawns.isEmpty()) {
|
||||
if (selectionData.isEmpty()) {
|
||||
return mapOf()
|
||||
}
|
||||
|
||||
val totalWeights = mutableMapOf<SpawnDetail, Float>()
|
||||
|
||||
val totalSpawnablePositionWeight = selectionData.spawnablePositionTypeToSpawns.keys.sumOf { getWeight(it).toDouble() }.toFloat()
|
||||
val totalSpawnablePositionWeight = selectionData.keys.sumOf { it.getWeight().toDouble() }.toFloat()
|
||||
|
||||
for ((spawnablePositionType, spawnPositionSelectionData) in selectionData.spawnablePositionTypeToSpawns) {
|
||||
val spawnPositionWeightCorrection = getWeight(spawnablePositionType) / totalSpawnablePositionWeight
|
||||
for ((spawnablePositionType, spawnPositionSelectionData) in selectionData) {
|
||||
val spawnPositionWeightCorrection = spawnablePositionType.getWeight() / totalSpawnablePositionWeight
|
||||
val spawnPositionProbabilities = getProbabilitiesFromSpawnablePositionType(spawner, spawnPositionSelectionData)
|
||||
|
||||
spawnPositionProbabilities.entries.forEach {
|
||||
|
||||
+9
-3
@@ -29,7 +29,7 @@ interface SpawningSelector<T : SpawnSelectionData> {
|
||||
val DEFAULT = FlatSpawnablePositionWeightedSelector()
|
||||
}
|
||||
|
||||
fun getSelectionData(spawner: Spawner, bucket: SpawnBucket, spawnablePositions: List<SpawnablePosition>): T
|
||||
fun getSelectionData(spawner: Spawner, spawnablePositions: List<SpawnablePosition>): T
|
||||
|
||||
fun selectSpawnAction(
|
||||
spawner: Spawner,
|
||||
@@ -37,12 +37,18 @@ interface SpawningSelector<T : SpawnSelectionData> {
|
||||
selectionData: T
|
||||
): SpawnAction<*>?
|
||||
|
||||
fun select(spawner: Spawner, bucket: SpawnBucket, spawnablePositions: List<SpawnablePosition>, maxSpawns: Int): List<SpawnAction<*>> {
|
||||
val selectionData = getSelectionData(spawner, bucket, spawnablePositions)
|
||||
fun select(
|
||||
spawner: Spawner,
|
||||
bucketSelector: () -> SpawnBucket,
|
||||
spawnablePositions: List<SpawnablePosition>,
|
||||
maxSpawns: Int
|
||||
): List<SpawnAction<*>> {
|
||||
val selectionData = getSelectionData(spawner, spawnablePositions)
|
||||
|
||||
val spawnActions = selectionData.spawnActions
|
||||
|
||||
while (spawnActions.size < maxSpawns) {
|
||||
val bucket = bucketSelector()
|
||||
val spawnAction = selectSpawnAction(
|
||||
spawner = spawner,
|
||||
bucket = bucket,
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.api.spawning.spawner
|
||||
|
||||
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
|
||||
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnRules
|
||||
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
|
||||
import com.cobblemon.mod.common.api.spawning.influence.BucketMultiplyingInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence.Companion.TYPICAL_VARIATION
|
||||
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
|
||||
import com.cobblemon.mod.common.util.mutableLazy
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import com.cobblemon.mod.common.api.spawning.BestSpawner;
|
||||
import com.cobblemon.mod.common.api.spawning.position.FishingSpawnablePosition
|
||||
|
||||
|
||||
/**
|
||||
* This isn't really a factory, one FishingSpawner is created as a field on [BestSpawner] on
|
||||
* server start. However, this emulates the systems that PlayerSpawner uses so extenders can
|
||||
* add influences that affect all fishing encounters.
|
||||
*
|
||||
* Responsible for creating [BasicSpawner]s for Fishing with whatever appropriate settings.
|
||||
* You can swap over the spawn pool and the influences here, and it will apply to all newly-generated
|
||||
* [BasicSpawner]s for Fishing. Has a global every-cast influence list, as well as a context-sensitive
|
||||
* influence list.
|
||||
*
|
||||
* @author Andy
|
||||
* @since January 23rd, 2026
|
||||
*/
|
||||
object FishingSpawnerFactory {
|
||||
var spawnPool: SpawnPool by mutableLazy {
|
||||
CobblemonSpawnPools.WORLD_SPAWN_POOL
|
||||
}
|
||||
|
||||
data class Context(
|
||||
val player: ServerPlayer,
|
||||
val rodStack: ItemStack,
|
||||
val lureLevel: Int,
|
||||
val luckOfTheSeaLevel: Int,
|
||||
val world: ServerLevel,
|
||||
val pos: BlockPos,
|
||||
val spawner: BasicSpawner
|
||||
)
|
||||
|
||||
/**
|
||||
* List of influences for a new cast of the fishing rod. Context is provided at
|
||||
* build time in [buildPositionInfluences]
|
||||
*/
|
||||
var positionInfluenceBuilders = mutableListOf<(ctx: Context) -> SpawningInfluence?>(
|
||||
{ ctx -> PlayerLevelRangeInfluence(ctx.player, TYPICAL_VARIATION) },
|
||||
{ ctx -> BucketNormalizingInfluence(tier = ctx.lureLevel + ctx.luckOfTheSeaLevel) }
|
||||
)
|
||||
|
||||
fun createSharedSpawner(): BasicSpawner {
|
||||
return BasicSpawner(
|
||||
name = "fishing",
|
||||
spawnPool = spawnPool
|
||||
).also { spawner ->
|
||||
spawner.influences += BucketMultiplyingInfluence(
|
||||
multipliers = mapOf(
|
||||
"uncommon" to 2.25f,
|
||||
"rare" to 5.5f,
|
||||
"ultra-rare" to 5.5f,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the per-cast influences for [FishingSpawnablePosition]
|
||||
*/
|
||||
fun buildPositionInfluences(ctx: Context): MutableList<SpawningInfluence> {
|
||||
return positionInfluenceBuilders.mapNotNullTo(mutableListOf()) { it(ctx) }
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.api.spawning.spawner
|
||||
|
||||
import com.cobblemon.mod.common.api.fishing.SpawnBait
|
||||
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
|
||||
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnRules
|
||||
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
|
||||
import com.cobblemon.mod.common.api.spawning.influence.BucketMultiplyingInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
|
||||
import com.cobblemon.mod.common.api.spawning.rules.SpawnRule
|
||||
import com.cobblemon.mod.common.util.mutableLazy
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
|
||||
/**
|
||||
* Responsible for creating [FixedAreaSpawner]s for PokeSnacks with whatever appropriate settings.
|
||||
* You can swap over the spawn pool and the influences here, and it will apply to all newly-generated
|
||||
* [FixedAreaSpawner]s for PokeSnacks.
|
||||
*
|
||||
* @author Andy
|
||||
* @since January 23rd, 2026
|
||||
*/
|
||||
object PokeSnackSpawnerFactory {
|
||||
var spawnPool: SpawnPool by mutableLazy {
|
||||
CobblemonSpawnPools.WORLD_SPAWN_POOL
|
||||
}
|
||||
|
||||
data class Context(
|
||||
val world: ServerLevel,
|
||||
val pos: BlockPos,
|
||||
val horizontalRadius: Int,
|
||||
val verticalRadius: Int,
|
||||
val maxPokemonPerChunk: Float,
|
||||
val name: String,
|
||||
val baitEffects: List<SpawnBait.Effect>
|
||||
)
|
||||
|
||||
/**
|
||||
* The list of influences for a new [FixedAreaSpawner] for a PokeSnack.
|
||||
* The influences will be added to the spawner in the order they are added to this list.
|
||||
*/
|
||||
var influenceBuilders = mutableListOf<(ctx: Context) -> SpawningInfluence?>(
|
||||
{ ctx ->
|
||||
val stackedLureTier = ctx.baitEffects
|
||||
.filter { it.type == SpawnBait.Effects.RARITY_BUCKET }
|
||||
.sumOf { it.value }
|
||||
.toInt()
|
||||
|
||||
if (stackedLureTier <= 0) null else BucketNormalizingInfluence(
|
||||
tier = stackedLureTier,
|
||||
gradient = 0.2F,
|
||||
firstTier = 1.2F
|
||||
)
|
||||
},
|
||||
{ _ ->
|
||||
BucketMultiplyingInfluence(
|
||||
mapOf(
|
||||
"uncommon" to 2.25f,
|
||||
"rare" to 5.5f,
|
||||
"ultra-rare" to 5.5f,
|
||||
)
|
||||
)
|
||||
},
|
||||
{ ctx ->
|
||||
SpawnBaitInfluence(effects = ctx.baitEffects)
|
||||
},
|
||||
)
|
||||
|
||||
fun create(ctx: Context): FixedAreaSpawner {
|
||||
val influences = influenceBuilders.mapNotNull { it(ctx) }
|
||||
|
||||
return FixedAreaSpawner(
|
||||
name = ctx.name,
|
||||
spawnPool = spawnPool,
|
||||
world = ctx.world,
|
||||
position = ctx.pos,
|
||||
horizontalRadius = ctx.horizontalRadius,
|
||||
verticalRadius = ctx.verticalRadius,
|
||||
maxPokemonPerChunk = ctx.maxPokemonPerChunk
|
||||
).also { spawner ->
|
||||
spawner.influences.addAll(influences)
|
||||
|
||||
// TODO(Andy): PokeSnacks currently bypass pokemon spawn rules, should that be true?
|
||||
// Adding this rule will change that
|
||||
spawner.influences.addAll(
|
||||
CobblemonSpawnRules.rules.values
|
||||
.filter(SpawnRule::enabled)
|
||||
.flatMap(SpawnRule::components)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,13 +29,6 @@ import com.cobblemon.mod.common.util.isBoxLoaded
|
||||
import com.cobblemon.mod.common.util.squeezeWithinBounds
|
||||
import com.cobblemon.mod.common.util.toVec3f
|
||||
import com.cobblemon.mod.common.util.weightedSelection
|
||||
import kotlin.Any
|
||||
import kotlin.Boolean
|
||||
import kotlin.Float
|
||||
import kotlin.Int
|
||||
import kotlin.Pair
|
||||
import kotlin.String
|
||||
import kotlin.collections.plus
|
||||
import kotlin.math.max
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.SectionPos
|
||||
@@ -95,10 +88,9 @@ interface Spawner {
|
||||
): SpawnAction<*>? {
|
||||
influences.removeIf { it.isExpired() }
|
||||
spawnablePosition.influences.addAll(influences)
|
||||
val bucket = chooseBucket(cause, spawnablePosition.influences)
|
||||
return selector.select(
|
||||
spawner = this,
|
||||
bucket = bucket,
|
||||
bucketSelector = { chooseBucket(cause, spawnablePosition.influences) },
|
||||
spawnablePositions = listOf(spawnablePosition),
|
||||
maxSpawns = 1
|
||||
).firstOrNull()
|
||||
@@ -116,9 +108,9 @@ interface Spawner {
|
||||
|
||||
val areaBox = AABB.ofSize(
|
||||
Vec3(constrainedArea.getCenter().toVec3f()),
|
||||
ENTITY_LIMIT_CHUNK_RANGE * 16.0 * 2,
|
||||
ENTITY_LIMIT_CHUNK_RANGE * 16.0,
|
||||
1000.0,
|
||||
ENTITY_LIMIT_CHUNK_RANGE * 16.0 * 2
|
||||
ENTITY_LIMIT_CHUNK_RANGE * 16.0
|
||||
)
|
||||
|
||||
if (!constrainedArea.world.isBoxLoaded(areaBox)) {
|
||||
@@ -140,11 +132,10 @@ interface Spawner {
|
||||
val zone = generator.generate(this, constrainedArea)
|
||||
val spawnablePositions = resolver.resolve(this, prioritizedAreaCalculators, zone)
|
||||
val influences = influences + zone.unconditionalInfluences
|
||||
val bucket = chooseBucket(zoneInput.cause, influences)
|
||||
|
||||
return selector.select(
|
||||
spawner = this,
|
||||
bucket = bucket,
|
||||
bucketSelector = { chooseBucket(zoneInput.cause, influences) },
|
||||
spawnablePositions = spawnablePositions,
|
||||
maxSpawns = maxSpawns
|
||||
)
|
||||
|
||||
@@ -77,6 +77,7 @@ abstract class PokemonStore<T : StorePosition> : Iterable<Pokemon> {
|
||||
remove(pokemon)
|
||||
val position = getFirstAvailablePosition() ?: return false // Couldn't fit, shrug emoji
|
||||
set(position, pokemon)
|
||||
Cobblemon.playerDataManager.getTMData(uuid)?.syncTMsFromPokemon(pokemon)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -170,4 +171,4 @@ abstract class PokemonStore<T : StorePosition> : Iterable<Pokemon> {
|
||||
* each change, accessed by [Pokemon.getChangeObservable].
|
||||
*/
|
||||
abstract fun getAnyChangeObservable(): Observable<Unit>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ open class PCStore(
|
||||
val toast = Toast(
|
||||
title = lang("wallpaper_unlocked"),
|
||||
description = unlockableWallpaper.displayName?.let { "\"".text().add(it.asTranslated()).add("\"") } ?: lang("unknown_wallpaper") ,
|
||||
icon = ItemStack(CobblemonItems.PC)
|
||||
icons = listOf(ItemStack(CobblemonItems.PC))
|
||||
)
|
||||
toast.addListeners(player)
|
||||
toast.expireAfter(5F)
|
||||
|
||||
+3
-1
@@ -28,6 +28,7 @@ data class GeneralPlayerData(
|
||||
var starterUUID: UUID?,
|
||||
var keyItems: MutableSet<ResourceLocation>,
|
||||
var battleTheme: ResourceLocation?,
|
||||
var partySelectTutorialDone: Boolean = false,
|
||||
val extraData: MutableMap<String, PlayerDataExtension>,
|
||||
) : InstancedPlayerData {
|
||||
var advancementData: PlayerAdvancementData = PlayerAdvancementData()
|
||||
@@ -53,7 +54,8 @@ data class GeneralPlayerData(
|
||||
starterSelected,
|
||||
starterUUID,
|
||||
advancementData.totalBattleVictoryCount == 0,
|
||||
battleTheme
|
||||
battleTheme,
|
||||
partySelectTutorialDone
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+29
-7
@@ -8,14 +8,17 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.storage.player
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.Cobblemon.MODID
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexManager
|
||||
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
|
||||
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
|
||||
import com.cobblemon.mod.common.api.storage.player.client.ClientInstancedPlayerData
|
||||
import com.cobblemon.mod.common.api.storage.player.factory.CachedPlayerDataStoreFactory
|
||||
import com.cobblemon.mod.common.api.tms.TMMoveManager
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import net.minecraft.server.MinecraftServer
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.player.Player
|
||||
@@ -30,7 +33,6 @@ import net.minecraft.world.entity.player.Player
|
||||
* @author Apion
|
||||
* @since February 21, 2024
|
||||
*/
|
||||
|
||||
open class PlayerInstancedDataStoreManager {
|
||||
val saveExecutor = Executors.newSingleThreadExecutor(
|
||||
ThreadFactoryBuilder()
|
||||
@@ -53,19 +55,27 @@ open class PlayerInstancedDataStoreManager {
|
||||
//Should put this somewhere else
|
||||
saveTasks[PlayerInstancedDataStoreTypes.GENERAL] = ScheduledTask.Builder()
|
||||
.execute { saveAllOfOneType(PlayerInstancedDataStoreTypes.GENERAL) }
|
||||
.delay(30f)
|
||||
.interval(120f)
|
||||
.delay(30F)
|
||||
.interval(120F)
|
||||
.infiniteIterations()
|
||||
.tracker(ServerTaskTracker)
|
||||
.build()
|
||||
|
||||
saveTasks[PlayerInstancedDataStoreTypes.POKEDEX] = ScheduledTask.Builder()
|
||||
.execute { saveAllOfOneType(PlayerInstancedDataStoreTypes.POKEDEX) }
|
||||
.delay(30f)
|
||||
.interval(120f)
|
||||
.delay(30F)
|
||||
.interval(120F)
|
||||
.infiniteIterations()
|
||||
.tracker(ServerTaskTracker)
|
||||
.build()
|
||||
|
||||
saveTasks[PlayerInstancedDataStoreTypes.TM_MOVES] = ScheduledTask.Builder()
|
||||
.execute { saveAllOfOneType(PlayerInstancedDataStoreTypes.TM_MOVES) }
|
||||
.delay(30F)
|
||||
.interval(120F)
|
||||
.infiniteIterations()
|
||||
.tracker(ServerTaskTracker)
|
||||
.build()
|
||||
}
|
||||
|
||||
open fun get(playerId: UUID, dataType: PlayerInstancedDataStoreType): InstancedPlayerData {
|
||||
@@ -126,4 +136,16 @@ open class PlayerInstancedDataStoreManager {
|
||||
open fun getPokedexData(playerId: UUID): PokedexManager {
|
||||
return get(playerId, PlayerInstancedDataStoreTypes.POKEDEX) as PokedexManager
|
||||
}
|
||||
}
|
||||
|
||||
fun getTMData(player: ServerPlayer): TMMoveManager {
|
||||
return getTMData(player.uuid)
|
||||
}
|
||||
|
||||
fun getTMData(playerId: UUID): TMMoveManager {
|
||||
val data = get(playerId, PlayerInstancedDataStoreTypes.TM_MOVES)
|
||||
if (data !is TMMoveManager) {
|
||||
Cobblemon.LOGGER.warn("getTMData: No TMMoveManager found for $playerId (got ${data?.javaClass?.name})")
|
||||
}
|
||||
return data as TMMoveManager
|
||||
}
|
||||
}
|
||||
|
||||
+16
-3
@@ -8,8 +8,10 @@
|
||||
|
||||
package com.cobblemon.mod.common.api.storage.player
|
||||
|
||||
import com.cobblemon.mod.common.api.storage.player.client.ClientGeneralPlayerData
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.storage.player.client.ClientPokedexManager
|
||||
import com.cobblemon.mod.common.api.storage.player.client.ClientGeneralPlayerData
|
||||
import com.cobblemon.mod.common.api.storage.player.client.ClientTMMoveManager
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
@@ -27,11 +29,22 @@ object PlayerInstancedDataStoreTypes {
|
||||
ClientPokedexManager::runAction,
|
||||
ClientPokedexManager::runIncremental
|
||||
))
|
||||
val TM_MOVES = register(PlayerInstancedDataStoreType(
|
||||
cobblemonResource("tm_moves"),
|
||||
ClientTMMoveManager::decode,
|
||||
ClientTMMoveManager::runAction,
|
||||
ClientTMMoveManager::runIncremental
|
||||
))
|
||||
|
||||
fun register(type: PlayerInstancedDataStoreType): PlayerInstancedDataStoreType {
|
||||
Cobblemon.LOGGER.debug("Registering PlayerInstancedDataStoreType: {}", type.id)
|
||||
types[type.id] = type
|
||||
return type
|
||||
}
|
||||
|
||||
fun getTypeById(id: ResourceLocation) = types[id]
|
||||
}
|
||||
fun getTypeById(id: ResourceLocation): PlayerInstancedDataStoreType? {
|
||||
val type = types[id]
|
||||
if (type == null) Cobblemon.LOGGER.warn("Unknown PlayerInstancedDataStoreType ID: {}", id)
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -36,7 +36,8 @@ class PlayerDataJsonBackend: JsonBackedPlayerDataStoreBackend<GeneralPlayerData>
|
||||
starterUUID = null,
|
||||
keyItems = Cobblemon.config.defaultKeyItems,
|
||||
extraData = mutableMapOf(),
|
||||
battleTheme = CobblemonSounds.PVP_BATTLE.location
|
||||
battleTheme = CobblemonSounds.PVP_BATTLE.location,
|
||||
partySelectTutorialDone = false
|
||||
)}
|
||||
|
||||
override val gson = GsonBuilder()
|
||||
|
||||
+2
-1
@@ -37,7 +37,8 @@ class PlayerDataMongoBackend(mongoClient: MongoClient, databaseName: String, col
|
||||
starterUUID = null,
|
||||
keyItems = Cobblemon.config.defaultKeyItems,
|
||||
extraData = mutableMapOf(),
|
||||
battleTheme = CobblemonSounds.PVP_BATTLE.location
|
||||
battleTheme = CobblemonSounds.PVP_BATTLE.location,
|
||||
partySelectTutorialDone = false
|
||||
)}
|
||||
|
||||
override val gson = GsonBuilder()
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.api.storage.player.adapter
|
||||
|
||||
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
|
||||
import com.cobblemon.mod.common.api.tms.TMMoveManager
|
||||
import com.cobblemon.mod.common.util.adapters.CodecBackedAdapter
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import java.util.UUID
|
||||
|
||||
class TMMoveJsonBackend : JsonBackedPlayerDataStoreBackend<TMMoveManager>("tm_moves", PlayerInstancedDataStoreTypes.TM_MOVES) {
|
||||
override val gson = GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.disableHtmlEscaping()
|
||||
.registerTypeAdapter(TMMoveManager::class.java, CodecBackedAdapter(TMMoveManager.CODEC))
|
||||
.create()
|
||||
|
||||
override val classToken = TypeToken.get(TMMoveManager::class.java)
|
||||
override val defaultData = defaultDataFunc
|
||||
|
||||
override fun initialize(store: TMMoveManager) {
|
||||
store.initialize()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val defaultDataFunc = { uuid: UUID -> TMMoveManager(uuid) }
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.api.storage.player.adapter
|
||||
|
||||
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
|
||||
import com.cobblemon.mod.common.api.tms.TMMoveManager
|
||||
import com.cobblemon.mod.common.util.adapters.CodecBackedAdapter
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.mongodb.client.MongoClient
|
||||
import java.util.UUID
|
||||
|
||||
class TMMoveMongoBackend(
|
||||
mongoClient: MongoClient,
|
||||
databaseName: String,
|
||||
collectionName: String
|
||||
) : MongoBackedPlayerDataStoreBackend<TMMoveManager>(
|
||||
mongoClient,
|
||||
databaseName,
|
||||
collectionName,
|
||||
PlayerInstancedDataStoreTypes.TM_MOVES
|
||||
) {
|
||||
override val gson = GsonBuilder()
|
||||
.registerTypeAdapter(TMMoveManager::class.java, CodecBackedAdapter(TMMoveManager.CODEC))
|
||||
.create()
|
||||
override val classToken = TypeToken.get(TMMoveManager::class.java)
|
||||
override val defaultData = defaultDataFunc
|
||||
|
||||
override fun initialize(store: TMMoveManager) {
|
||||
store.initialize()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val defaultDataFunc = { uuid: UUID -> TMMoveManager(uuid) }
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.api.storage.player.adapter
|
||||
|
||||
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
|
||||
import com.cobblemon.mod.common.api.tms.TMMoveManager
|
||||
import java.util.UUID
|
||||
|
||||
class TMMoveNbtBackend : NbtBackedPlayerData<TMMoveManager>("tm_moves", PlayerInstancedDataStoreTypes.TM_MOVES) {
|
||||
override val codec = TMMoveManager.CODEC
|
||||
override val defaultData = TMMoveJsonBackend.defaultDataFunc
|
||||
}
|
||||
+6
-2
@@ -34,7 +34,8 @@ data class ClientGeneralPlayerData(
|
||||
var starterSelected: Boolean = false,
|
||||
var starterUUID: UUID? = null,
|
||||
var showChallengeLabel: Boolean = true,
|
||||
val battleTheme: ResourceLocation? = null
|
||||
val battleTheme: ResourceLocation? = null,
|
||||
var partySelectTutorialDone: Boolean = false
|
||||
) : ClientInstancedPlayerData {
|
||||
|
||||
override fun encode(buf: RegistryFriendlyByteBuf) {
|
||||
@@ -46,6 +47,7 @@ data class ClientGeneralPlayerData(
|
||||
buf.writeNullable(starterUUID) { pb, value -> pb.writeString(value.toString()) }
|
||||
buf.writeNullable(resetStarters) { pb, value -> pb.writeBoolean(value) }
|
||||
buf.writeNullable(battleTheme) {pb, value -> pb.writeIdentifier(value)}
|
||||
buf.writeBoolean(partySelectTutorialDone)
|
||||
}
|
||||
companion object {
|
||||
fun decode(buffer: RegistryFriendlyByteBuf): SetClientPlayerDataPacket {
|
||||
@@ -57,6 +59,7 @@ data class ClientGeneralPlayerData(
|
||||
val starterUUID = buffer.readNullable { it.readString() }?.let { UUID.fromString(it) }
|
||||
val resetStarterPrompt = buffer.readNullable { it.readBoolean() }
|
||||
val battleTheme = buffer.readNullable { it.readIdentifier() }
|
||||
val partySelectTutorialDone = buffer.readBoolean()
|
||||
val data = ClientGeneralPlayerData(
|
||||
resetStarterPrompt,
|
||||
promptStarter,
|
||||
@@ -64,7 +67,8 @@ data class ClientGeneralPlayerData(
|
||||
starterSelected,
|
||||
starterUUID,
|
||||
showChallengeLabel,
|
||||
battleTheme
|
||||
battleTheme,
|
||||
partySelectTutorialDone
|
||||
)
|
||||
//Weird to do this, but since the flag doesn't get passed to the decoded obj, do it here
|
||||
//Should be fine, as long as decode doesn't get run on the server for some reason
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.api.storage.player.client
|
||||
|
||||
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
|
||||
import com.cobblemon.mod.common.util.readString
|
||||
import com.cobblemon.mod.common.util.writeString
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
class ClientTMMoveManager(
|
||||
val learnedTMs: MutableSet<ResourceLocation>
|
||||
) : ClientInstancedPlayerData {
|
||||
|
||||
override fun encode(buf: RegistryFriendlyByteBuf) {
|
||||
buf.writeCollection(learnedTMs) { _, id -> buf.writeString(id.toString()) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun decode(buf: RegistryFriendlyByteBuf): SetClientPlayerDataPacket {
|
||||
val tms = buf.readCollection(
|
||||
{ mutableSetOf<ResourceLocation>() },
|
||||
{ buf.readString().let(ResourceLocation::tryParse) ?: ResourceLocation("minecraft", "empty") }
|
||||
)
|
||||
return SetClientPlayerDataPacket(
|
||||
PlayerInstancedDataStoreTypes.TM_MOVES,
|
||||
ClientTMMoveManager(tms)
|
||||
)
|
||||
}
|
||||
|
||||
fun runAction(data: ClientInstancedPlayerData) {
|
||||
if (data !is ClientTMMoveManager) return
|
||||
CobblemonClient.clientTMMoveData = data
|
||||
}
|
||||
|
||||
fun runIncremental(data: ClientInstancedPlayerData) {
|
||||
if (data !is ClientTMMoveManager) return
|
||||
CobblemonClient.clientTMMoveData.learnedTMs.addAll(data.learnedTMs)
|
||||
}
|
||||
}
|
||||
}
|
||||
-2
@@ -40,8 +40,6 @@ class CachedPlayerDataStoreFactory<T : InstancedPlayerData>(val backend: PlayerD
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun saveAll() {
|
||||
cache.forEach { (_, pd) -> backend.save(pd) }
|
||||
cache.removeIf { (uuid, _) -> uuid.getPlayer() == null }
|
||||
|
||||
@@ -91,6 +91,7 @@ object CobblemonBlockTags {
|
||||
@JvmField val TUMBLESTONE_BRICKS = createTag("tumblestone_bricks")
|
||||
@JvmField val TUMBLESTONES = createTag("tumblestones")
|
||||
@JvmField val TUMBLESTONE_HEAT_SOURCE = createTag("tumblestone_heat_source")
|
||||
@JvmField val TYPE_GEM_BLOCKS = createTag("type_gem_blocks")
|
||||
@JvmField val WALL_HANGING_SIGNS = createTag("wall_hanging_signs")
|
||||
@JvmField val WALL_SIGNS = createTag("wall_signs")
|
||||
@JvmField val WATER_STONE_ORES = createTag("water_stone_ores")
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.api.tms
|
||||
|
||||
import com.cobblemon.mod.common.tms.obtain.DefaultObtainMethod
|
||||
import com.cobblemon.mod.common.tms.obtain.PlayerHasAdvancementObtainMethod
|
||||
import com.cobblemon.mod.common.tms.obtain.PlayerYObtainMethod
|
||||
import com.cobblemon.mod.common.tms.obtain.UnlockableObtainMethod
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
|
||||
interface ObtainMethod {
|
||||
val passive: Boolean
|
||||
|
||||
/**
|
||||
* Checks if this [ObtainMethod] matches the given player.
|
||||
*/
|
||||
fun matches(player: ServerPlayer): Boolean
|
||||
|
||||
/**
|
||||
* Serializes this [ObtainMethod] to the buffer.
|
||||
*/
|
||||
fun writeToBuffer(buffer: RegistryFriendlyByteBuf)
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Deserializes an [ObtainMethod] from the buffer.
|
||||
*/
|
||||
fun readFromBuffer(buffer: RegistryFriendlyByteBuf): ObtainMethod {
|
||||
val variant = buffer.readUtf()
|
||||
return when (variant) {
|
||||
"cobblemon:unlockable" -> UnlockableObtainMethod()
|
||||
"cobblemon:default" -> DefaultObtainMethod()
|
||||
"cobblemon:advancement" -> {
|
||||
val advancement = buffer.readResourceLocation()
|
||||
PlayerHasAdvancementObtainMethod(advancement)
|
||||
}
|
||||
"cobblemon:y_level" -> {
|
||||
val yLevel = buffer.readVarInt()
|
||||
val operator = buffer.readUtf()
|
||||
PlayerYObtainMethod(yLevel, operator)
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unknown ObtainMethod variant: $variant")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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.api.tms
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
|
||||
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
|
||||
import com.cobblemon.mod.common.api.storage.player.InstancedPlayerData
|
||||
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
|
||||
import com.cobblemon.mod.common.api.storage.player.client.ClientTMMoveManager
|
||||
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
|
||||
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import java.util.UUID
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
class TMMoveManager(
|
||||
override val uuid: UUID,
|
||||
val learnedTMs: MutableSet<ResourceLocation> = mutableSetOf()
|
||||
) : InstancedPlayerData {
|
||||
|
||||
val storeType = PlayerInstancedDataStoreTypes.TM_MOVES
|
||||
|
||||
override fun toClientData() = ClientTMMoveManager(learnedTMs.toMutableSet())
|
||||
fun toClientDataFrom(set: Set<ResourceLocation>) = ClientTMMoveManager(set.toMutableSet())
|
||||
|
||||
fun syncTMsFromPokemon(pokemon: Pokemon) {
|
||||
learn(getLearnableTMsFromPokemon(pokemon))
|
||||
}
|
||||
|
||||
fun syncTMFromMove(moveTemplate: MoveTemplate) {
|
||||
val tmId = TechnicalMachines.moveToTM[moveTemplate]?.id ?: return
|
||||
learn(listOf(tmId))
|
||||
}
|
||||
|
||||
fun getLearnableTMsFromPokemon(pokemon: Pokemon): Collection<ResourceLocation> {
|
||||
val learnableMoves = pokemon.allAccessibleMoves.toMutableSet()
|
||||
learnableMoves.addAll(pokemon.moveSet.map { it.template })
|
||||
|
||||
var preEvolution = pokemon.preEvolution
|
||||
while (preEvolution != null) {
|
||||
learnableMoves.addAll(preEvolution.form.moves.getLevelUpMovesUpTo(pokemon.level))
|
||||
learnableMoves.addAll(preEvolution.form.moves.evolutionMoves)
|
||||
preEvolution = preEvolution.form.preEvolution
|
||||
}
|
||||
|
||||
return TechnicalMachines.tmMap.values
|
||||
.filter { tm -> tm.moveName in learnableMoves }
|
||||
.map(TechnicalMachine::id)
|
||||
}
|
||||
|
||||
fun scheduleFullSyncFromStores(
|
||||
party: Iterable<Pokemon?>,
|
||||
pc: Iterable<Pokemon?>,
|
||||
batchSize: Int = 25
|
||||
) {
|
||||
val iterator = sequence {
|
||||
for (pokemon in party) {
|
||||
if (pokemon != null) yield(pokemon)
|
||||
}
|
||||
for (pokemon in pc) {
|
||||
if (pokemon != null) yield(pokemon)
|
||||
}
|
||||
}.iterator()
|
||||
|
||||
if (!iterator.hasNext()) return
|
||||
|
||||
val tmIds = mutableSetOf<ResourceLocation>()
|
||||
|
||||
ScheduledTask.Builder()
|
||||
.tracker(ServerTaskTracker)
|
||||
.interval(0F)
|
||||
.infiniteIterations()
|
||||
.execute { task ->
|
||||
var processed = 0
|
||||
while (processed < batchSize && iterator.hasNext()) {
|
||||
val pokemon = iterator.next()
|
||||
tmIds.addAll(getLearnableTMsFromPokemon(pokemon))
|
||||
processed++
|
||||
}
|
||||
|
||||
if (!iterator.hasNext()) {
|
||||
learn(tmIds)
|
||||
task.expire()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
||||
fun learn(tmIds: Collection<ResourceLocation>): Boolean {
|
||||
val newLearnedTms = mutableListOf<ResourceLocation>()
|
||||
for (tmId in tmIds) {
|
||||
if (learnedTMs.add(tmId)) {
|
||||
newLearnedTms.add(tmId)
|
||||
}
|
||||
}
|
||||
|
||||
if (newLearnedTms.isEmpty()) return false
|
||||
|
||||
syncClient(newLearnedTms.toSet())
|
||||
sendTMToast(newLearnedTms)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun unlearn(tmIds: Collection<ResourceLocation>): Boolean {
|
||||
val removedTms = mutableListOf<ResourceLocation>()
|
||||
|
||||
for (tmId in tmIds) {
|
||||
if (learnedTMs.remove(tmId)) {
|
||||
removedTms.add(tmId)
|
||||
}
|
||||
}
|
||||
|
||||
if (removedTms.isEmpty()) return false
|
||||
|
||||
syncClient(learnedTMs, isIncremental = false)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun sendTMToast(tmIds: List<ResourceLocation>) {
|
||||
val player = uuid.getPlayer() ?: return
|
||||
var moveName: Component = Component.empty()
|
||||
val icons = tmIds.mapNotNull {
|
||||
val tm = TechnicalMachines.tmMap[it] ?: return@mapNotNull null
|
||||
moveName = tm.translatedMoveName()
|
||||
|
||||
return@mapNotNull tm.createItemStack()
|
||||
}
|
||||
|
||||
val description = if (icons.size == 1) moveName else lang("tms.check_tmm")
|
||||
|
||||
val packet = ToastPacket(
|
||||
title = lang("tms.new_tms_learned"),
|
||||
description = description,
|
||||
icons = icons,
|
||||
frameTexture = ResourceLocation.parse("minecraft:toast/advancement"),
|
||||
progress = -1F,
|
||||
progressColor = 0x00FF00,
|
||||
// Keeping it a fixed UUID makes it so the ToastTracker merges different toasts on the client
|
||||
uuid = UUID.nameUUIDFromBytes("tm_toast".toByteArray()),
|
||||
behaviour = ToastPacket.Behaviour.SHOW_OR_UPDATE,
|
||||
durationMs = 4000L
|
||||
)
|
||||
|
||||
player.sendPacket(packet)
|
||||
}
|
||||
|
||||
fun syncClient(updateSet: Set<ResourceLocation> = learnedTMs, isIncremental: Boolean = true) {
|
||||
uuid.getPlayer()?.sendPacket(
|
||||
SetClientPlayerDataPacket(
|
||||
type = storeType,
|
||||
playerData = toClientDataFrom(updateSet),
|
||||
isIncremental = isIncremental
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CODEC: Codec<TMMoveManager> = RecordCodecBuilder.create { instance ->
|
||||
instance.group(
|
||||
Codec.STRING.xmap(UUID::fromString, UUID::toString)
|
||||
.fieldOf("uuid")
|
||||
.forGetter { it.uuid },
|
||||
Codec.list(ResourceLocation.CODEC)
|
||||
.xmap({ it.toMutableSet() }, { it.toList() })
|
||||
.fieldOf("learnedTMs")
|
||||
.forGetter { it.learnedTMs }
|
||||
).apply(instance, ::TMMoveManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.api.tms
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.types.ElementalType
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.client.CobblemonClient
|
||||
import com.cobblemon.mod.common.item.components.TMMoveComponent
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.tms.obtain.DefaultObtainMethod
|
||||
import com.cobblemon.mod.common.util.lang
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
|
||||
class TechnicalMachine(
|
||||
val moveName: MoveTemplate,
|
||||
val recipe: List<TechnicalMachineRecipe>?,
|
||||
val obtainMethods: List<ObtainMethod> = emptyList(),
|
||||
val type: String
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
* Filters all available [TechnicalMachines] based on three optional arguments
|
||||
*
|
||||
* @param search A [String] to check against move names
|
||||
* @param type An [ElementalType] to check for
|
||||
* @param pokemon A [Pokemon] to check the [Learnset] of
|
||||
* @return A [MutableSet] of [TechnicalMachine] that passed the filter
|
||||
*/
|
||||
fun filterTms(
|
||||
search: String?,
|
||||
type: ElementalType?,
|
||||
pokemon: Pokemon?,
|
||||
player: Player? = null,
|
||||
includeUnlearned: Boolean = false
|
||||
): MutableSet<TechnicalMachine> {
|
||||
val tms = TechnicalMachines.tmMap.values.toMutableSet()
|
||||
|
||||
type?.let {
|
||||
val iterator = tms.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val tm = iterator.next()
|
||||
if (ElementalTypes.get(tm.type) != type) {
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pokemon?.let {
|
||||
val iterator = tms.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val tm = iterator.next()
|
||||
if (tm.moveName !in pokemon.form.moves.tmLearnableMoves()) {
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
search?.let {
|
||||
val iterator = tms.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val tm = iterator.next()
|
||||
if (!tm.translatedMoveName().string.contains(search, ignoreCase = true)) {
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (player != null && !includeUnlearned) {
|
||||
val learnedTMs = CobblemonClient.clientTMMoveData.learnedTMs
|
||||
tms.retainAll { tm -> tm.isPassivelyObtained() || tm.id in learnedTMs }
|
||||
}
|
||||
|
||||
return tms
|
||||
}
|
||||
}
|
||||
|
||||
lateinit var id: ResourceLocation
|
||||
|
||||
/**
|
||||
* Gets the recipe list limited to the provided amount to match the slot amount of the TM Machine.
|
||||
*
|
||||
* @param limit The amount of recipe item slots to take.
|
||||
* @return The recipe list limited to 3 ingredients.
|
||||
*/
|
||||
fun getClampedRecipe(limit: Int = 3): List<TechnicalMachineRecipe>? = recipe?.take(limit)
|
||||
|
||||
/**
|
||||
* Unlocks this [TechnicalMachine] for the player.
|
||||
*
|
||||
* @param player The [ServerPlayer] to give this [TechnicalMachine] to.
|
||||
* @return Whether the player was successfully granted the [TechnicalMachine]
|
||||
*/
|
||||
fun unlock(player: ServerPlayer): Boolean {
|
||||
if (!obtainMethods.any { it is DefaultObtainMethod }) {
|
||||
player.sendSystemMessage(lang("tms.unlock_tm", moveName.displayName))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun isPassivelyObtained() = obtainMethods.any { it is DefaultObtainMethod }
|
||||
|
||||
/**
|
||||
* Returns a [MutableComponent] of the translated move name of this [TechnicalMachine]
|
||||
*
|
||||
* @return This [TechnicalMachine]'s move name, translated
|
||||
*/
|
||||
fun translatedMoveName(): MutableComponent = moveName.displayName
|
||||
|
||||
fun createItemStack(): ItemStack {
|
||||
val stack = ItemStack(CobblemonItems.TECHNICAL_MACHINE)
|
||||
TMMoveComponent.setTMMove(stack, moveName)
|
||||
return stack
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.api.tms
|
||||
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
|
||||
/**
|
||||
* DTO used for [TechnicalMachine] JSON loading because Gson cannot deserialize Minecraft Ingredients.
|
||||
*/
|
||||
data class TechnicalMachineDTO(
|
||||
val moveName: MoveTemplate,
|
||||
val recipe: List<TechnicalMachineRecipeDTO>?,
|
||||
val obtainMethods: List<ObtainMethod> = emptyList(),
|
||||
val type: String
|
||||
) {
|
||||
fun toTechnicalMachine(): TechnicalMachine {
|
||||
return TechnicalMachine(
|
||||
moveName = moveName,
|
||||
recipe = recipe?.map { it.toTechnicalMachineRecipe() },
|
||||
obtainMethods = obtainMethods,
|
||||
type = type
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.api.tms
|
||||
|
||||
import net.minecraft.world.item.crafting.Ingredient
|
||||
|
||||
/**
|
||||
* Represents the ingredients necessary to craft a [TechnicalMachine] in the [TMBlock]
|
||||
*/
|
||||
data class TechnicalMachineRecipe(
|
||||
val ingredient: Ingredient,
|
||||
val count: Int
|
||||
)
|
||||
@@ -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.api.tms
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.core.registries.Registries
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.tags.TagKey
|
||||
import net.minecraft.world.item.crafting.Ingredient
|
||||
|
||||
/**
|
||||
* DTO used for [TechnicalMachineRecipe] JSON loading because Gson cannot deserialize Minecraft Ingredients.
|
||||
*/
|
||||
data class TechnicalMachineRecipeDTO(
|
||||
val item: ResourceLocation?,
|
||||
val tag: ResourceLocation?,
|
||||
@SerializedName("count") private val _count: Int?,
|
||||
) {
|
||||
private val count: Int
|
||||
get() = _count ?: 1
|
||||
|
||||
fun toTechnicalMachineRecipe(): TechnicalMachineRecipe {
|
||||
val ingredient = when {
|
||||
item != null ->
|
||||
Ingredient.of(
|
||||
BuiltInRegistries.ITEM.get(item)
|
||||
)
|
||||
|
||||
tag != null ->
|
||||
Ingredient.of(
|
||||
TagKey.create(Registries.ITEM, tag)
|
||||
)
|
||||
|
||||
else ->
|
||||
error("Recipe entry must define either 'item' or 'tag'")
|
||||
}
|
||||
|
||||
return TechnicalMachineRecipe(
|
||||
ingredient = ingredient,
|
||||
count = count
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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.api.tms
|
||||
|
||||
import com.cobblemon.mod.common.api.data.JsonDataRegistry
|
||||
import com.cobblemon.mod.common.api.moves.MoveTemplate
|
||||
import com.cobblemon.mod.common.api.moves.adapters.MoveTemplateAdapter
|
||||
import com.cobblemon.mod.common.api.reactive.SimpleObservable
|
||||
import com.cobblemon.mod.common.util.adapters.TMObtainMethodAdapter
|
||||
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import java.io.BufferedReader
|
||||
import java.util.concurrent.ExecutionException
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.server.packs.PackType
|
||||
|
||||
object TechnicalMachines : JsonDataRegistry<TechnicalMachine> {
|
||||
override val gson = GsonBuilder()
|
||||
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
|
||||
.registerTypeAdapter(ObtainMethod::class.java, TMObtainMethodAdapter)
|
||||
.registerTypeAdapter(MoveTemplate::class.java, MoveTemplateAdapter)
|
||||
.create()
|
||||
|
||||
override val typeToken = TypeToken.get(TechnicalMachine::class.java)
|
||||
override val resourcePath = "tms"
|
||||
override val id = cobblemonResource("technical_machines")
|
||||
override val type = PackType.SERVER_DATA
|
||||
override val observable = SimpleObservable<TechnicalMachines>()
|
||||
|
||||
val tmMap = mutableMapOf<ResourceLocation, TechnicalMachine>()
|
||||
val moveToTM = mutableMapOf<MoveTemplate, TechnicalMachine>()
|
||||
|
||||
/**
|
||||
* Overridden to deserialize a DTO first and convert it to a runtime
|
||||
* [TechnicalMachine], since Gson cannot deserialize Minecraft Ingredients
|
||||
* directly.
|
||||
*/
|
||||
override fun parse(
|
||||
reader: BufferedReader,
|
||||
identifier: ResourceLocation
|
||||
): TechnicalMachine {
|
||||
try {
|
||||
val technicalMachineDto = gson.fromJson(reader, TechnicalMachineDTO::class.java)
|
||||
val technicalMachine = technicalMachineDto.toTechnicalMachine()
|
||||
return technicalMachine
|
||||
} catch (exception: Exception) {
|
||||
throw ExecutionException(
|
||||
"Error loading Technical Machine JSON: $identifier",
|
||||
exception
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun reload(data: Map<ResourceLocation, TechnicalMachine>) {
|
||||
data.forEach { (id, tm) ->
|
||||
tmMap[id] = tm
|
||||
tm.id = id
|
||||
moveToTM[tm.moveName] = tm
|
||||
}
|
||||
}
|
||||
|
||||
fun getByResourceLocation(resourceLocation: ResourceLocation) = tmMap[resourceLocation]
|
||||
fun getAllResourceLocations() = tmMap.keys.toSet()
|
||||
override fun sync(player: ServerPlayer) {}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import net.minecraft.world.item.ItemStack
|
||||
*
|
||||
* @param title The [Component] representing the toast title, this can be a [Component.empty].
|
||||
* @param description The [Component] representing the toast description, this can be a [Component.empty].
|
||||
* @param icon The [ItemStack] that is rendered as the toast icon, this can be a [ItemStack.EMPTY].
|
||||
* @param icons The [ItemStack] that is rendered as the toast icon, this can be a [ItemStack.EMPTY].
|
||||
* @param frameTexture The texture of the frame, default is the [VANILLA_BACKGROUND_SPRITE].
|
||||
* @param progress The value of the progress bar, this accepts a value between 0.0F and 1.0F, any other value will not render a progress bar.
|
||||
* @param progressColor The progress bar color in RGB, default is the [VANILLA_PROGRESS_COLOR].
|
||||
@@ -38,7 +38,7 @@ import net.minecraft.world.item.ItemStack
|
||||
class Toast(
|
||||
title: Component,
|
||||
description: Component,
|
||||
icon: ItemStack,
|
||||
icons: List<ItemStack>,
|
||||
frameTexture: ResourceLocation = VANILLA_BACKGROUND_SPRITE,
|
||||
progress: Float = -1F,
|
||||
progressColor: Int = VANILLA_PROGRESS_COLOR
|
||||
@@ -57,7 +57,7 @@ class Toast(
|
||||
/**
|
||||
* The [ItemStack] that is rendered as the toast icon, this can be a [ItemStack.EMPTY].
|
||||
*/
|
||||
var icon: ItemStack by Delegates.observable(icon) { _, old, new -> if (old != new) this.launchUpdate() }
|
||||
var icons: List<ItemStack> by Delegates.observable(icons) { _, old, new -> if (old != new) this.launchUpdate() }
|
||||
|
||||
/**
|
||||
* The texture of the frame.
|
||||
@@ -185,7 +185,7 @@ class Toast(
|
||||
private fun toPacket(behaviour: ToastPacket.Behaviour): ToastPacket = ToastPacket(
|
||||
this.title,
|
||||
this.description,
|
||||
this.icon,
|
||||
this.icons,
|
||||
this.frameTexture,
|
||||
this.progress,
|
||||
this.progressColor,
|
||||
|
||||
@@ -29,9 +29,12 @@ class ElementalType(
|
||||
val displayName: MutableComponent,
|
||||
val hue: Int,
|
||||
val textureXMultiplier: Int,
|
||||
val primaryColor: Int,
|
||||
val secondaryColor: Int,
|
||||
val resourceLocation: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, "ui/types.png"),
|
||||
val showdownId: String = ShowdownIdentifiable.REGEX.replace(name.lowercase(), "")
|
||||
) : ShowdownIdentifiable {
|
||||
val showdownId: String = ShowdownIdentifiable.REGEX.replace(name.lowercase(), ""),
|
||||
val typeGem: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, showdownId + "_gem"),
|
||||
) : ShowdownIdentifiable {
|
||||
|
||||
override fun showdownId(): String {
|
||||
return showdownId
|
||||
|
||||
@@ -23,7 +23,9 @@ object ElementalTypes {
|
||||
name = "Normal",
|
||||
displayName = Component.translatable("cobblemon.type.normal"),
|
||||
hue = 0xE8E8DA,
|
||||
textureXMultiplier = 0
|
||||
textureXMultiplier = 0,
|
||||
primaryColor = 0x8FA689,
|
||||
secondaryColor = 0xFFFBE2
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -31,7 +33,9 @@ object ElementalTypes {
|
||||
name = "Fire",
|
||||
displayName = Component.translatable("cobblemon.type.fire"),
|
||||
hue = 0xFF6E21,
|
||||
textureXMultiplier = 1
|
||||
textureXMultiplier = 1,
|
||||
primaryColor = 0xF92D2D,
|
||||
secondaryColor = 0xF99B1D
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -39,7 +43,9 @@ object ElementalTypes {
|
||||
name = "Water",
|
||||
displayName = Component.translatable("cobblemon.type.water"),
|
||||
hue = 0x3FA5FF,
|
||||
textureXMultiplier = 2
|
||||
textureXMultiplier = 2,
|
||||
primaryColor = 0x377AF6,
|
||||
secondaryColor = 0x50DDF9
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -47,7 +53,9 @@ object ElementalTypes {
|
||||
name = "Grass",
|
||||
displayName = Component.translatable("cobblemon.type.grass"),
|
||||
hue = 0x62D14F,
|
||||
textureXMultiplier = 3
|
||||
textureXMultiplier = 3,
|
||||
primaryColor = 0x0EAE95,
|
||||
secondaryColor = 0xC6F554
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -55,7 +63,9 @@ object ElementalTypes {
|
||||
name = "Electric",
|
||||
displayName = Component.translatable("cobblemon.type.electric"),
|
||||
hue = 0xFFD314,
|
||||
textureXMultiplier = 4
|
||||
textureXMultiplier = 4,
|
||||
primaryColor = 0xF7AB19,
|
||||
secondaryColor = 0xFFF257
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -63,7 +73,9 @@ object ElementalTypes {
|
||||
name = "Ice",
|
||||
displayName = Component.translatable("cobblemon.type.ice"),
|
||||
hue = 0x54F2F2,
|
||||
textureXMultiplier = 5
|
||||
textureXMultiplier = 5,
|
||||
primaryColor = 0x41BAED,
|
||||
secondaryColor = 0x91F4F9
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -71,7 +83,9 @@ object ElementalTypes {
|
||||
name = "Fighting",
|
||||
displayName = Component.translatable("cobblemon.type.fighting"),
|
||||
hue = 0xEF565D,
|
||||
textureXMultiplier = 6
|
||||
textureXMultiplier = 6,
|
||||
primaryColor = 0xC8356B,
|
||||
secondaryColor = 0xF36A3F
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -79,7 +93,9 @@ object ElementalTypes {
|
||||
name = "Poison",
|
||||
displayName = Component.translatable("cobblemon.type.poison"),
|
||||
hue = 0xD651FF,
|
||||
textureXMultiplier = 7
|
||||
textureXMultiplier = 7,
|
||||
primaryColor = 0x7B41DD,
|
||||
secondaryColor = 0xE568F4
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -87,7 +103,9 @@ object ElementalTypes {
|
||||
name = "Ground",
|
||||
displayName = Component.translatable("cobblemon.type.ground"),
|
||||
hue = 0xF4A453,
|
||||
textureXMultiplier = 8
|
||||
textureXMultiplier = 8,
|
||||
primaryColor = 0xE08835,
|
||||
secondaryColor = 0xFFD057
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -95,7 +113,9 @@ object ElementalTypes {
|
||||
name = "Flying",
|
||||
displayName = Component.translatable("cobblemon.type.flying"),
|
||||
hue = 0xB8B2FF,
|
||||
textureXMultiplier = 9
|
||||
textureXMultiplier = 9,
|
||||
primaryColor = 0x6083F6,
|
||||
secondaryColor = 0xD5CCFA
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -103,7 +123,9 @@ object ElementalTypes {
|
||||
name = "Psychic",
|
||||
displayName = Component.translatable("cobblemon.type.psychic"),
|
||||
hue = 0xFF5E9E,
|
||||
textureXMultiplier = 10
|
||||
textureXMultiplier = 10,
|
||||
primaryColor = 0xE553CE,
|
||||
secondaryColor = 0xF69DD5
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -111,7 +133,9 @@ object ElementalTypes {
|
||||
name = "Bug",
|
||||
displayName = Component.translatable("cobblemon.type.bug"),
|
||||
hue = 0xD3D319,
|
||||
textureXMultiplier = 11
|
||||
textureXMultiplier = 11,
|
||||
primaryColor = 0x9FBE1B,
|
||||
secondaryColor = 0xE9FB5B
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -119,7 +143,9 @@ object ElementalTypes {
|
||||
name = "Rock",
|
||||
displayName = Component.translatable("cobblemon.type.rock"),
|
||||
hue = 0xB7A16E,
|
||||
textureXMultiplier = 12
|
||||
textureXMultiplier = 12,
|
||||
primaryColor = 0x9F814A,
|
||||
secondaryColor = 0xEECB7A
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -127,7 +153,9 @@ object ElementalTypes {
|
||||
name = "Ghost",
|
||||
displayName = Component.translatable("cobblemon.type.ghost"),
|
||||
hue = 0x9C80F7,
|
||||
textureXMultiplier = 13
|
||||
textureXMultiplier = 13,
|
||||
primaryColor = 0x4F57DD,
|
||||
secondaryColor = 0xC182F1
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -135,7 +163,9 @@ object ElementalTypes {
|
||||
name = "Dragon",
|
||||
displayName = Component.translatable("cobblemon.type.dragon"),
|
||||
hue = 0x7580FF,
|
||||
textureXMultiplier = 14
|
||||
textureXMultiplier = 14,
|
||||
primaryColor = 0x4E68E2,
|
||||
secondaryColor = 0x47C7F7
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -143,7 +173,9 @@ object ElementalTypes {
|
||||
name = "Dark",
|
||||
displayName = Component.translatable("cobblemon.type.dark"),
|
||||
hue = 0x587DA0,
|
||||
textureXMultiplier = 15
|
||||
textureXMultiplier = 15,
|
||||
primaryColor = 0x465B99,
|
||||
secondaryColor = 0x689EBF
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -151,7 +183,9 @@ object ElementalTypes {
|
||||
name = "Steel",
|
||||
displayName = Component.translatable("cobblemon.type.steel"),
|
||||
hue = 0xABD1F4,
|
||||
textureXMultiplier = 16
|
||||
textureXMultiplier = 16,
|
||||
primaryColor = 0x628CB9,
|
||||
secondaryColor = 0xD6F7F3
|
||||
)
|
||||
|
||||
@JvmField
|
||||
@@ -159,16 +193,20 @@ object ElementalTypes {
|
||||
name = "Fairy",
|
||||
displayName = Component.translatable("cobblemon.type.fairy"),
|
||||
hue = 0xFF7FE5,
|
||||
textureXMultiplier = 17
|
||||
textureXMultiplier = 17,
|
||||
primaryColor = 0xE2678C,
|
||||
secondaryColor = 0xF5A7B7
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int): ElementalType {
|
||||
fun register(name: String, displayName: MutableComponent, hue: Int, textureXMultiplier: Int, primaryColor: Int, secondaryColor: Int): ElementalType {
|
||||
return ElementalType(
|
||||
name = name,
|
||||
displayName = displayName,
|
||||
hue = hue,
|
||||
textureXMultiplier = textureXMultiplier
|
||||
textureXMultiplier = textureXMultiplier,
|
||||
primaryColor = primaryColor,
|
||||
secondaryColor = secondaryColor
|
||||
).also {
|
||||
allTypes.add(it)
|
||||
}
|
||||
@@ -273,4 +311,4 @@ object ElementalTypes {
|
||||
@JvmName("getFAIRY")
|
||||
@Deprecated("Use ElementalTypes.FAIRY, provided for backwards compatibility until Cobblemon 1.8.")
|
||||
fun getFAIRY() = FAIRY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import kotlin.collections.associateWith
|
||||
import kotlin.collections.partition
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
/**
|
||||
* AI that tries to choose the best move for the given situations. Based off of the Pokemon Trainer Tournament Simulator Github
|
||||
* https://github.com/cRz-Shadows/Pokemon_Trainer_Tournament_Simulator/blob/main/pokemon-showdown/sim/examples/Simulation-test-1.ts#L330
|
||||
@@ -49,7 +48,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
private val hpWeightConsideration = 0.25 // how much HP difference is a consideration for switchins
|
||||
private val hpFractionCoefficient = 0.4 // how much HP differences should be taken into account for switch ins
|
||||
private val boostWeightCoefficient = 1 // the amount of boosts considered a baseline to be removed
|
||||
private val switchOutMatchupThreshold = 0 // todo change this to get it feeling just right (-7 never switches)
|
||||
private val switchOutMatchupThreshold = -2 // todo change this to get it feeling just right (-7 never switches)
|
||||
private val selfKoMoveMatchupThreshold = 0.3
|
||||
private val trickRoomThreshold = 85
|
||||
private val recoveryMoveThreshold = 0.50
|
||||
@@ -219,19 +218,14 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
.filter { it.second.canBeSentOut() }
|
||||
|
||||
if (forceSwitch || activeBattlePokemon.isGone()) {
|
||||
if (battle.turn == 1) {
|
||||
val switchTo = activeBattlePokemon.actor.pokemonList.filter { it.canBeSentOut() }.randomOrNull()
|
||||
?: return DefaultActionResponse()
|
||||
switchTo.willBeSwitchedIn = true
|
||||
return SwitchActionResponse(switchTo.uuid)
|
||||
}
|
||||
else {
|
||||
val bestEstimation = availableSwitches.maxByOrNull { estimateMatchup(activeBattlePokemon, aiSide, battle, it.first) }
|
||||
?: return PassActionResponse
|
||||
// When forced to switch, pick the Pokemon with the best matchup estimation
|
||||
val bestSwitchScore = availableSwitches.maxOfOrNull { estimateMatchup(activeBattlePokemon, aiSide, battle, it.first) }
|
||||
?: return PassActionResponse
|
||||
val bestSwitch = availableSwitches.firstOrNull { estimateMatchup(activeBattlePokemon, aiSide, battle, it.first) == bestSwitchScore }
|
||||
?: return PassActionResponse
|
||||
|
||||
bestEstimation.second.willBeSwitchedIn = true
|
||||
return SwitchActionResponse(bestEstimation.second.uuid)
|
||||
}
|
||||
bestSwitch.second.willBeSwitchedIn = true
|
||||
return SwitchActionResponse(bestSwitch.second.uuid)
|
||||
}
|
||||
if (moveset == null) {
|
||||
return PassActionResponse
|
||||
@@ -259,9 +253,9 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
|
||||
if (!checkSkillLevel()){
|
||||
val move = availableMoves
|
||||
.filter { it.first.target.targetList(activeBattlePokemon)?.isEmpty() != true }
|
||||
.randomOrNull()
|
||||
?: return MoveActionResponse("struggle")
|
||||
.filter { it.first.target.targetList(activeBattlePokemon)?.isEmpty() != true }
|
||||
.randomOrNull()
|
||||
?: return MoveActionResponse("struggle")
|
||||
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
@@ -270,7 +264,6 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
if (checkSwitchOutSkill() && shouldSwitchOut(aiSide, battle, activeBattlePokemon, moveset)) {
|
||||
considerSwitching(activeBattlePokemon, activeTrackerPokemon, opponents, availableMoves, availableSwitches, battle, aiSide)
|
||||
}
|
||||
activeTrackerPokemon.firstTurn = false
|
||||
|
||||
// If HP is below 30% and not switching out, always use the most damaging move
|
||||
if (activeTrackerPokemon.currentHpPercent < 0.3 && !shouldSwitchOut(aiSide, battle, activeBattlePokemon, moveset)) {
|
||||
@@ -296,8 +289,6 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
return chooseMove(fakeOut.first, activeBattlePokemon, targets.firstOrNull { validTarget != null && validTarget.id == it.battlePokemon!!.uuid})
|
||||
}
|
||||
|
||||
activeTrackerPokemon.firstTurn = false
|
||||
|
||||
|
||||
// Explosion/Self destruct
|
||||
availableMoves.firstOrNull {
|
||||
@@ -315,7 +306,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
|
||||
// Deal with non-weather related field changing effects
|
||||
for (move in availableMoves) {
|
||||
|
||||
|
||||
// Tailwind
|
||||
if (move.first.id == "tailwind" && move.first.id != activeTracker.alliedSide.tailwindCondition &&
|
||||
availableSwitches.size > 2) {
|
||||
@@ -324,21 +315,21 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
|
||||
// Trick room
|
||||
if (move.first.id == "trickroom" && move.first.id != activeTracker.currentRoom
|
||||
&& availableSwitches.count { statEstimationActive(it.first, Stats.SPEED) <= trickRoomThreshold } >= 2) {
|
||||
&& availableSwitches.count { statEstimationActive(it.first, Stats.SPEED) <= trickRoomThreshold } >= 2) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
|
||||
// todo find a way to get list of active screens
|
||||
// Aurora veil
|
||||
if (move.first.id == "auroraveil" && move.first.id != activeTracker.alliedSide.screenCondition
|
||||
&& activeTracker.currentWeather in listOf("hail", "snow")) {
|
||||
&& activeTracker.currentWeather in listOf("hail", "snow")) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
|
||||
// todo find a way to get list of active screens
|
||||
// Light Screen
|
||||
if (move.first.id == "lightscreen" && move.first.id != activeTracker.alliedSide.screenCondition
|
||||
&& opponents.any { (it.species?.baseStats?.get(Stats.SPECIAL_ATTACK) ?: 0) > (it.species?.baseStats?.get(Stats.ATTACK) ?: 0) }
|
||||
&& opponents.any { (it.species?.baseStats?.get(Stats.SPECIAL_ATTACK) ?: 0) > (it.species?.baseStats?.get(Stats.ATTACK) ?: 0) }
|
||||
&& availableSwitches.size > 1) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
@@ -356,13 +347,13 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
for (move in availableMoves) {
|
||||
// Setup
|
||||
if (nOppRemainingMons >= 3 && move.first.id in AIUtility.entryHazards
|
||||
&& !activeTracker.opponentSide.sideHazards.contains(move.first.id)) {
|
||||
&& !activeTracker.opponentSide.sideHazards.contains(move.first.id)) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
|
||||
// Removal
|
||||
if (nRemainingMons >= 2 && move.first.id in AIUtility.antiHazardsMoves
|
||||
&& activeTracker.alliedSide.sideHazards.isNotEmpty()) {
|
||||
&& activeTracker.alliedSide.sideHazards.isNotEmpty()) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
}
|
||||
@@ -372,10 +363,10 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
// Court Change
|
||||
for (move in availableMoves) {
|
||||
if (move.first.id == "courtchange"
|
||||
&& (!AIUtility.entryHazards.none { it in activeTracker.alliedSide.sideHazards }
|
||||
|| setOf("tailwind", "lightscreen", "reflect").any { it in activeTracker.opponentSide.sideHazards })
|
||||
&& setOf("tailwind", "lightscreen", "reflect").none { it in activeTracker.opponentSide.sideHazards }
|
||||
&& AIUtility.entryHazards.none { it in activeTracker.opponentSide.sideHazards }) {
|
||||
&& (!AIUtility.entryHazards.none { it in activeTracker.alliedSide.sideHazards }
|
||||
|| setOf("tailwind", "lightscreen", "reflect").any { it in activeTracker.opponentSide.sideHazards })
|
||||
&& setOf("tailwind", "lightscreen", "reflect").none { it in activeTracker.opponentSide.sideHazards }
|
||||
&& AIUtility.entryHazards.none { it in activeTracker.opponentSide.sideHazards }) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
}
|
||||
@@ -383,7 +374,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
// Strength Sap
|
||||
for (move in availableMoves) {
|
||||
if (move.first.id == "strengthsap" && activeTrackerPokemon.currentHpPercent < 0.5
|
||||
&& activeTrackerPokemon.species!!.baseStats.getOrDefault(Stats.ATTACK, 0) > 80) {
|
||||
&& activeTrackerPokemon.species!!.baseStats.getOrDefault(Stats.ATTACK, 0) > 80) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
}
|
||||
@@ -391,10 +382,10 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
// Belly Drum
|
||||
for (move in availableMoves) {
|
||||
if (move.first.id == "bellydrum"
|
||||
&& (activeTrackerPokemon.currentHpPercent > 0.6
|
||||
&& activeTrackerPokemon.pokemon!!.heldItem().item == CobblemonItems.SITRUS_BERRY
|
||||
|| activeTrackerPokemon.currentHpPercent > 0.8)
|
||||
&& activeTrackerPokemon.boosts.getOrDefault(Stats.ATTACK, 0) < 1) {
|
||||
&& (activeTrackerPokemon.currentHpPercent > 0.6
|
||||
&& activeTrackerPokemon.pokemon!!.heldItem().item == CobblemonItems.SITRUS_BERRY
|
||||
|| activeTrackerPokemon.currentHpPercent > 0.8)
|
||||
&& activeTrackerPokemon.boosts.getOrDefault(Stats.ATTACK, 0) < 1) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
}
|
||||
@@ -404,8 +395,8 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
for (move in availableMoves) {
|
||||
AIUtility.weatherSetupMoves[move.first.id]?.let { requiredWeather ->
|
||||
if (activeTracker.currentWeather != requiredWeather.lowercase() &&
|
||||
!(activeTracker.currentWeather == "primordialsea" && requiredWeather == "raindance") &&
|
||||
!(activeTracker.currentWeather == "desolateland" && requiredWeather == "sunnyday")) {
|
||||
!(activeTracker.currentWeather == "primordialsea" && requiredWeather == "raindance") &&
|
||||
!(activeTracker.currentWeather == "desolateland" && requiredWeather == "sunnyday")) {
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
}
|
||||
@@ -431,8 +422,8 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
// Stall out side conditions
|
||||
if ((activeTracker.opponentSide.screenCondition != null || activeTracker.opponentSide.tailwindCondition != null) &&
|
||||
(activeTracker.alliedSide.screenCondition == null || activeTracker.alliedSide.tailwindCondition == null) ||
|
||||
opponents.any { it.currentStatus == null } &&
|
||||
activeTrackerPokemon.protectCount == 0 && opponents.none { it.currentAbility?.name == "unseenfist"}) {
|
||||
opponents.any { it.currentStatus == null } &&
|
||||
activeTrackerPokemon.protectCount == 0 && opponents.none { it.currentAbility?.name == "unseenfist"}) {
|
||||
activeTrackerPokemon.protectCount = 3
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
@@ -460,10 +451,10 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
activeTrackerPokemon.firstTurn = false
|
||||
|
||||
val move = availableMoves
|
||||
.filter { it.first.canBeUsed() }
|
||||
.filter { it.first.mustBeUsed() || it.first.target.targetList(activeBattlePokemon)?.isEmpty() != true }
|
||||
.randomOrNull()
|
||||
?: return MoveActionResponse("struggle")
|
||||
.filter { it.first.canBeUsed() }
|
||||
.filter { it.first.mustBeUsed() || it.first.target.targetList(activeBattlePokemon)?.isEmpty() != true }
|
||||
.randomOrNull()
|
||||
?: return MoveActionResponse("struggle")
|
||||
|
||||
return chooseMove(move.first, activeBattlePokemon)
|
||||
}
|
||||
@@ -520,6 +511,10 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
return false
|
||||
|
||||
val activeTrackerPokemon = activeTracker.alliedSide.activePokemon.first { it.id == activeBattlePokemon.battlePokemon!!.uuid }
|
||||
|
||||
// Don't switch out if we just switched in
|
||||
if (activeTrackerPokemon.firstTurn)
|
||||
return false
|
||||
val actorTracker = activeTracker.alliedSide.actors.first { activeTrackerPokemon in it.activePokemon }
|
||||
val availableSwitches = actorTracker.party.filter { it.currentHp!! > 0 }
|
||||
val currentAbility = activeTrackerPokemon.pokemon!!.ability
|
||||
@@ -532,7 +527,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
// Only switch if a switch-in is significantly better than the current Pokémon
|
||||
val currentScore = estimateMatchup(activeBattlePokemon, side, battle)
|
||||
val bestSwitchScore = availableSwitches.maxOfOrNull { estimateMatchup(activeBattlePokemon, side, battle, it) } ?: currentScore
|
||||
val improvementThreshold = currentScore.absoluteValue * 0.25 + 2
|
||||
val improvementThreshold = currentScore.absoluteValue * 0.5 + 3
|
||||
if (bestSwitchScore <= currentScore + improvementThreshold) {
|
||||
return false
|
||||
}
|
||||
@@ -567,7 +562,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
if (availableSwitches.isEmpty() || availableSwitches.none { estimateMatchup(activeBattlePokemon, side, battle, it) > 0 })
|
||||
return false
|
||||
|
||||
|
||||
|
||||
|
||||
// ...and a 'good' reason to switch out
|
||||
if (opponentActiveTracker.any { it.boosts.getOrDefault(Stats.ACCURACY, 0) <= accuracySwitchThreshold } ||
|
||||
@@ -995,6 +990,4 @@ class StrongBattleAI(skill: Int) : BattleAI {
|
||||
return MoveActionResponse(move.id, chosenTarget.getPNX())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+2
@@ -132,6 +132,7 @@ class TrackerActor(val uuid: UUID) {
|
||||
actor.activePokemon.filter { newActive -> activePokemon.none { it.id == newActive.battlePokemon?.uuid } }.forEach {
|
||||
val active = party.firstOrNull { trackMon -> trackMon.id == it.battlePokemon?.uuid } ?: return@forEach
|
||||
party.remove(active)
|
||||
active.firstTurn = true // Reset firstTurn when switching in
|
||||
activePokemon.add(active)
|
||||
}
|
||||
val swappedOut = activePokemon.filter { trackMon -> actor.activePokemon.none { it.battlePokemon?.uuid == trackMon.id } }
|
||||
@@ -145,6 +146,7 @@ class TrackerActor(val uuid: UUID) {
|
||||
actor.activePokemon.filter { newActive -> activePokemon.none { it.id == newActive.battlePokemon?.uuid } }.forEach {
|
||||
val active = party.firstOrNull { trackMon -> trackMon.id == it.battlePokemon?.uuid } ?: return@forEach
|
||||
party.remove(active)
|
||||
active.firstTurn = true // Reset firstTurn when switching in
|
||||
activePokemon.add(active)
|
||||
}
|
||||
val swappedOut = activePokemon.filter { trackMon -> actor.activePokemon.none { it.battlePokemon?.uuid == trackMon.id } }
|
||||
|
||||
@@ -133,7 +133,10 @@ class ApricornBlock(settings: Properties, val apricorn: Apricorn) : HorizontalDi
|
||||
override fun isBonemealSuccess(world: Level, random: RandomSource, pos: BlockPos, state: BlockState) = true
|
||||
|
||||
override fun performBonemeal(world: ServerLevel, random: RandomSource, pos: BlockPos, state: BlockState) {
|
||||
world.setBlock(pos, state.setValue(AGE, state.getValue(AGE) + 1), 2)
|
||||
val curAge = state.getValue(AGE)
|
||||
val newAge = curAge + 1
|
||||
if (newAge > MAX_AGE) return
|
||||
world.setBlock(pos, state.setValue(AGE, newAge), UPDATE_CLIENTS)
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
@@ -186,17 +189,17 @@ class ApricornBlock(settings: Properties, val apricorn: Apricorn) : HorizontalDi
|
||||
* This uses [Block.dropResources] to handle the drops.
|
||||
* It will also reset the [BlockState] of this block at the given location to the start of growth.
|
||||
*
|
||||
* @param world The [World] the apricorn is in.
|
||||
* @param world The [Level] the apricorn is in.
|
||||
* @param state The [BlockState] of the apricorn.
|
||||
* @param pos The [BlockPos] of the apricorn.
|
||||
* @return The [BlockState] after harvest.
|
||||
*/
|
||||
fun harvest(world: Level, state: BlockState, pos: BlockPos): BlockState {
|
||||
// Uses loot tables, to change the drops use 'data/cobblemon/loot_tables/blocks/<color>_apricorn.json'
|
||||
Block.dropResources(state, world, pos)
|
||||
dropResources(state, world, pos)
|
||||
// Don't use default as we want to keep the facing
|
||||
val resetState = state.setValue(AGE, MIN_AGE)
|
||||
world.setBlock(pos, resetState, Block.UPDATE_CLIENTS)
|
||||
world.setBlock(pos, resetState, UPDATE_CLIENTS)
|
||||
return resetState
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,32 @@ class BerryBlock(private val berryIdentifier: ResourceLocation, settings: Proper
|
||||
|
||||
override fun isBonemealSuccess(world: Level, random: RandomSource, pos: BlockPos, state: BlockState) = !this.isMaxAge(state)
|
||||
|
||||
override fun <T : BlockEntity> getTicker(world: Level, blockState: BlockState, blockWithEntityType: BlockEntityType<T>): BlockEntityTicker<T>? = createTickerHelper(blockWithEntityType, CobblemonBlockEntities.BERRY, BerryBlockEntity.TICKER)
|
||||
// Use Random Tick instead of BlockEntityTicker for performance optimization
|
||||
override fun <T : BlockEntity> getTicker(world: Level, blockState: BlockState, blockWithEntityType: BlockEntityType<T>): BlockEntityTicker<T>? = null
|
||||
|
||||
// Enable Random Tick: only immature and non-rooted berries need to tick
|
||||
override fun isRandomlyTicking(state: BlockState): Boolean {
|
||||
return state.getValue(AGE) < FRUIT_AGE && !state.getValue(IS_ROOTED)
|
||||
}
|
||||
|
||||
// Random Tick handler: uses time compensation to ensure correct growth speed
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
|
||||
val blockEntity = world.getBlockEntity(pos) as? BerryBlockEntity ?: return
|
||||
|
||||
// processTick calculates elapsed time and updates timer, returns whether growth is needed
|
||||
if (blockEntity.processTick(world, pos, state) && !world.getBlockTicks().hasScheduledTick(pos, this)) {
|
||||
// Schedule growth for next tick to avoid tick scheduler conflicts during randomTick
|
||||
world.scheduleTick(pos, this, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Scheduled tick handler: performs the actual growth
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun tick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
|
||||
if (state.getValue(IS_ROOTED)) return
|
||||
growHelper(world, random, pos, state)
|
||||
}
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
|
||||
@@ -8,20 +8,29 @@
|
||||
|
||||
package com.cobblemon.mod.common.block
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.CobblemonItemComponents
|
||||
import com.cobblemon.mod.common.CobblemonModificationBlocks
|
||||
import com.cobblemon.mod.common.api.modification.ModificationVariants
|
||||
import com.cobblemon.mod.common.block.entity.DisplayCaseBlockEntity
|
||||
import com.cobblemon.mod.common.item.PokeBallItem
|
||||
import com.cobblemon.mod.common.item.components.ModificationVariantComponent
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.Containers
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.InteractionResult
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.BlockItem
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.LevelAccessor
|
||||
import net.minecraft.world.level.LevelReader
|
||||
import net.minecraft.world.level.block.BaseEntityBlock
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
@@ -32,6 +41,11 @@ import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.DirectionProperty
|
||||
import net.minecraft.world.level.pathfinder.PathComputationType
|
||||
import net.minecraft.world.phys.BlockHitResult
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
import net.minecraft.world.level.storage.loot.LootParams
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
|
||||
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
class DisplayCaseBlock(settings: Properties) : BaseEntityBlock(settings) {
|
||||
@@ -80,6 +94,12 @@ class DisplayCaseBlock(settings: Properties) : BaseEntityBlock(settings) {
|
||||
else super.updateShape(state, direction, neighborState, world, pos, neighborPos)
|
||||
}
|
||||
|
||||
override fun setPlacedBy(world: Level, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) {
|
||||
super.setPlacedBy(world, pos, state, placer, stack)
|
||||
val entity = world.getBlockEntity(pos) as? DisplayCaseBlockEntity ?: return
|
||||
entity.applyVariantFromStack(stack)
|
||||
}
|
||||
|
||||
override fun useWithoutItem(
|
||||
state: BlockState,
|
||||
world: Level,
|
||||
@@ -95,6 +115,28 @@ class DisplayCaseBlock(settings: Properties) : BaseEntityBlock(settings) {
|
||||
return result
|
||||
}
|
||||
|
||||
override fun getCloneItemStack(level: LevelReader, pos: BlockPos, state: BlockState): ItemStack {
|
||||
val stack = super.getCloneItemStack(level, pos, state)
|
||||
if (state.block != CobblemonBlocks.DISPLAY_CASE) {
|
||||
return stack
|
||||
}
|
||||
|
||||
val variantId = (level.getBlockEntity(pos) as? DisplayCaseBlockEntity)?.variantIdentifier ?: return stack
|
||||
stack.set(CobblemonItemComponents.MODIFICATION_VARIANT, ModificationVariantComponent(variantId))
|
||||
return stack
|
||||
}
|
||||
|
||||
override fun getDrops(state: BlockState, params: LootParams.Builder): List<ItemStack> {
|
||||
val drop = ItemStack(this)
|
||||
if (state.block == CobblemonBlocks.DISPLAY_CASE) {
|
||||
val variantId = (params.getOptionalParameter(LootContextParams.BLOCK_ENTITY) as? DisplayCaseBlockEntity)?.variantIdentifier
|
||||
if (variantId != null) {
|
||||
drop.set(CobblemonItemComponents.MODIFICATION_VARIANT, ModificationVariantComponent(variantId))
|
||||
}
|
||||
}
|
||||
return listOf(drop)
|
||||
}
|
||||
|
||||
override fun onRemove(
|
||||
state: BlockState,
|
||||
world: Level,
|
||||
@@ -124,6 +166,24 @@ class DisplayCaseBlock(settings: Properties) : BaseEntityBlock(settings) {
|
||||
|
||||
override fun hasAnalogOutputSignal(state: BlockState): Boolean = true
|
||||
|
||||
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
return getVariantShape(state, level, pos) ?: BASE_SHAPE
|
||||
}
|
||||
|
||||
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
return getShape(state, level, pos, context)
|
||||
}
|
||||
|
||||
private fun getVariantShape(state: BlockState, level: BlockGetter, pos: BlockPos): VoxelShape? {
|
||||
val blockEntity = level.getBlockEntity(pos) as? DisplayCaseBlockEntity ?: return null
|
||||
val variantIdentifier = blockEntity.variantIdentifier
|
||||
?: CobblemonModificationBlocks.displayCaseVariantIdentifier(state.block)
|
||||
?: return null
|
||||
val variant = ModificationVariants.getByIdentifier(variantIdentifier) ?: return null
|
||||
val shape = variant.createShape() ?: return null
|
||||
return rotateShape(Direction.NORTH, state.getValue(FACING), shape)
|
||||
}
|
||||
|
||||
override fun codec(): MapCodec<out BaseEntityBlock> {
|
||||
return CODEC
|
||||
}
|
||||
@@ -133,6 +193,7 @@ class DisplayCaseBlock(settings: Properties) : BaseEntityBlock(settings) {
|
||||
companion object {
|
||||
val CODEC = simpleCodec(::DisplayCaseBlock)
|
||||
val ITEM_DIRECTION = DirectionProperty.create("item_facing")
|
||||
val BASE_SHAPE: VoxelShape = Shapes.block()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.block.DirectionalBlock
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.Property
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
@@ -42,6 +43,8 @@ abstract class GrowableStoneBlock(
|
||||
val nextStage: Block?
|
||||
) : DirectionalBlock(settings) {
|
||||
|
||||
abstract val growthChance: Int
|
||||
|
||||
private val upShape: VoxelShape = Block.box(
|
||||
xzOffset.toDouble(),
|
||||
0.0,
|
||||
@@ -95,20 +98,26 @@ abstract class GrowableStoneBlock(
|
||||
.setValue(FACING, Direction.DOWN))
|
||||
}
|
||||
|
||||
abstract fun canGrow(pos: BlockPos, world: BlockGetter): Boolean
|
||||
abstract fun canGrow(state: BlockState, pos: BlockPos, world: BlockGetter): Boolean
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(FACING)
|
||||
}
|
||||
|
||||
override fun isRandomlyTicking(state: BlockState): Boolean = stage < MAX_STAGE
|
||||
|
||||
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
|
||||
if (world.random.nextInt(5) == 0 && canGrow(pos, world)) {
|
||||
if (world.random.nextInt(growthChance) == 0 && canGrow(state, pos, world)) {
|
||||
val block = nextStage
|
||||
|
||||
if (block != null) {
|
||||
val newState = block.defaultBlockState().setValue(FACING, state.getValue(FACING)) as BlockState
|
||||
var newState = block.defaultBlockState()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
for (property in state.properties) {
|
||||
if (newState.hasProperty(property)) {
|
||||
newState = newState.setValue(property as Property<Comparable<Any>>, state.getValue(property) as Comparable<Any>)
|
||||
}
|
||||
}
|
||||
|
||||
world.setBlockAndUpdate(pos, newState)
|
||||
}
|
||||
}
|
||||
@@ -155,14 +164,4 @@ abstract class GrowableStoneBlock(
|
||||
else -> upShape
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val STAGE_0 = 0
|
||||
const val STAGE_1 = 1
|
||||
const val STAGE_2 = 2
|
||||
const val STAGE_3 = 3
|
||||
|
||||
const val MAX_STAGE = STAGE_3
|
||||
const val MIN_STAGE = STAGE_0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.block
|
||||
|
||||
import com.cobblemon.mod.common.block.modificationstation.ModificationStationMenu
|
||||
import com.cobblemon.mod.common.util.rotateShape
|
||||
import com.mojang.serialization.MapCodec
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.stats.Stats
|
||||
import net.minecraft.world.InteractionResult
|
||||
import net.minecraft.world.MenuProvider
|
||||
import net.minecraft.world.SimpleMenuProvider
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.inventory.ContainerLevelAccess
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock
|
||||
import net.minecraft.world.level.block.Mirror
|
||||
import net.minecraft.world.level.block.RenderShape
|
||||
import net.minecraft.world.level.block.Rotation
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.DirectionProperty
|
||||
import net.minecraft.world.level.pathfinder.PathComputationType
|
||||
import net.minecraft.world.phys.BlockHitResult
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class ModificationStationBlock(properties: Properties) : Block(properties) {
|
||||
override fun codec(): MapCodec<ModificationStationBlock> = CODEC
|
||||
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any().setValue(FACING, Direction.NORTH))
|
||||
}
|
||||
|
||||
override fun getStateForPlacement(context: BlockPlaceContext): BlockState {
|
||||
return defaultBlockState().setValue(FACING, context.horizontalDirection.opposite)
|
||||
}
|
||||
|
||||
override fun useWithoutItem(state: BlockState, level: Level, pos: BlockPos, player: Player, hit: BlockHitResult): InteractionResult {
|
||||
if (level.isClientSide) {
|
||||
return InteractionResult.SUCCESS
|
||||
}
|
||||
|
||||
player.openMenu(state.getMenuProvider(level, pos))
|
||||
player.awardStat(Stats.INTERACT_WITH_STONECUTTER)
|
||||
return InteractionResult.CONSUME
|
||||
}
|
||||
|
||||
override fun getMenuProvider(state: BlockState, level: Level, pos: BlockPos): MenuProvider {
|
||||
return SimpleMenuProvider(
|
||||
{ syncId, inventory, _ -> ModificationStationMenu(syncId, inventory, ContainerLevelAccess.create(level, pos)) },
|
||||
CONTAINER_TITLE
|
||||
)
|
||||
}
|
||||
|
||||
override fun getShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
return SHAPES_BY_FACING[state.getValue(FACING)] ?: SHAPE
|
||||
}
|
||||
|
||||
override fun getCollisionShape(state: BlockState, level: BlockGetter, pos: BlockPos, context: CollisionContext): VoxelShape {
|
||||
return getShape(state, level, pos, context)
|
||||
}
|
||||
|
||||
override fun useShapeForLightOcclusion(state: BlockState): Boolean = true
|
||||
override fun getRenderShape(state: BlockState): RenderShape = RenderShape.MODEL
|
||||
|
||||
override fun rotate(state: BlockState, rotation: Rotation): BlockState {
|
||||
return state.setValue(FACING, rotation.rotate(state.getValue(FACING)))
|
||||
}
|
||||
|
||||
override fun mirror(state: BlockState, mirror: Mirror): BlockState {
|
||||
return state.rotate(mirror.getRotation(state.getValue(FACING)))
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(FACING)
|
||||
}
|
||||
|
||||
override fun isPathfindable(state: BlockState, type: PathComputationType): Boolean = false
|
||||
|
||||
companion object {
|
||||
val CODEC: MapCodec<ModificationStationBlock> = simpleCodec(::ModificationStationBlock)
|
||||
val CONTAINER_TITLE: Component = Component.translatable("container.cobblemon.modification_station")
|
||||
val FACING: DirectionProperty = HorizontalDirectionalBlock.FACING
|
||||
val SHAPE: VoxelShape = Shapes.or(
|
||||
box(3.0, 0.0, 4.0, 13.0, 17.0, 13.0),
|
||||
box(0.0, 8.0, 4.0, 3.0, 13.0, 13.0),
|
||||
box(2.0, 10.5, 0.5, 16.0, 14.5, 13.5),
|
||||
box(3.0, 3.0, 4.0, 4.0, 8.0, 6.0),
|
||||
box(12.0, 3.0, 4.0, 13.0, 8.0, 6.0),
|
||||
box(11.5, 13.5, 0.5, 15.5, 14.5, 4.5),
|
||||
box(1.0, 13.0, 9.5, 2.0, 17.0, 12.5)
|
||||
)
|
||||
val SHAPES_BY_FACING: Map<Direction, VoxelShape> = mapOf(
|
||||
Direction.NORTH to SHAPE,
|
||||
Direction.EAST to rotateShape(Direction.NORTH, Direction.EAST, SHAPE),
|
||||
Direction.SOUTH to rotateShape(Direction.NORTH, Direction.SOUTH, SHAPE),
|
||||
Direction.WEST to rotateShape(Direction.NORTH, Direction.WEST, SHAPE)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,11 @@ import com.mojang.serialization.MapCodec
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.util.StringRepresentable
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.InteractionResult
|
||||
import net.minecraft.world.ItemInteractionResult
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.Level
|
||||
@@ -28,6 +33,7 @@ import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties.HORIZONTAL_FACING
|
||||
import net.minecraft.world.level.block.state.properties.EnumProperty
|
||||
import net.minecraft.world.level.pathfinder.PathComputationType
|
||||
import net.minecraft.world.phys.BlockHitResult
|
||||
import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
@@ -35,7 +41,8 @@ import net.minecraft.world.phys.shapes.VoxelShape
|
||||
class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
|
||||
init {
|
||||
registerDefaultState(stateDefinition.any()
|
||||
.setValue(HORIZONTAL_FACING, Direction.NORTH))
|
||||
.setValue(HORIZONTAL_FACING, Direction.NORTH)
|
||||
.setValue(SCREEN, MonitorScreen.OFF))
|
||||
}
|
||||
|
||||
override fun createMultiBlockEntity(
|
||||
@@ -55,6 +62,47 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
|
||||
return defaultBlockState().setValue(HORIZONTAL_FACING, blockPlaceContext.horizontalDirection)
|
||||
}
|
||||
|
||||
override fun useWithoutItem(
|
||||
state: BlockState,
|
||||
level: Level,
|
||||
pos: BlockPos,
|
||||
player: Player,
|
||||
hit: BlockHitResult
|
||||
): InteractionResult {
|
||||
val entity = level.getBlockEntity(pos) as? FossilMultiblockEntity ?: return InteractionResult.PASS
|
||||
return entity.handleUseWithoutItem(state, level, pos, player)
|
||||
}
|
||||
|
||||
override fun useItemOn(
|
||||
stack: ItemStack,
|
||||
state: BlockState,
|
||||
level: Level,
|
||||
pos: BlockPos,
|
||||
player: Player,
|
||||
hand: InteractionHand,
|
||||
hit: BlockHitResult
|
||||
): ItemInteractionResult {
|
||||
val entity = level.getBlockEntity(pos) as? FossilMultiblockEntity
|
||||
?: return ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION
|
||||
val result = entity.handleUseItem(stack, state, level, pos, player, hand)
|
||||
if (result == ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION) {
|
||||
return if (entity.handleUseWithoutItem(state, level, pos, player).consumesAction()) {
|
||||
ItemInteractionResult.sidedSuccess(level.isClientSide)
|
||||
} else {
|
||||
ItemInteractionResult.PASS_TO_DEFAULT_BLOCK_INTERACTION
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun onRemove(state: BlockState, level: Level, pos: BlockPos, newState: BlockState, movedByPiston: Boolean) {
|
||||
if (state.block != newState.block && !level.isClientSide && !movedByPiston) {
|
||||
val entity = level.getBlockEntity(pos) as? FossilMultiblockEntity
|
||||
entity?.dropDisk(level, pos, state)
|
||||
}
|
||||
super.onRemove(state, level, pos, newState, movedByPiston)
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(HORIZONTAL_FACING)
|
||||
builder.add(SCREEN)
|
||||
@@ -143,8 +191,27 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
|
||||
BLUE_PROGRESS_7,
|
||||
BLUE_PROGRESS_8,
|
||||
BLUE_PROGRESS_9,
|
||||
GREEN_PROGRESS_9;
|
||||
GREEN_PROGRESS_9,
|
||||
TM_NORMAL,
|
||||
TM_FIRE,
|
||||
TM_WATER,
|
||||
TM_GRASS,
|
||||
TM_ELECTRIC,
|
||||
TM_ICE,
|
||||
TM_FIGHTING,
|
||||
TM_POISON,
|
||||
TM_GROUND,
|
||||
TM_FLYING,
|
||||
TM_PSYCHIC,
|
||||
TM_BUG,
|
||||
TM_ROCK,
|
||||
TM_GHOST,
|
||||
TM_DRAGON,
|
||||
TM_DARK,
|
||||
TM_STEEL,
|
||||
TM_FAIRY,
|
||||
MUSIC;
|
||||
|
||||
override fun getSerializedName(): String = this.name.lowercase()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ import net.minecraft.world.phys.shapes.CollisionContext
|
||||
import net.minecraft.world.phys.shapes.Shapes
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class PCBlock(properties: Properties): BaseEntityBlock(properties), SimpleWaterloggedBlock {
|
||||
open class PCBlock(properties: Properties): BaseEntityBlock(properties), SimpleWaterloggedBlock {
|
||||
companion object {
|
||||
val CODEC = simpleCodec(::PCBlock)
|
||||
val PART = EnumProperty.create("part", PCPart::class.java)
|
||||
@@ -163,7 +163,7 @@ class PCBlock(properties: Properties): BaseEntityBlock(properties), SimpleWaterl
|
||||
}
|
||||
}
|
||||
|
||||
fun getPositionOfOtherPart(state: BlockState, pos: BlockPos): BlockPos {
|
||||
open fun getPositionOfOtherPart(state: BlockState, pos: BlockPos): BlockPos {
|
||||
return if (state.getValue(PART) == PCPart.BOTTOM) {
|
||||
pos.above()
|
||||
} else {
|
||||
@@ -171,7 +171,7 @@ class PCBlock(properties: Properties): BaseEntityBlock(properties), SimpleWaterl
|
||||
}
|
||||
}
|
||||
|
||||
fun getBasePosition(state: BlockState, pos: BlockPos): BlockPos {
|
||||
open fun getBasePosition(state: BlockState, pos: BlockPos): BlockPos {
|
||||
return if (isBase(state)) {
|
||||
pos
|
||||
} else {
|
||||
@@ -331,4 +331,4 @@ class PCBlock(properties: Properties): BaseEntityBlock(properties), SimpleWaterl
|
||||
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.block
|
||||
|
||||
import com.cobblemon.mod.common.block.entity.TMShelfBlockEntity
|
||||
import com.mojang.serialization.MapCodec
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.InteractionResult
|
||||
import net.minecraft.world.ItemInteractionResult
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.item.context.BlockPlaceContext
|
||||
import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.block.*
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty
|
||||
import net.minecraft.world.level.block.state.properties.DirectionProperty
|
||||
import net.minecraft.world.Containers
|
||||
import net.minecraft.world.phys.BlockHitResult
|
||||
import net.minecraft.world.phys.shapes.VoxelShape
|
||||
|
||||
class TMShelfBlock(properties: Properties) : BaseEntityBlock(properties) {
|
||||
companion object {
|
||||
val CODEC: MapCodec<TMShelfBlock> = simpleCodec(::TMShelfBlock)
|
||||
val FACING: DirectionProperty = HorizontalDirectionalBlock.FACING
|
||||
val SLOT_OCCUPIED_PROPERTIES = (0 until 14).map {
|
||||
BooleanProperty.create("slot_${it}_occupied")
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val baseState = stateDefinition.any().setValue(FACING, Direction.NORTH)
|
||||
registerDefaultState(SLOT_OCCUPIED_PROPERTIES.fold(baseState) { state, prop ->
|
||||
state.setValue(prop, false)
|
||||
})
|
||||
}
|
||||
|
||||
override fun codec() = CODEC
|
||||
|
||||
override fun newBlockEntity(pos: BlockPos, state: BlockState): BlockEntity {
|
||||
return TMShelfBlockEntity(pos, state)
|
||||
}
|
||||
|
||||
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
|
||||
builder.add(FACING)
|
||||
SLOT_OCCUPIED_PROPERTIES.forEach { builder.add(it) }
|
||||
}
|
||||
|
||||
override fun getRenderShape(state: BlockState): RenderShape = RenderShape.MODEL
|
||||
|
||||
override fun getStateForPlacement(ctx: BlockPlaceContext): BlockState {
|
||||
return defaultBlockState().setValue(FACING, ctx.horizontalDirection.opposite)
|
||||
}
|
||||
|
||||
override fun useWithoutItem(
|
||||
state: BlockState, level: Level, pos: BlockPos,
|
||||
player: Player, hit: BlockHitResult
|
||||
): InteractionResult {
|
||||
val entity = level.getBlockEntity(pos) as? TMShelfBlockEntity ?: return InteractionResult.PASS
|
||||
return entity.handleUseWithoutItem(state, level, pos, player, hit)
|
||||
}
|
||||
|
||||
override fun useItemOn(
|
||||
stack: ItemStack, state: BlockState, level: Level, pos: BlockPos,
|
||||
player: Player, hand: InteractionHand, hit: BlockHitResult
|
||||
): ItemInteractionResult {
|
||||
val entity = level.getBlockEntity(pos) as? TMShelfBlockEntity ?: return ItemInteractionResult.SKIP_DEFAULT_BLOCK_INTERACTION
|
||||
return entity.handleUseItem(stack, state, level, pos, player, hit)
|
||||
}
|
||||
|
||||
override fun hasAnalogOutputSignal(state: BlockState) = true
|
||||
|
||||
override fun getAnalogOutputSignal(state: BlockState, level: Level, pos: BlockPos): Int {
|
||||
val entity = level.getBlockEntity(pos) as? TMShelfBlockEntity
|
||||
return entity?.items?.count { !it.isEmpty }?.coerceIn(0, 14) ?: 0
|
||||
}
|
||||
|
||||
override fun onRemove(state: BlockState, level: Level, pos: BlockPos, newState: BlockState, movedByPiston: Boolean) {
|
||||
if (!state.`is`(newState.block)) {
|
||||
if (!level.isClientSide && !movedByPiston) {
|
||||
val entity = level.getBlockEntity(pos) as? TMShelfBlockEntity
|
||||
if (entity != null) {
|
||||
for (index in entity.items.indices) {
|
||||
val stack = entity.items[index]
|
||||
if (!stack.isEmpty) {
|
||||
Containers.dropItemStack(level, pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, stack)
|
||||
entity.items[index] = ItemStack.EMPTY
|
||||
}
|
||||
}
|
||||
level.updateNeighbourForOutputSignal(pos, this)
|
||||
}
|
||||
}
|
||||
super.onRemove(state, level, pos, newState, movedByPiston)
|
||||
} else {
|
||||
super.onRemove(state, level, pos, newState, movedByPiston)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getShape(
|
||||
state: BlockState, level: BlockGetter,
|
||||
pos: BlockPos, context: net.minecraft.world.phys.shapes.CollisionContext
|
||||
): VoxelShape = Block.box(0.0, 0.0, 0.0, 16.0, 16.0, 16.0)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user