Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.talhanation.bannermod.bootstrap;

import com.talhanation.bannermod.commands.military.RecruitsAdminCommands;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;

Expand All @@ -11,6 +12,6 @@ public class BannerModLifecycle {

@SubscribeEvent
public void onRegisterCommands(RegisterCommandsEvent event) {
// Recruit and worker command registration will be filled in by wave 4 (events + commands wave)
RecruitsAdminCommands.register(event.getDispatcher());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ public BannerModMain() {
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, WorkersServerConfig.SERVER, "bannermod-workers-server.toml");
// Register war/RP config — own filename to avoid the SERVER filename collision called out above.
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, WarServerConfig.SERVER, "bannermod-war-server.toml");
// Register governor contract config.
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, com.talhanation.bannermod.config.BannerModGovernorContractConfig.SERVER, "bannermod-governor-contracts-server.toml");

// Lifecycle
modEventBus.addListener(this::setup);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package com.talhanation.bannermod.client.military.gui;

import com.mojang.blaze3d.systems.RenderSystem;
import com.talhanation.bannermod.bootstrap.BannerModMain;
import com.talhanation.bannermod.governance.BannerModContractPolicy;
import com.talhanation.bannermod.network.messages.military.MessageAcceptContract;
import com.talhanation.bannermod.network.messages.military.MessageCancelContract;
import com.talhanation.bannermod.network.messages.military.MessagePinContract;
import com.talhanation.bannermod.network.messages.military.MessageSetContractMaxReward;
import com.talhanation.bannermod.network.messages.military.MessageToClientUpdateContractBoard;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.client.gui.widget.ExtendedButton;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class GovernorContractScreen extends Screen {
private static final ResourceLocation TEXTURE = new ResourceLocation(BannerModMain.MOD_ID, "textures/gui/professions/blank_gui.png");

private static ContractBoardState latestState = ContractBoardState.empty();

private final int imageWidth = 260;
private final int imageHeight = 240;
private int leftPos;
private int topPos;

private EditBox maxRewardBox;

public GovernorContractScreen() {
super(Component.literal("Contract Board"));
}

public static void applyUpdate(UUID governorRecruitId, UUID claimUuid, boolean isOwner,
int maxContractReward, long currentTick,
List<MessageToClientUpdateContractBoard.ContractDto> contracts) {
latestState = new ContractBoardState(governorRecruitId, claimUuid, isOwner, maxContractReward, currentTick,
new ArrayList<>(contracts));
Minecraft mc = Minecraft.getInstance();
if (!(mc.screen instanceof GovernorContractScreen)) {
mc.setScreen(new GovernorContractScreen());
}
}

@Override
protected void init() {
this.leftPos = (this.width - imageWidth) / 2;
this.topPos = (this.height - imageHeight) / 2;

ContractBoardState state = latestState;

if (state.isOwner) {
maxRewardBox = new EditBox(this.font, leftPos + 130, topPos + 14, 60, 12,
Component.literal("max reward"));
maxRewardBox.setMaxLength(6);
maxRewardBox.setValue(state.maxContractReward > 0 ? String.valueOf(state.maxContractReward) : "");
addRenderableWidget(maxRewardBox);

addRenderableWidget(new ExtendedButton(leftPos + 195, topPos + 12, 50, 14,
Component.literal("Set"),
btn -> commitMaxReward()));
}

int yBase = topPos + 40;
for (int i = 0; i < state.contracts.size() && i < 6; i++) {
MessageToClientUpdateContractBoard.ContractDto dto = state.contracts.get(i);
int rowY = yBase + i * 32;
int col = leftPos + 6;

if (state.isOwner) {
addRenderableWidget(new ExtendedButton(col + 200, rowY + 2, 50, 12,
Component.literal(dto.pinned() ? "Unpin" : "Pin"),
btn -> sendPin(dto.contractId(), !dto.pinned())));
if ("open".equalsIgnoreCase(dto.status())) {
addRenderableWidget(new ExtendedButton(col + 200, rowY + 16, 50, 12,
Component.literal("Cancel"),
btn -> sendCancel(dto.contractId())));
}
} else if ("open".equalsIgnoreCase(dto.status()) && !dto.hasAcceptor()) {
addRenderableWidget(new ExtendedButton(col + 200, rowY + 8, 50, 14,
Component.literal("Accept"),
btn -> sendAccept(dto.contractId())));
}
}
}

private void commitMaxReward() {
if (maxRewardBox == null) return;
try {
int value = Integer.parseInt(maxRewardBox.getValue().trim());
BannerModMain.SIMPLE_CHANNEL.sendToServer(
new MessageSetContractMaxReward(latestState.governorRecruitId, Math.max(0, value)));
} catch (NumberFormatException ignored) {
}
}

private void sendAccept(UUID contractId) {
BannerModMain.SIMPLE_CHANNEL.sendToServer(
new MessageAcceptContract(latestState.governorRecruitId, contractId));
}

private void sendCancel(UUID contractId) {
BannerModMain.SIMPLE_CHANNEL.sendToServer(
new MessageCancelContract(latestState.governorRecruitId, contractId));
}

private void sendPin(UUID contractId, boolean pin) {
BannerModMain.SIMPLE_CHANNEL.sendToServer(
new MessagePinContract(latestState.governorRecruitId, contractId, pin));
}

@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float delta) {
renderBackground(guiGraphics);
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderColor(1F, 1F, 1F, 1F);
RenderSystem.setShaderTexture(0, TEXTURE);
guiGraphics.blit(TEXTURE, leftPos, topPos, 0, 0, imageWidth, imageHeight);

ContractBoardState state = latestState;
int x = leftPos + 6;
int y = topPos + 6;

guiGraphics.drawString(font, Component.literal("Contract Board — " + state.claimUuid.toString().substring(0, 8) + "…"), x, y, 0x4E3320, false);

if (state.isOwner) {
guiGraphics.drawString(font, Component.literal("Max reward cap: "), x, topPos + 16, 4210752, false);
}

if (state.contracts.isEmpty()) {
guiGraphics.drawString(font, Component.literal("No contracts posted yet."), x, topPos + 50, 4210752, false);
}

int yBase = topPos + 40;
for (int i = 0; i < state.contracts.size() && i < 6; i++) {
MessageToClientUpdateContractBoard.ContractDto dto = state.contracts.get(i);
int rowY = yBase + i * 32;
guiGraphics.fill(leftPos + 4, rowY, leftPos + imageWidth - 4, rowY + 30, 0x22000000);
guiGraphics.drawString(font, Component.literal(dto.type().replace('_', ' ')), x + 2, rowY + 2, 0x205020, false);
guiGraphics.drawString(font, Component.literal(BannerModContractPolicy.contractDescription(
com.talhanation.bannermod.governance.BannerModGovernorContractType.fromToken(dto.type())
)), x + 2, rowY + 12, 4210752, false);
String deadline = BannerModContractPolicy.deadlineLabel(dto.deadlineTick(), state.currentTick, dto.pinned());
guiGraphics.drawString(font, Component.literal("Reward: " + dto.reward() + " | " + dto.status() + " | " + deadline),
x + 2, rowY + 20, 0x666666, false);
}

super.render(guiGraphics, mouseX, mouseY, delta);
}

