Skip to content

Commit 140f295

Browse files
committed
Integration testing of Marketplace hut complete.
1 parent ebf5a6b commit 140f295

File tree

14 files changed

+598
-81
lines changed

14 files changed

+598
-81
lines changed

README.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,43 @@
11
MC Trade Post
22
=======
33
*Description*
4+
45
This mod extends the MineColonies mod. It, and it's dependencies, are required for MC Trade Post to function.
56

67
MC Trade Post introduces an economic system which allows the selling of excess items for Minecash (‡). These accounts can be used for a variety of purposes, such as bribing raiders to leave the town alone, and unlocking additional upgrades and features.
78

8-
*How-To*
9-
Craft a Marketplace hut. Using the build tool, choose the Economics style, and contruct the Marketplace as you would any other hut. Interactions for the MC Trade Post mod are handled primarily in the Marketplace interface on that hut block.
9+
*In-Game How-To*
10+
11+
Craft a Marketplace hut. Using the build tool, choose the "Economics" style from the MC Trade Post mod, and contruct the Marketplace as you would any other hut. Interactions for the MC Trade Post mod are handled primarily in the Marketplace interface on that hut block.
12+
13+
*Custom Items*
14+
15+
Marketplace Hut - Supports the Marketplace building and the Shopkeeper job.
16+
Advanced Clipboard - Just like the regular clipboard but with a button that filters the outstanding needs down to only those expected to be fulfilled by a player.
17+
18+
*Custom Blocks*
19+
20+
Marketplace Hut Block - Block implementation of the item above.
21+
22+
==========
23+
*Compatibility Reference*
24+
25+
mctradepost-0.0.4 -> MineColonies 1.1.950 (Minecraft 1.21.1)
26+
27+
*Installation*
28+
29+
Download the appropriate mctradepost-x.y.z.jar file from Github at the root project directory.
30+
Copy that Jar file into your mods directory.
31+
Note that MineColonies and its dependencies must be present to work.
32+
1033

34+
==========
1135
*Current Status*
36+
1237
This mod can best be described as a "pre-alpha" state. It functionality may change rapidly and without warning.
1338

14-
*Roadmap*
39+
*Roadmap (Roughly Prioritized)*
40+
1541
Interface for spending earned income.
1642
- Unlock subsequent levels of the merchant building
1743
- Summon traders
@@ -23,6 +49,7 @@ Assembling colonies into collections (empire, state, etc.)
2349
Express shipping (intracolony and inter-colony) - faster item transport. Faster people transport.
2450

2551
*Complete*
52+
2653
Marketplace implementation for selling items.
2754

2855

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ mod_name=MC Trade Post
3232
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
3333
mod_license=All Rights Reserved
3434
# The mod version. See https://semver.org/
35-
mod_version=1.0.0
35+
mod_version=0.0.4
3636
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
3737
# This should match the base package used for the mod sources.
3838
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html

mctradepost-0.0.4.jar

663 KB
Binary file not shown.

src/main/java/com/deathfrog/mctradepost/MCTradePostMod.java

Lines changed: 360 additions & 30 deletions
Large diffs are not rendered by default.

