Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ After publishing, the action prepares the next development version and archives

## Unreleased

- The market and the automaton follow the village. A villager counts while their bed, bell, or job site sits in that village, wherever they are standing.
- An automaton placed directly under a job site trades only with the villager who claimed it. The window lists just that villager's offers.

## 2.0.1

- The villager faces sit in the barrel instead of sticking out. The market opening is an oval. The automaton opening is an oval over a square cut, and its bottom stays solid. The nose is narrower.
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

Trade with nearby villagers from one block, or let a Market Automaton trade for you.

The Villager Market looks for villagers within 48 blocks, and 16 blocks up or down. Their offers are grouped by a custom name when they have one, and by profession otherwise. Show All, on by default, lists offers you cannot pay for yet. x1 buys the selected offer once. xN buys as many as you can pay for and the offer still has left. The window closes when you walk more than 8 blocks away.
The Villager Market follows the village around the block. A villager counts while their bed, bell, or job site sits in that village, wherever they are standing. Offers are grouped by a custom name when they have one, and by profession otherwise. Show All, on by default, lists offers you cannot pay for yet. x1 buys the selected offer once. xN buys as many as you can pay for and the offer still has left. The window closes when you walk more than 8 blocks away.

The Market Automaton trades on its own. Choose an offer, and hoppers can fill the payment and take the result from the bottom. Put it directly under a job site and it trades only with the villager who claimed that block.

## Jars

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ public void componentsMustMatch(GameTestHelper helper) {
}