@Override
public boolean isPauseScreen() {
return false;
}

private record ContractBoardState(UUID governorRecruitId, UUID claimUuid, boolean isOwner,
int maxContractReward, long currentTick,
List<MessageToClientUpdateContractBoard.ContractDto> contracts) {
static ContractBoardState empty() {
return new ContractBoardState(new UUID(0, 0), new UUID(0, 0), false, 0, 0L, List.of());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import com.mojang.blaze3d.systems.RenderSystem;
import com.talhanation.bannermod.bootstrap.BannerModMain;
import com.talhanation.bannermod.entity.military.AbstractRecruitEntity;
import com.talhanation.bannermod.governance.BannerModGovernorAdvisory;
import com.talhanation.bannermod.governance.BannerModGovernorPolicy;
import com.talhanation.bannermod.inventory.military.GovernorContainer;
import com.talhanation.bannermod.network.messages.military.MessageOpenContractBoard;
import com.talhanation.bannermod.network.messages.military.MessageOpenGovernorScreen;
import com.talhanation.bannermod.network.messages.military.MessageToggleGovernorAutoManage;
import com.talhanation.bannermod.network.messages.military.MessageUpdateGovernorPolicy;
import de.maxhenkel.corelib.inventory.ScreenBase;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
Expand All @@ -30,8 +32,8 @@ public class GovernorScreen extends ScreenBase<GovernorContainer> {

public GovernorScreen(GovernorContainer container, Inventory playerInventory, Component title) {
super(TEXTURE, container, playerInventory, title);
this.imageWidth = 195;
this.imageHeight = 160;
this.imageWidth = 240;
this.imageHeight = 230;
this.player = container.getPlayerEntity();
this.recruit = container.getRecruit();
}
Expand All @@ -44,9 +46,25 @@ protected void init() {
addPolicyButtons(BannerModGovernorPolicy.GARRISON_PRIORITY, 88);
addPolicyButtons(BannerModGovernorPolicy.FORTIFICATION_PRIORITY, 112);
addPolicyButtons(BannerModGovernorPolicy.TAX_PRESSURE, 136);
addRenderableWidget(new ExtendedButton(this.leftPos + 150, this.topPos + 160, 80, 16,
Component.literal(autoManageLabel()),
button -> toggleAutoManage(button)));
addRenderableWidget(new ExtendedButton(this.leftPos + 150, this.topPos + 180, 80, 16,
Component.literal("[Contracts]"),
button -> BannerModMain.SIMPLE_CHANNEL.sendToServer(new MessageOpenContractBoard(this.recruit.getUUID()))));
BannerModMain.SIMPLE_CHANNEL.sendToServer(new MessageOpenGovernorScreen(this.recruit.getUUID(), false));
}

private String autoManageLabel() {
return latestState.autoManage ? "[Auto-manage: ON]" : "[Auto-manage: OFF]";
}

private void toggleAutoManage(net.minecraft.client.gui.components.Button button) {
boolean next = !latestState.autoManage;
button.setMessage(Component.literal(next ? "[Auto-manage: ON]" : "[Auto-manage: OFF]"));
BannerModMain.SIMPLE_CHANNEL.sendToServer(new MessageToggleGovernorAutoManage(this.recruit.getUUID(), next));
}

private void addPolicyButtons(BannerModGovernorPolicy policy, int yOffset) {
addRenderableWidget(new ExtendedButton(this.leftPos + 150, this.topPos + yOffset, 16, 16, Component.literal("-"), button -> stepPolicy(policy, -1)));
addRenderableWidget(new ExtendedButton(this.leftPos + 170, this.topPos + yOffset, 16, 16, Component.literal("+"), button -> stepPolicy(policy, 1)));
Expand All @@ -73,7 +91,8 @@ public void renderForeground(GuiGraphics guiGraphics, int mouseX, int mouseY, fl
GovernorViewState state = latestState;
int x = leftPos + 10;
int y = topPos + 10;
guiGraphics.drawString(font, Component.literal("Governor: " + recruit.getName().getString()), x, y, 4210752, false);

guiGraphics.drawString(font, Component.literal("Governor: " + recruit.getName().getString()), x, y, 0x4E3320, false);
y += 12;
guiGraphics.drawString(font, Component.literal("Settlement: " + state.settlementStatus), x, y, 4210752, false);
y += 12;
Expand All @@ -84,33 +103,40 @@ public void renderForeground(GuiGraphics guiGraphics, int mouseX, int mouseY, fl
guiGraphics.drawString(font, Component.literal("Treasury: " + state.treasuryBalance + " (net " + state.lastTreasuryNet + ")"), x, y, 4210752, false);
y += 12;
guiGraphics.drawString(font, Component.literal("Projected: " + state.projectedTreasuryBalance), x, y, 4210752, false);
y += 12;
guiGraphics.drawString(font, Component.literal("Heartbeat: " + state.lastHeartbeatTick), x, y, 4210752, false);
y += 12;

y += 16;
guiGraphics.drawString(font, Component.literal("Garrison guidance: " + readableToken(state.garrisonRecommendation)), x, y, 4210752, false);
y += 12;
guiGraphics.drawString(font, Component.literal("Fortification advice: " + readableToken(state.fortificationRecommendation)), x, y, 4210752, false);

y += 16;
guiGraphics.drawString(font, Component.literal("Incidents:"), x, y, 4210752, false);
guiGraphics.drawString(font, Component.literal("Incidents:"), x, y, 0x8B0000, false);
y += 12;
for (String incident : state.incidents) {
guiGraphics.drawString(font, Component.literal("- " + incident), x, y, 4210752, false);
guiGraphics.drawString(font, Component.literal("- " + incident.replace('_', ' ')), x, y, 0xCC3300, false);
y += 10;
}
if (state.incidents.isEmpty()) {
guiGraphics.drawString(font, Component.literal("- none"), x, y, 4210752, false);
y += 10;
}

y += 6;
guiGraphics.drawString(font, Component.literal("Recommendations:"), x, y, 4210752, false);
guiGraphics.drawString(font, Component.literal("Governor says:"), x, y, 0x205020, false);
y += 12;
for (String recommendation : state.recommendations) {
guiGraphics.drawString(font, Component.literal("- " + recommendation), x, y, 4210752, false);
y += 10;
}
if (state.recommendations.isEmpty()) {
guiGraphics.drawString(font, Component.literal("- none"), x, y, 4210752, false);
List<String> advisoryLines = state.lastHeartbeatTick == 0
? List.of(BannerModGovernorAdvisory.firstContactHint())
: BannerModGovernorAdvisory.buildAdvisoryLines(state.incidents, state.recommendations);
for (String line : advisoryLines) {
for (String wrapped : wrapText(line, 220)) {
guiGraphics.drawString(font, Component.literal(wrapped), x, y, 0x305030, false);
y += 10;
}
}

y += 6;
guiGraphics.drawString(font, Component.literal(BannerModGovernorAdvisory.autoManageTip(state.autoManage)), x, y, 0x444444, false);

guiGraphics.drawString(font, Component.literal("garrison priority: " + BannerModGovernorPolicy.GARRISON_PRIORITY.valueLabel(state.garrisonPriority)), leftPos + 10, topPos + 92, 4210752, false);
guiGraphics.drawString(font, Component.literal("fortification priority: " + BannerModGovernorPolicy.FORTIFICATION_PRIORITY.valueLabel(state.fortificationPriority)), leftPos + 10, topPos + 116, 4210752, false);
guiGraphics.drawString(font, Component.literal("tax pressure: " + BannerModGovernorPolicy.TAX_PRESSURE.valueLabel(state.taxPressure)), leftPos + 10, topPos + 140, 4210752, false);
Expand All @@ -130,18 +156,41 @@ public static void applyUpdate(UUID recruitId,
int treasuryBalance,
int lastTreasuryNet,
int projectedTreasuryBalance,
boolean autoManage,
List<String> incidents,
List<String> recommendations) {
latestState = new GovernorViewState(recruitId, settlementStatus, citizenCount, taxesDue, taxesCollected, lastHeartbeatTick,
garrisonRecommendation, fortificationRecommendation, garrisonPriority, fortificationPriority, taxPressure,
treasuryBalance, lastTreasuryNet, projectedTreasuryBalance,
autoManage,
new ArrayList<>(incidents), new ArrayList<>(recommendations));
}

private static String readableToken(String token) {
return token == null || token.isBlank() ? "none" : token.replace('_', ' ');
}

private static List<String> wrapText(String text, int maxPixelWidth) {
List<String> lines = new ArrayList<>();
if (text == null || text.isBlank()) {
return lines;
}
int charsPerLine = Math.max(10, maxPixelWidth / 6);
String[] words = text.split(" ");
StringBuilder current = new StringBuilder();
for (String word : words) {
if (current.length() + word.length() + 1 > charsPerLine) {
lines.add(current.toString().trim());
current = new StringBuilder();
}
current.append(word).append(' ');
}
if (!current.toString().isBlank()) {
lines.add(current.toString().trim());
}
return lines;
}

private record GovernorViewState(UUID recruitId,
String settlementStatus,
int citizenCount,
Expand All @@ -156,13 +205,15 @@ private record GovernorViewState(UUID recruitId,
int treasuryBalance,
int lastTreasuryNet,
int projectedTreasuryBalance,
boolean autoManage,
List<String> incidents,
List<String> recommendations) {
private static GovernorViewState empty() {
return new GovernorViewState(new UUID(0L, 0L), "unknown", 0, 0, 0, 0L,
"hold_course", "hold_course",
BannerModGovernorPolicy.DEFAULT_VALUE, BannerModGovernorPolicy.DEFAULT_VALUE, BannerModGovernorPolicy.DEFAULT_VALUE,
0, 0, 0,
false,
List.of(), List.of());
}
}
Expand Down
Loading