src/main/java/com/deathfrog/mctradepost/core/blocks/huts/MCTPBaseBlockHut.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public void onBlockPlacedByBuildTool(
7777
if (tileEntity != null) {
7878
MCTradePostMod.LOGGER.info("Placing hut of type {}", tileEntity.getClass().getName());
7979
} else {
80-
MCTradePostMod.LOGGER.info("Attempting to place a null tile entity.");
80+
MCTradePostMod.LOGGER.error("Attempting to place a null tile entity.");
8181
}
8282

8383
if (tileEntity instanceof final MCTPTileEntityColonyBuilding hut)

src/main/java/com/deathfrog/mctradepost/core/client/gui/modules/WindowEconModule.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ private int getStatFor(String id, String intervalArg) {
8383
*/
8484
private void updateStats()
8585
{
86+
// TODO: Add current balance field.
8687
int itemCount = getStatFor(ITEM_SOLD, selectedInterval);
8788
final Text countLabel = findPaneOfTypeByID("itemcount", Text.class);
8889
countLabel.setText(Component.literal(itemCount + ""));

src/main/java/com/deathfrog/mctradepost/core/colony/jobs/JobShopkeeper.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ public EntityAIWorkShopkeeper generateAI()
4040
@Override
4141
public ResourceLocation getModel()
4242
{
43+
// TODO: Fix [17May2025 13:09:20.953] [Render thread/WARN] [net.minecraft.client.renderer.texture.TextureManager/]: Failed to load texture: mctradepost:textures/entity/citizen/default/shopkeepermale1_b.png
44+
// java.io.FileNotFoundException: mctradepost:textures/entity/citizen/default/shopkeepermale1_b.png
45+
// TODO: Fix [18May2025 08:30:35.970] [Render thread/WARN] [net.minecraft.client.renderer.texture.TextureManager/]: Failed to load texture: mctradepost:textures/entity/citizen/default/shopkeepermale1_w.png
46+
// java.io.FileNotFoundException: mctradepost:textures/entity/citizen/default/shopkeepermale1_w.png
47+
4348
// MCTradePostMod.LOGGER.info("Getting JobShopkeeper model {}", ModModelTypes.SHOPKEEPER_MODEL_ID);
4449
// MCTradePostMod.LOGGER.warn("Model load trace", new Exception("Model trace"));
4550
return ModModelTypes.SHOPKEEPER_MODEL_ID;

src/main/java/com/deathfrog/mctradepost/core/colony/jobs/buildings/modules/ItemValueRegistry.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
public class ItemValueRegistry
2929
{
3030
private static final Map<Item, Integer> itemValues = new ConcurrentHashMap<>();
31+
3132
private static boolean configured = false;
3233
private static boolean startTracking = false;
3334

@@ -50,7 +51,6 @@ public static void generateValues()
5051
Level level = server.overworld();
5152
RecipeManager recipeManager = level.getRecipeManager();
5253

53-
// TODO: Cut back on the logging once satisfied with the value system.
5454
for (Item item : BuiltInRegistries.ITEM)
5555
{
5656
if (itemValues.containsKey(item)) {
@@ -278,6 +278,24 @@ public static Set<ItemStorage> getSellableItems()
278278
.collect(Collectors.toSet());
279279
}
280280

281+
/**
282+
* Retrieves a set of item keys representing sellable items.
283+
* Logs an error message if the item values map is empty.
284+
*
285+
* @return a set of strings representing the keys of sellable items.
286+
*/
287+
public static Set<String> getSellableItemKeys()
288+
{
289+
if (itemValues.isEmpty())
290+
{
291+
MCTradePostMod.LOGGER.error("getSellableItemKeys called when itemValues is empty");
292+
}
293+
return itemValues.entrySet().stream()
294+
.filter(e -> e.getValue() > 0)
295+
.map(e -> e.getKey().toString())
296+
.collect(Collectors.toSet());
297+
}
298+
281299
/**
282300
* Logs all known item values to the mod logger.
283301
*/
@@ -327,5 +345,10 @@ private static void loadInitialValuesFromJson() {
327345

328346
MCTradePostMod.LOGGER.info("Item seed values loaded.");
329347
// logValues();
348+
}
349+
350+
/* In theory this should only be called on the client side after deserializing... */
351+
public static void deserializedSellableItem(String s, int value) {
352+
itemValues.putIfAbsent(BuiltInRegistries.ITEM.get(ResourceLocation.parse(s)), value);
330353
}
331354
}

src/main/java/com/deathfrog/mctradepost/core/colony/jobs/buildings/workerbuildings/BuildingMarketplace.java

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,12 @@ public void lostShelfAtDisplayPos(BlockPos pos) {
129129
if (displayShelfContents.containsKey(pos)) {
130130
List<ItemFrame> frames = colony.getWorld().getEntitiesOfClass(ItemFrame.class, new AABB(pos));
131131

132+
// If a new frame can be found at the expected position, use it.
132133
if (!frames.isEmpty()) {
133134
displayShelfContents.put(pos, new DisplayCase(pos, frames.get(0).getUUID(), displayShelfContents.get(pos).getStack(), 0));
135+
// Otherwise, issue a message about the missing frame.
134136
} else {
137+
// TODO: Put some delay logic on the sending of this message so it isn't a constant spam.
135138
displayShelfContents.put(pos, new DisplayCase(pos, null));
136139
MCTradePostMod.LOGGER.warn("Missing a display frame at {}", pos);
137140
MessageUtils.format("entity.shopkeeper.brokenframe").sendTo(getColony()).forAllPlayers();
@@ -165,6 +168,20 @@ private void syncDisplayFramesWithSavedItems()
165168
}
166169
}
167170

171+
/**
172+
* Initialize the display shelf locations based on what is tagged in the structure.
173+
* This makes the building look for the correct number of display shelves even if some are missing.
174+
* That way a "repair" action will fix the problem.
175+
*/
176+
public void identifyExpectedShelfPositions() {
177+
// We want any tagged display locations nto be added into the displayShelfContents if not already there.
178+
final List<BlockPos> shelfLocations = getLocationsFromTag("display_shelf");
179+
180+
for (BlockPos pos : shelfLocations)
181+
{
182+
displayShelfContents.putIfAbsent(pos, new DisplayCase(pos, null));
183+
}
184+
}
168185

169186
/**
170187
* Deserializes the NBT data for the building, restoring its state from the provided CompoundTag.
@@ -178,20 +195,8 @@ private void syncDisplayFramesWithSavedItems()
178195
public void deserializeNBT(@NotNull final HolderLookup.Provider provider, final CompoundTag compound)
179196
{
180197
super.deserializeNBT(provider, compound);
181-
displayShelfContents.clear();
182-
183-
final List<BlockPos> shelfLocations = getLocationsFromTag("display_shelf");
184-
MCTradePostMod.LOGGER.info("Shelf locations, as reported by structure tagging: {}", shelfLocations.size());
185-
186-
// Initialize the display shelf locations based on what is tagged in the structure.
187-
// This makes the building look for the correct number of display shelves even if some are missing.
188-
// That way a "repair" action will fix the problem.
189-
for (BlockPos pos : shelfLocations)
190-
{
191-
displayShelfContents.put(pos, new DisplayCase(pos, null));
192-
}
193198

194-
// Then fill in those locations with the saved items.
199+
// Fill in display shelf locations with the saved items.
195200
ListTag shelfTagList = compound.getList(TAG_DISPLAYSHELVES, Tag.TAG_COMPOUND);
196201
for (int i = 0; i < shelfTagList.size(); ++i)
197202
{

src/main/java/com/deathfrog/mctradepost/core/entity/ai/workers/crafting/EntityAIWorkShopkeeper.java

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import net.minecraft.world.level.Level;
2727
import org.jetbrains.annotations.NonNls;
2828
import org.jetbrains.annotations.NotNull;
29+
import java.util.Map;
30+
2931
import com.minecolonies.api.util.InventoryUtils;
3032
import static com.minecolonies.api.entity.ai.statemachine.states.AIWorkerState.*;
3133
import static com.minecolonies.api.util.constant.Constants.*;
@@ -37,7 +39,8 @@
3739

3840

3941
/**
40-
* Handles the Marketplace.
42+
* Handles the Shopkeeper AI. The shopkeeper works in the Marketplace.
43+
*
4144
*/
4245
public class EntityAIWorkShopkeeper extends AbstractEntityAIInteract<JobShopkeeper, BuildingMarketplace>
4346
{
@@ -87,6 +90,9 @@ public class EntityAIWorkShopkeeper extends AbstractEntityAIInteract<JobShopkeep
8790
private final static VisibleCitizenStatus SELLING =
8891
new VisibleCitizenStatus(ResourceLocation.parse(MCTradePostMod.MODID + ":textures/icons/work/shopkeeper.png"), "com.mctradepost.gui.visiblestatus.shopkeeper");
8992

93+
private final static VisibleCitizenStatus COMMUTING =
94+
new VisibleCitizenStatus(ResourceLocation.parse(MCTradePostMod.MODID + ":textures/icons/work/shopkeeper_commute.png"), "com.mctradepost.gui.visiblestatus.shopkeeper");
95+
9096
/**
9197
* Constructor for the AI
9298
*
@@ -238,21 +244,33 @@ private IAIState getMaterials()
238244
*/
239245
private IAIState decideWhatToDo()
240246
{
241-
worker.getCitizenData().setVisibleStatus(VisibleCitizenStatus.WORKING);
247+
248+
worker.getCitizenData().setVisibleStatus(COMMUTING);
242249

243250
if (!walkToBuilding())
244251
{
245252
setDelay(2);
246253
return getState();
247254
}
255+
256+
worker.getCitizenData().setVisibleStatus(VisibleCitizenStatus.WORKING);
248257

249258
final BuildingMarketplace building = this.building;
250-
// Map<BlockPos, DisplayCase> displayShelves = building.getDisplayShelves();
251259

260+
building.identifyExpectedShelfPositions();
261+
262+
Map<BlockPos, DisplayCase> displayShelves = building.getDisplayShelves();
252263
// MCTradePostMod.LOGGER.info("Deciding what to do. Display shelves: {}", displayShelves.size());
253264

265+
if (displayShelves.isEmpty()) {
266+
MCTradePostMod.LOGGER.warn("No display frames were found for the shopkeeper to use.");
267+
MessageUtils.format("entity.shopkeeper.noframes").sendTo(building.getColony()).forAllPlayers();
268+
setDelay(DECIDE_DELAY);
269+
return PAUSED;
270+
}
271+
254272
// First pass: Find any empty item frame (considered available to fill)
255-
for (final BlockPos displayLocation : building.getDisplayShelves().keySet())
273+
for (final BlockPos displayLocation : displayShelves.keySet())
256274
{
257275
DisplayCase displayCase = building.getDisplayShelves().get(displayLocation);
258276
final Level world = building.getColony().getWorld();
@@ -294,25 +312,12 @@ private IAIState decideWhatToDo()
294312
return CRAFT;
295313
} else if (ticks >= 0) {
296314
displayCase.setTickcount(ticks + 1);
297-
} else { // Something got put in the display frame but not by the builder. Remove it!
298-
ItemStack frameItem = frame.getItem();
299-
if (!frameItem.isEmpty())
300-
{
301-
// Remove the item from the frame
302-
frame.setItem(ItemStack.EMPTY);
303-
304-
// Attempt to insert into the worker's inventory
305-
ItemStack leftover = InventoryUtils.addItemStackToItemHandlerWithResult(worker.getInventoryCitizen(), frameItem);
306-
307-
// If inventory is full and there are leftovers, drop it at the worker's feet
308-
if (!leftover.isEmpty()) {
309-
InventoryUtils.spawnItemStack(world, worker.getX(), worker.getY(), worker.getZ(), leftover);
310-
}
311-
312-
MCTradePostMod.LOGGER.info("Shopkeeper removed unauthorized item {} from display at {}", frameItem, pos);
313-
314-
}
315-
315+
} else {
316+
// We are going to rely on sellFromDisplay (from CRAFT state) to take the item out.
317+
this.currentTarget = pos;
318+
setDelay(DECIDE_DELAY);
319+
worker.getCitizenData().setVisibleStatus(SELLING);
320+
return CRAFT;
316321
}
317322

318323
}
@@ -325,6 +330,7 @@ private IAIState decideWhatToDo()
325330

326331
/**
327332
* The AI will now remove the item from the display stand that he found full on his building and "sell" it, adding experience and stats.
333+
* Triggered from the CRAFT state.
328334
* @return the next IAIState after doing this
329335
*/
330336
private IAIState sellFromDisplay()
@@ -338,16 +344,33 @@ private IAIState sellFromDisplay()
338344
ItemStack item = frame.getItem();
339345
if (!item.isEmpty())
340346
{
341-
// "Sell" the item — remove it from the frame
342-
frame.setItem(ItemStack.EMPTY); // Empty the visual frame
343-
sellItem(item); // Calculate the value of the item and credit it to the economic module and stats module.
344-
displayCase.setStack(ItemStack.EMPTY); // Note that the display case is empty
345-
displayCase.setTickcount(-1); // Reset the timer.
347+
final List<ItemStorage> list = building.getModuleMatching(ItemListModule.class, m -> m.getId().equals(SELLABLE_LIST)).getList();
346348

347-
// Add experience, stats, etc.
348-
worker.getCitizenExperienceHandler().addExperience(BASE_XP_GAIN);
349-
incrementActionsDoneAndDecSaturation();
349+
if (list.contains(new ItemStorage(item)))
350+
{
351+
// "Sell" the item — remove it from the frame
352+
frame.setItem(ItemStack.EMPTY); // Empty the visual frame
353+
sellItem(item); // Calculate the value of the item and credit it to the economic module and stats module.
354+
355+
// Add experience, stats, etc.
356+
worker.getCitizenExperienceHandler().addExperience(BASE_XP_GAIN);
357+
incrementActionsDoneAndDecSaturation();
358+
} else {
359+
// Remove the invalid item from the frame
360+
frame.setItem(ItemStack.EMPTY);
361+
362+
// Attempt to insert into the worker's inventory
363+
ItemStack leftover = InventoryUtils.addItemStackToItemHandlerWithResult(worker.getInventoryCitizen(), item);
364+
365+
// If inventory is full and there are leftovers, drop it at the worker's feet
366+
if (!leftover.isEmpty()) {
367+
InventoryUtils.spawnItemStack(world, worker.getX(), worker.getY(), worker.getZ(), leftover);
368+
}
350369

370+
MCTradePostMod.LOGGER.info("Shopkeeper removed unauthorized item {} from display at {}", item, currentTarget);
371+
}
372+
displayCase.setStack(ItemStack.EMPTY); // Note that the display case is empty
373+
displayCase.setTickcount(-1); // Reset the timer.
351374
}
352375
}
353376

0 commit comments

Comments
 (0)