@GameTest(structure = "villagermarket:wide")
public void villagerOutsideTheBoxIsNotTraded(GameTestHelper helper) {
MarketGameTests.villagerOutsideTheBoxIsNotTraded(helper);
public void villagerOutsideTheVillageIsNotTraded(GameTestHelper helper) {
MarketGameTests.villagerOutsideTheVillageIsNotTraded(helper);
}

@GameTest(structure = "villagermarket:wide")
public void distantVillagerWithAVillageJobIsTraded(GameTestHelper helper) {
MarketGameTests.distantVillagerWithAVillageJobIsTraded(helper);
}

@GameTest(structure = "villagermarket:wide")
Expand Down Expand Up @@ -49,4 +54,14 @@ public void automatonStopsWithoutRedstone(GameTestHelper helper) {
public void automatonBuysTheCheapestSimilar(GameTestHelper helper) {
MarketGameTests.automatonBuysTheCheapestSimilar(helper);
}

@GameTest(structure = "villagermarket:wide")
public void automatonUnderAJobSiteTradesWithItsOwner(GameTestHelper helper) {
MarketGameTests.automatonUnderAJobSiteTradesWithItsOwner(helper);
}

@GameTest(structure = "villagermarket:wide")
public void unclaimedJobSiteDoesNotTrade(GameTestHelper helper) {
MarketGameTests.unclaimedJobSiteDoesNotTrade(helper);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private static void registerTests(RegisterGameTestsEvent event) {
@GameTestGenerator
public static Collection<TestFunction> generated() {
return MarketGameTests.CASES.stream()
.map(test -> new TestFunction(test.name(), Constants.MOD_ID + "." + test.name(), "villagermarket:empty",
.map(test -> new TestFunction(test.name(), Constants.MOD_ID + "." + test.name(), "villagermarket:wide",
100, 0L, true, test.body()))
.toList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import net.modcrafters.minecraft.villagermarket.ModContent;
import net.modcrafters.minecraft.villagermarket.menu.MachineSlots;
import net.modcrafters.minecraft.villagermarket.menu.MarketAutomatonMenu;
import net.modcrafters.minecraft.villagermarket.menu.VillagerMarketMenu;
import net.modcrafters.minecraft.villagermarket.network.MarketPayloads;
import net.modcrafters.minecraft.villagermarket.platform.Services;
import net.modcrafters.minecraft.villagermarket.search.JobSites;
import net.modcrafters.minecraft.villagermarket.search.MarketSearch;
import net.modcrafters.minecraft.villagermarket.trade.ArmedTrade;
import net.modcrafters.minecraft.villagermarket.trade.ListedOffer;
Expand Down Expand Up @@ -47,13 +47,19 @@ public class MarketAutomatonBlockEntity extends BlockEntity implements WorldlyCo
private RedstoneMode redstone = RedstoneMode.IGNORED;
private boolean similar;
private int ticks;
private boolean sawBinding;
private boolean bound;

public MarketAutomatonBlockEntity(BlockPos pos, BlockState state) {
super(ModContent.automatonType(), pos, state);
}

public static void serverTick(Level level, BlockPos pos, BlockState state, MarketAutomatonBlockEntity machine) {
if (!(level instanceof ServerLevel server) || !machine.hasRecipe() || !machine.allowsWork(level)) {
if (!(level instanceof ServerLevel server)) {
return;
}
machine.watchBinding(server, pos);
if (!machine.hasRecipe() || !machine.allowsWork(level)) {
return;
}
if (machine.ticks > 0) {
Expand Down Expand Up @@ -127,7 +133,7 @@ private boolean trade(ServerLevel server, BlockPos pos) {
MerchantOffer chosen = null;
int bestA = Integer.MAX_VALUE;
int bestB = Integer.MAX_VALUE;
for (Villager villager : MarketSearch.find(server, pos)) {
for (Villager villager : MarketSearch.forAutomaton(server, pos)) {
var offers = villager.getOffers();
for (int index = 0; index < offers.size(); index++) {
MerchantOffer offer = offers.get(index);
Expand Down Expand Up @@ -157,8 +163,22 @@ private boolean trade(ServerLevel server, BlockPos pos) {
return true;
}

private void watchBinding(ServerLevel server, BlockPos pos) {
boolean now = JobSites.workstation(server.getBlockState(pos.above()));
if (!sawBinding) {
sawBinding = true;
bound = now;
return;
}
if (now == bound) {
return;
}
bound = now;
publishOffers(server);
}

private void publishOffers(ServerLevel server) {
List<ListedOffer> fresh = VillagerMarketMenu.scan(server, this.worldPosition);
List<ListedOffer> fresh = MarketAutomatonMenu.scan(server, this.worldPosition);
for (ServerPlayer player : server.players()) {
if (player.containerMenu instanceof MarketAutomatonMenu menu && menu.pos().equals(this.worldPosition)) {
menu.replace(fresh);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import net.modcrafters.minecraft.villagermarket.block.entity.RedstoneMode;
import net.modcrafters.minecraft.villagermarket.menu.MachineSlots;
import net.modcrafters.minecraft.villagermarket.search.JobSites;
import net.modcrafters.minecraft.villagermarket.menu.MarketAutomatonMenu;
import net.modcrafters.minecraft.villagermarket.network.AutomatonPayloads;
import net.modcrafters.minecraft.villagermarket.network.MarketPayloads;
Expand Down Expand Up @@ -76,39 +77,45 @@ public static void applyOffers(MarketPayloads.Offers payload) {
@Override
public void extractBackground(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float partialTick) {
super.extractBackground(graphics, mouseX, mouseY, partialTick);
bind();
int left = this.leftPos;
int top = this.topPos;
drawPanel(graphics, left, top);
graphics.text(this.font, this.title, left + 8, top + 6, LABEL, false);
List<String> groups = logic.groups(this.menu.offers());
graphics.enableScissor(left + MarketScreenLogic.GROUP_X, top + MarketScreenLogic.GROUP_Y,
left + MarketScreenLogic.GROUP_X + MarketScreenLogic.GROUP_W, top + MarketScreenLogic.GROUP_Y + logic.listHeight);
drawGroup(graphics, left, top, mouseX, mouseY, 0, logic.selectedGroup.isEmpty(), Component.translatable("gui.villagermarket.all"));
for (int i = 0; i < groups.size(); i++) {
drawGroup(graphics, left, top, mouseX, mouseY, i + 1, groups.get(i).equals(logic.selectedGroup), VillagerMarketScreen.groupLabel(groups.get(i)));
if (!logic.singleVillager) {
List<String> groups = logic.groups(this.menu.offers());
graphics.enableScissor(left + MarketScreenLogic.GROUP_X, top + MarketScreenLogic.GROUP_Y,
left + MarketScreenLogic.GROUP_X + MarketScreenLogic.GROUP_W, top + MarketScreenLogic.GROUP_Y + logic.listHeight);
drawGroup(graphics, left, top, mouseX, mouseY, 0, logic.selectedGroup.isEmpty(), Component.translatable("gui.villagermarket.all"));
for (int i = 0; i < groups.size(); i++) {
drawGroup(graphics, left, top, mouseX, mouseY, i + 1, groups.get(i).equals(logic.selectedGroup), VillagerMarketScreen.groupLabel(groups.get(i)));
}
graphics.disableScissor();
}
graphics.disableScissor();
hovered = ItemStack.EMPTY;
tip = Component.empty();
Inventory inventory = this.minecraft.player.getInventory();
List<ListedOffer> shown = logic.visible(this.menu.offers(), inventory);
logic.fitOffer(shown.size());
int offerX = logic.offerX();
int offerW = logic.offerWidth(shown.size());
graphics.enableScissor(left + MarketScreenLogic.OFFER_X, top + MarketScreenLogic.OFFER_Y,
left + MarketScreenLogic.OFFER_X + offerW, top + MarketScreenLogic.OFFER_Y + logic.listHeight);
graphics.enableScissor(left + offerX, top + MarketScreenLogic.OFFER_Y,
left + offerX + offerW, top + MarketScreenLogic.OFFER_Y + logic.listHeight);
for (int i = logic.offerScroll; i < shown.size(); i++) {
int y = top + MarketScreenLogic.OFFER_Y + (i - logic.offerScroll) * MarketScreenLogic.OFFER_ROW;
if (y + MarketScreenLogic.OFFER_ROW > top + MarketScreenLogic.OFFER_Y + logic.listHeight) {
break;
}
drawOffer(graphics, shown.get(i), left + MarketScreenLogic.OFFER_X, y, offerW, mouseX, mouseY, inventory);
drawOffer(graphics, shown.get(i), left + offerX, y, offerW, mouseX, mouseY, inventory);
}
graphics.disableScissor();
drawOfferBar(graphics, left, top, shown.size());
if (!logic.singleVillager) {
drawSimilar(graphics, left, top, mouseX, mouseY);
}
Component show = Component.translatable(logic.showAll ? "gui.villagermarket.show_all" : "gui.villagermarket.available");
drawButton(graphics, left + MarketScreenLogic.SHOW_X, top + MachineSlots.SHOW_Y, MarketScreenLogic.SHOW_W, MarketScreenLogic.SHOW_H,
show, logic.showAll || inside(mouseX, mouseY, left + MarketScreenLogic.SHOW_X, top + MachineSlots.SHOW_Y, MarketScreenLogic.SHOW_W, MarketScreenLogic.SHOW_H));
drawSimilar(graphics, left, top, mouseX, mouseY);
drawSlot(graphics, left + MachineSlots.INPUT_X - 1, top + MachineSlots.INPUT_Y - 1);
drawSlot(graphics, left + MachineSlots.INPUT_X + 17, top + MachineSlots.INPUT_Y - 1);
graphics.blitSprite(RenderPipelines.GUI_TEXTURED, FLOW_ARROW, left + MachineSlots.ARROW_X, top + MachineSlots.INPUT_Y, MachineSlots.ARROW_W, MachineSlots.ARROW_H);
Expand Down Expand Up @@ -228,8 +235,13 @@ private void drawStack(GuiGraphicsExtractor graphics, ItemStack stack, int x, in
}
}

private void bind() {
logic.singleVillager = this.minecraft.level != null
&& JobSites.workstation(this.minecraft.level.getBlockState(this.menu.pos().above()));
}

private boolean similarHit(double mouseX, double mouseY) {
return inside((int) mouseX, (int) mouseY, this.leftPos + MachineSlots.SIMILAR_X, this.topPos + MachineSlots.SIMILAR_Y, MachineSlots.SIMILAR_SIZE, MachineSlots.SIMILAR_SIZE);
return !logic.singleVillager && inside((int) mouseX, (int) mouseY, this.leftPos + MachineSlots.SIMILAR_X, this.topPos + MachineSlots.SIMILAR_Y, MachineSlots.SIMILAR_SIZE, MachineSlots.SIMILAR_SIZE);
}

private boolean redstoneHit(double mouseX, double mouseY) {
Expand All @@ -256,6 +268,7 @@ public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mo

@Override
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
bind();
if (similarHit(event.x(), event.y())) {
Services.PLATFORM.sendToServer(new AutomatonPayloads.Similar());
return true;
Expand Down Expand Up @@ -285,6 +298,7 @@ public boolean mouseReleased(MouseButtonEvent event) {

@Override
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
bind();
List<ListedOffer> shown = logic.visible(this.menu.offers(), this.minecraft.player.getInventory());
logic.scroll(mouseX, mouseY, this.leftPos, this.topPos, scrollY, shown.size(), logic.groups(this.menu.offers()).size());
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public final class MarketScreenLogic {
public int listHeight = OFFER_H;
public int showY = SHOW_Y;
public boolean showAll = true;
public boolean singleVillager;
public String selectedGroup = "";
public int groupScroll;
public int offerScroll;
Expand Down Expand Up @@ -84,8 +85,13 @@ public boolean offerBar(int count) {
return count > offerRows();
}

public int offerX() {
return singleVillager ? GROUP_X : OFFER_X;
}

public int offerWidth(int count) {
return offerBar(count) ? BAR_X - OFFER_X - 2 : OFFER_W;
int right = offerBar(count) ? BAR_X - 2 : WIDTH - 8;
return right - offerX();
}

public void fitOffer(int count) {
Expand Down Expand Up @@ -184,7 +190,7 @@ public Click browse(double mouseX, double mouseY, int left, int top, List<Listed
}

private Click browseAt(int x, int y, List<ListedOffer> offers, Inventory inventory) {
if (inside(x, y, GROUP_X, GROUP_Y, GROUP_W, listHeight)) {
if (!singleVillager && inside(x, y, GROUP_X, GROUP_Y, GROUP_W, listHeight)) {
int index = groupScroll + (y - GROUP_Y) / GROUP_ROW;
List<String> groups = groups(offers);
if (index == 0) {
Expand All @@ -199,7 +205,7 @@ private Click browseAt(int x, int y, List<ListedOffer> offers, Inventory invento
if (pressBar(x, y, shown.size())) {
return Click.BAR;
}
if (inside(x, y, OFFER_X, OFFER_Y, offerWidth(shown.size()), listHeight)) {
if (inside(x, y, offerX(), OFFER_Y, offerWidth(shown.size()), listHeight)) {
int index = offerScroll + (y - OFFER_Y) / OFFER_ROW;
selected = index >= 0 && index < shown.size() ? shown.get(index) : null;
return Click.OFFER;
Expand All @@ -211,10 +217,10 @@ public void scroll(double mouseX, double mouseY, int left, int top, double delta
int x = (int) mouseX - left;
int y = (int) mouseY - top;
int step = delta > 0 ? -1 : 1;
if (inside(x, y, GROUP_X, GROUP_Y, GROUP_W, listHeight)) {
if (!singleVillager && inside(x, y, GROUP_X, GROUP_Y, GROUP_W, listHeight)) {
int visible = Math.max(1, listHeight / GROUP_ROW);
groupScroll = clamp(groupScroll + step, groupCount + 1 - visible);
} else if (inside(x, y, OFFER_X, OFFER_Y, BAR_X + BAR_W - OFFER_X, listHeight)) {
} else if (inside(x, y, offerX(), OFFER_Y, BAR_X + BAR_W - offerX(), listHeight)) {
offerScroll = clamp(offerScroll + step, offerCount - offerRows());
}
}
Expand Down
Loading
Loading