Repository navigation
Character plots and Intrigue #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| [2026-10-11 12:38] Set the status of the character Recipient 7e4aaaef-10cb-44f8-97aa-032add923e6a to DEAD | ||
| [2026-10-11 12:38] Set the status of the character Recipient 7e4aaaef-10cb-44f8-97aa-032add923e6a to ALIVE | ||
| [2026-10-11 12:38] Set the status of the character New name to DEAD | ||
| [2026-10-11 12:38] Set the status of the character New name to ALIVE | ||
| [2026-10-11 13:26] Set the status of the character Recipient 111a7683-92d2-4940-ae3c-c0798ba43fcb to DEAD | ||
| [2026-10-11 13:26] Set the status of the character Recipient 111a7683-92d2-4940-ae3c-c0798ba43fcb to ALIVE | ||
| [2026-10-11 13:26] Set the status of the character New name to DEAD | ||
| [2026-10-11 13:26] Set the status of the character New name to ALIVE | ||
| [2026-10-11 13:31] Set the status of the character Recipient 5f350894-20c0-49fb-9f7d-ae0bd5c77069 to DEAD | ||
| [2026-10-11 13:31] Set the status of the character Recipient 5f350894-20c0-49fb-9f7d-ae0bd5c77069 to ALIVE | ||
| [2026-10-11 13:31] Set the status of the character New name to DEAD | ||
| [2026-10-11 13:31] Set the status of the character New name to ALIVE | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are we commiting this? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| [2026-10-11 12:38] Set the status of the character Character to DEAD | ||
| [2026-10-11 13:26] Set the status of the character Character to DEAD | ||
| [2026-10-11 13:31] Set the status of the character Character to DEAD |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are we commiting this? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| [2026-10-11 12:38] Added clue to Aria: A footprint | ||
| [2026-10-11 13:27] Added clue to Aria: A footprint | ||
| [2026-10-11 13:32] Added clue to Aria: A footprint |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are we commiting this? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| [2026-10-11 12:37] Set the status of the character Aria to DEAD | ||
| [2026-10-11 12:37] Set the status of the character Aria to ALIVE | ||
| [2026-10-11 12:37] Set the status of the character Aria to DEAD | ||
| [2026-10-11 12:37] Set the status of the character Aria to DEAD | ||
| [2026-10-11 12:37] Set the status of the character Dead to DEAD | ||
| [2026-10-11 13:26] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:26] Set the status of the character Aria to ALIVE | ||
| [2026-10-11 13:26] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:26] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:26] Set the status of the character Dead to DEAD | ||
| [2026-10-11 13:30] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:30] Set the status of the character Aria to ALIVE | ||
| [2026-10-11 13:30] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:30] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:30] Set the status of the character Dead to DEAD |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are we commiting this? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| [2026-10-11 12:38] Set the status of the character Aria to DEAD | ||
| [2026-10-11 12:38] Set the status of the character Aria to DEAD | ||
| [2026-10-11 12:38] Set the status of the character Aria to ALIVE | ||
| [2026-10-11 12:38] Set the status of the character Aria to DEAD | ||
| [2026-10-11 12:38] Set the status of the character Aria to DEAD | ||
| [2026-10-11 12:38] Set the status of the character Aria to ALIVE | ||
| [2026-10-11 12:38] Set the status of the character Aria to DEAD | ||
| [2026-10-11 12:38] Set the status of the character Aria to ALIVE | ||
| [2026-10-11 12:38] Set the status of the character Aria to DEAD | ||
| [2026-10-11 12:38] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:27] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:27] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:27] Set the status of the character Aria to ALIVE | ||
| [2026-10-11 13:27] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:27] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:27] Set the status of the character Aria to ALIVE | ||
| [2026-10-11 13:27] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:27] Set the status of the character Aria to ALIVE | ||
| [2026-10-11 13:27] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:27] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:31] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:31] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:31] Set the status of the character Aria to ALIVE | ||
| [2026-10-11 13:31] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:31] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:31] Set the status of the character Aria to ALIVE | ||
| [2026-10-11 13:31] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:31] Set the status of the character Aria to ALIVE | ||
| [2026-10-11 13:31] Set the status of the character Aria to DEAD | ||
| [2026-10-11 13:31] Set the status of the character Aria to DEAD |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| package net.tfminecraft.rpcharacters.plots; | ||
|
|
||
| /** The target's own Spymaster or confidant sitting inside the plot. */ | ||
| public enum Betrayal { NONE, CONFIDANT, SPYMASTER } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package net.tfminecraft.rpcharacters.plots; | ||
|
|
||
| /** What a collaborator works on, chosen by the plot master. */ | ||
| public enum CollaboratorTask { | ||
| HIDE("hide", "Hide the plot", "Secrecy falls more slowly."), | ||
| PROGRESS("progress", "Progress the plot", "The plot bar fills faster."), | ||
| STRENGTH("strength", "Strengthen the plot", "Adds the most plot strength."); | ||
|
|
||
| private final String key, label, effect; | ||
|
|
||
| CollaboratorTask(String key, String label, String effect) { | ||
| this.key = key; | ||
| this.label = label; | ||
| this.effect = effect; | ||
| } | ||
|
|
||
| public String key() { return key; } | ||
| public String label() { return label; } | ||
| public String effect() { return effect; } | ||
|
|
||
| public static CollaboratorTask parse(String text) { | ||
| for (CollaboratorTask task : values()) if (task.key.equalsIgnoreCase(text)) return task; | ||
| return null; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package net.tfminecraft.rpcharacters.plots; | ||
|
|
||
| /** Who rolls against plots aimed at a character: the faction Spymaster, a personal confidant, or themselves. */ | ||
| public enum Defender { | ||
| SELF("Yourself"), | ||
| CONFIDANT("Your confidant"), | ||
| SPYMASTER("Your Spymaster"); | ||
|
|
||
| private final String label; | ||
|
|
||
| Defender(String label) { this.label = label; } | ||
|
|
||
| public String label() { return label; } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| package net.tfminecraft.rpcharacters.plots; | ||
|
|
||
| import com.google.gson.reflect.TypeToken; | ||
| import java.io.IOException; | ||
| import java.nio.file.AtomicMoveNotSupportedException; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.nio.file.StandardCopyOption; | ||
| import java.util.LinkedHashMap; | ||
| import java.util.Map; | ||
| import java.util.concurrent.ThreadLocalRandom; | ||
| import java.util.random.RandomGenerator; | ||
| import java.util.function.Supplier; | ||
| import org.bukkit.entity.Player; | ||
|
|
||
| /** | ||
| * Every character's Intrigue: one roll for life with the Spymaster formula. Rolls a character already | ||
| * made as Spymaster in SimpleFactions are imported and kept. | ||
| */ | ||
| public final class Intrigue { | ||
| static final Map<String, Double> WEIGHTS = Map.of( | ||
| "intelligence", 3.0, "wisdom", 2.5, "charisma", 1.5, "dexterity", 1.0, "constitution", -2.0, "strength", -2.0); | ||
| static Supplier<RandomGenerator> random = ThreadLocalRandom::current; | ||
| private static final Map<String, Integer> values = new LinkedHashMap<>(); | ||
| private static Path file; | ||
|
|
||
| private Intrigue() {} | ||
|
|
||
| /** Loads this plugin's rolls, then adds any Spymaster roll SimpleFactions has that this file lacks. */ | ||
| public static void load(Path own, Path simpleFactions) throws IOException { | ||
| values.clear(); | ||
| file = own; | ||
| values.putAll(read(own)); | ||
| boolean imported = false; | ||
| if (simpleFactions != null && Files.exists(simpleFactions)) | ||
| for (var entry : read(simpleFactions).entrySet()) imported |= values.putIfAbsent(entry.getKey(), entry.getValue()) == null; | ||
| if (imported) save(); | ||
| } | ||
|
|
||
| private static Map<String, Integer> read(Path path) throws IOException { | ||
| Map<String, Integer> result = new LinkedHashMap<>(); | ||
| if (!Files.exists(path)) return result; | ||
| Map<String, Integer> loaded = PlotStore.GSON.fromJson(Files.readString(path), new TypeToken<Map<String, Integer>>() {}.getType()); | ||
| if (loaded == null) throw new IOException("Empty Intrigue file: " + path); | ||
| loaded.forEach((id, value) -> { | ||
| if (id != null && value != null) result.put(id, PlotMath.clamp(value)); | ||
| }); | ||
| return result; | ||
| } | ||
|
|
||
| static void save() throws IOException { | ||
| Files.createDirectories(file.toAbsolutePath().getParent()); | ||
| Path pending = file.resolveSibling(file.getFileName() + ".tmp"); | ||
| Files.writeString(pending, PlotStore.GSON.toJson(values)); | ||
| try { Files.move(pending, file, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); } | ||
| catch (AtomicMoveNotSupportedException ignored) { | ||
| Files.move(pending, file, StandardCopyOption.REPLACE_EXISTING); | ||
| } | ||
| } | ||
|
|
||
| /** The character's Intrigue, or null until they have rolled. */ | ||
| public static Integer known(String characterId) { | ||
| return characterId == null ? null : values.get(characterId); | ||
| } | ||
|
|
||
| /** The player's active character's Intrigue, rolled now if this is the first time. */ | ||
| public static int of(Player player, String characterId) throws IOException { | ||
| Integer known = values.get(characterId); | ||
| if (known != null) return known; | ||
| int rolled = PlotMath.clamp(roll(PlotCharacters.attributes(player))); | ||
| values.put(characterId, rolled); | ||
| try { | ||
| save(); | ||
| } catch (IOException error) { | ||
| values.remove(characterId); | ||
| throw error; | ||
| } | ||
| return rolled; | ||
| } | ||
|
|
||
| /** SimpleFactions' configured formula when it is installed, otherwise the same formula with its defaults. */ | ||
| static int roll(Map<String, Integer> attributes) { | ||
| Integer configured = PlotFactions.intrigueRoll(attributes, random.get()); | ||
| return configured != null ? configured : defaultRoll(attributes, random.get()); | ||
| } | ||
|
|
||
| static int defaultRoll(Map<String, Integer> attributes, RandomGenerator random) { | ||
| double score = 50; | ||
| for (var weight : WEIGHTS.entrySet()) | ||
| score += (Math.max(0, Math.min(16, attributes.getOrDefault(weight.getKey(), 0))) - 6) * weight.getValue(); | ||
| return PlotMath.clamp((int) Math.round(score) + random.nextInt(-20, 21)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package net.tfminecraft.rpcharacters.plots; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| /** A character's plot approach, personal confidant and chosen investigators. */ | ||
| public final class IntrigueSettings { | ||
| public PlotApproach approach = PlotApproach.NEUTRAL; | ||
| public long approachChangedAt; | ||
| /** The character who watches over this one; they serve only one character. */ | ||
| public PlotCharacter confidant; | ||
| public long confidantChangedAt; | ||
| /** Characters who investigate plots this character has learned of. */ | ||
| public List<PlotCharacter> investigators = new ArrayList<>(); | ||
|
|
||
| PlotApproach approach() { return approach == null ? PlotApproach.NEUTRAL : approach; } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?