Files
TiedUp-/src/main/java/com/tiedup/remake/dialogue/EntityDialogueManager.java
NotEvil f6466360b6 Clean repo for open source release
Remove build artifacts, dev tool configs, unused dependencies,
and third-party source dumps. Add proper README, update .gitignore,
clean up Makefile.
2026-04-12 00:51:22 +02:00

636 lines
21 KiB
Java

package com.tiedup.remake.dialogue;
import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.util.MessageDispatcher;
import java.util.List;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
/**
* Complete dialogue system for EntityDamsel and EntityKidnapper.
*
* Phase 14.3: Centralized NPC dialogue management
*
* Features:
* - Multiple dialogue variants per action category
* - Integration with GagTalkManager for gagged NPCs
* - Formatted messages with entity name
* - Action messages vs speech messages
* - Radius-based broadcasting
*/
public class EntityDialogueManager {
// ========================================
// DIALOGUE CATEGORIES
// ========================================
/**
* Dialogue categories for different NPC actions.
*/
public enum DialogueCategory {
// === KIDNAPPER CAPTURE SEQUENCE ===
CAPTURE_START, // When kidnapper starts chasing
CAPTURE_APPROACHING, // While approaching target
CAPTURE_CHASE, // When pursuing an escaped captive
CAPTURE_TYING, // While tying up target
CAPTURE_TIED, // After target is tied
CAPTURE_GAGGING, // While gagging target
CAPTURE_GAGGED, // After target is gagged
CAPTURE_ENSLAVED, // After enslaving target
CAPTURE_ESCAPE, // When captive escapes
// === SLAVE MANAGEMENT ===
SLAVE_TALK_RESPONSE, // When slave tries to talk
SLAVE_STRUGGLE, // When slave struggles
SLAVE_TRANSPORT, // When transporting slave
SLAVE_ARRIVE_PRISON, // When arriving at prison
SLAVE_TIED_TO_POLE, // When tying slave to pole
PUNISH, // When punishing a recaptured or misbehaving captive
// === SALE SYSTEM ===
SALE_WAITING, // Waiting for buyer
SALE_ANNOUNCE, // Announcing sale
SALE_OFFER, // Offering to sell to a passing player (needs %s for player name and %s for price)
SALE_COMPLETE, // Sale completed
SALE_ABANDONED, // When kidnapper abandons captive (solo mode)
SALE_KEPT, // When kidnapper decides to keep captive (solo mode)
// === JOB SYSTEM ===
JOB_ASSIGNED, // Giving job to slave
JOB_HURRY, // Urging slave to hurry
JOB_COMPLETE, // Job completed successfully
JOB_FAILED, // Job failed
JOB_LAST_CHANCE, // Warning before killing
JOB_KILL, // Killing the slave for failure
// === COMBAT/ATTACK ===
ATTACKED_RESPONSE, // When attacked by someone
ATTACK_SLAVE, // When slave attacks
// === DAMSEL SPECIFIC ===
DAMSEL_PANIC, // When damsel is scared
DAMSEL_FLEE, // When damsel flees
DAMSEL_CAPTURED, // When damsel is captured
DAMSEL_FREED, // When damsel is freed
DAMSEL_GREETING, // Greeting nearby players
DAMSEL_IDLE, // Random idle chatter
DAMSEL_CALL_FOR_HELP, // When tied damsel sees a player nearby (needs %s for player name)
// === GENERAL ===
FREED, // When freeing someone
GOODBYE, // Saying goodbye
GET_OUT, // Telling someone to leave
GENERIC_THREAT, // Generic threatening line
GENERIC_TAUNT, // Generic taunting line
// === PERSONALITY SYSTEM (Phase E) ===
COMMAND_ACCEPT, // When NPC accepts a command
COMMAND_REFUSE, // When NPC refuses a command
COMMAND_HESITATE, // When NPC hesitates before command
// === DISCIPLINE SYSTEM (Training V2) ===
PRAISE_RESPONSE, // When NPC is praised
SCOLD_RESPONSE, // When NPC is scolded
THREATEN_RESPONSE, // When NPC is threatened
NEED_HUNGRY, // When NPC is hungry
NEED_TIRED, // When NPC is tired
NEED_UNCOMFORTABLE, // When NPC is uncomfortable
NEED_DIGNITY_LOW, // When NPC has low dignity
PERSONALITY_HINT_TIMID, // Hint for TIMID personality
PERSONALITY_HINT_GENTLE, // Hint for GENTLE personality
PERSONALITY_HINT_SUBMISSIVE, // Hint for SUBMISSIVE personality
PERSONALITY_HINT_CALM, // Hint for CALM personality
PERSONALITY_HINT_CURIOUS, // Hint for CURIOUS personality
PERSONALITY_HINT_PROUD, // Hint for PROUD personality
PERSONALITY_HINT_FIERCE, // Hint for FIERCE personality
PERSONALITY_HINT_DEFIANT, // Hint for DEFIANT personality
PERSONALITY_HINT_PLAYFUL, // Hint for PLAYFUL personality
PERSONALITY_HINT_MASOCHIST, // Hint for MASOCHIST personality
PERSONALITY_HINT_SADIST, // Hint for SADIST personality (kidnappers)
}
// ========================================
// DIALOGUE RETRIEVAL (Data-Driven Only)
// ========================================
/**
* Get random dialogue for a category using the data-driven system.
* All dialogues are now loaded from JSON files.
*
* @param category The dialogue category
* @return Dialogue text, or a fallback message if not found
*/
public static String getDialogue(DialogueCategory category) {
// Use data-driven system with default context
if (com.tiedup.remake.dialogue.DialogueManager.isInitialized()) {
String dialogueId =
com.tiedup.remake.dialogue.DialogueBridge.categoryToDialogueId(
category
);
com.tiedup.remake.dialogue.DialogueContext defaultContext =
com.tiedup.remake.dialogue.DialogueContext.builder()
.personality(
com.tiedup.remake.personality.PersonalityType.CALM
)
.mood(50)
.build();
String text =
com.tiedup.remake.dialogue.DialogueManager.getDialogue(
dialogueId,
defaultContext
);
if (text != null) {
return text;
}
}
// Fallback for uninitialized system
com.tiedup.remake.core.TiedUpMod.LOGGER.warn(
"[EntityDialogueManager] Data-driven dialogue not found for category: {}",
category.name()
);
return "[" + category.name() + "]";
}
/**
* Get dialogue for a category using the data-driven system.
* Uses entity context for personality-aware dialogue selection.
*
* @param entity The entity speaking
* @param player The player (can be null)
* @param category The dialogue category
* @return Dialogue text
*/
public static String getDialogue(
EntityDamsel entity,
Player player,
DialogueCategory category
) {
// Always use data-driven system
String dataDriven =
com.tiedup.remake.dialogue.DialogueBridge.getDataDrivenDialogue(
entity,
player,
category
);
if (dataDriven != null) {
return dataDriven;
}
// Fallback to category-only lookup (no entity context)
return getDialogue(category);
}
// ========================================
// MESSAGE SENDING METHODS
// ========================================
/**
* Send a dialogue message to a specific player.
* Format: *EntityName* : message
* Uses data-driven dialogue system if available.
*
* @param entity The entity speaking
* @param player The player receiving the message
* @param category The dialogue category
*/
public static void talkTo(
EntityDamsel entity,
Player player,
DialogueCategory category
) {
// Use data-driven dialogue when available
talkTo(entity, player, getDialogue(entity, player, category));
}
/**
* Send a custom message to a specific player.
* Delegates formatting, gag talk, and earplug handling to MessageDispatcher.
*
* @param entity The entity speaking
* @param player The player receiving the message
* @param message The message to send
*/
public static void talkTo(
EntityDamsel entity,
Player player,
String message
) {
if (entity == null || player == null || message == null) return;
if (entity.level().isClientSide()) return;
if (!(player instanceof ServerPlayer)) return;
MessageDispatcher.talkTo(entity, player, message);
}
/**
* Send a dialogue message using a dialogue ID.
* Resolves the ID to actual text via DialogueManager.
*
* @param entity The entity speaking
* @param player The player receiving the message
* @param dialogueId The dialogue ID (e.g., "action.whip")
*/
public static void talkByDialogueId(
EntityDamsel entity,
Player player,
String dialogueId
) {
if (entity == null || player == null || dialogueId == null) return;
if (entity.level().isClientSide()) return;
if (!com.tiedup.remake.dialogue.DialogueManager.isInitialized()) {
talkTo(entity, player, "[Dialogue system not ready]");
return;
}
// Build context and resolve dialogue text
com.tiedup.remake.dialogue.DialogueContext context =
com.tiedup.remake.dialogue.DialogueBridge.buildContext(
entity,
player
);
String text = com.tiedup.remake.dialogue.DialogueManager.getDialogue(
dialogueId,
context
);
if (text == null) {
text = "[Missing: " + dialogueId + "]";
com.tiedup.remake.core.TiedUpMod.LOGGER.warn(
"[EntityDialogueManager] Missing dialogue for ID: {} with personality: {}",
dialogueId,
context.getPersonality()
);
}
talkTo(entity, player, text);
}
/**
* Send an action message to a specific player.
* Format: EntityName action
* Uses data-driven dialogue system if available.
*
* @param entity The entity performing the action
* @param player The player receiving the message
* @param category The dialogue category
*/
public static void actionTo(
EntityDamsel entity,
Player player,
DialogueCategory category
) {
// Use data-driven dialogue when available
actionTo(entity, player, getDialogue(entity, player, category));
}
/**
* Send a custom action message to a specific player.
* Delegates formatting and earplug handling to MessageDispatcher.
*
* @param entity The entity performing the action
* @param player The player receiving the message
* @param action The action description
*/
public static void actionTo(
EntityDamsel entity,
Player player,
String action
) {
if (entity == null || player == null || action == null) return;
if (entity.level().isClientSide()) return;
if (!(player instanceof ServerPlayer)) return;
MessageDispatcher.actionTo(entity, player, action);
}
/**
* Talk to all players within a radius.
* Builds proper context per player for variable substitution.
*
* @param entity The entity speaking
* @param category The dialogue category
* @param radius The radius in blocks
*/
public static void talkToNearby(
EntityDamsel entity,
DialogueCategory category,
int radius
) {
if (entity == null) return;
List<Player> players = entity
.level()
.getEntitiesOfClass(
Player.class,
entity.getBoundingBox().inflate(radius)
);
for (Player player : players) {
// Use the context-aware dialogue lookup for proper {player} substitution
String dialogue = getDialogue(entity, player, category);
talkTo(entity, player, dialogue);
}
}
/**
* Talk to all players within a radius with a custom message.
*
* @param entity The entity speaking
* @param message The message to send
* @param radius The radius in blocks
*/
public static void talkToNearby(
EntityDamsel entity,
String message,
int radius
) {
if (entity == null || message == null) return;
List<Player> players = entity
.level()
.getEntitiesOfClass(
Player.class,
entity.getBoundingBox().inflate(radius)
);
for (Player player : players) {
talkTo(entity, player, message);
}
}
/**
* Send an action message to all players within a radius.
* Builds proper context per player for variable substitution.
*
* @param entity The entity performing the action
* @param category The dialogue category
* @param radius The radius in blocks
*/
public static void actionToNearby(
EntityDamsel entity,
DialogueCategory category,
int radius
) {
if (entity == null) return;
List<Player> players = entity
.level()
.getEntitiesOfClass(
Player.class,
entity.getBoundingBox().inflate(radius)
);
for (Player player : players) {
// Use the context-aware dialogue lookup for proper {player} substitution
String action = getDialogue(entity, player, category);
actionTo(entity, player, action);
}
}
/**
* Send a custom action message to all players within a radius.
*
* @param entity The entity performing the action
* @param action The action description
* @param radius The radius in blocks
*/
public static void actionToNearby(
EntityDamsel entity,
String action,
int radius
) {
if (entity == null || action == null) return;
List<Player> players = entity
.level()
.getEntitiesOfClass(
Player.class,
entity.getBoundingBox().inflate(radius)
);
for (Player player : players) {
actionTo(entity, player, action);
}
}
// ========================================
// CONVENIENCE METHODS
// ========================================
/**
* Make entity say something to their target (if player).
*/
public static void talkToTarget(
EntityDamsel entity,
LivingEntity target,
DialogueCategory category
) {
if (target instanceof Player player) {
talkTo(entity, player, category);
}
}
/**
* Make entity say a custom message to their target (if player).
*/
public static void talkToTarget(
EntityDamsel entity,
LivingEntity target,
String message
) {
if (target instanceof Player player) {
talkTo(entity, player, message);
}
}
/**
* Make entity perform action to their target (if player).
*/
public static void actionToTarget(
EntityDamsel entity,
LivingEntity target,
DialogueCategory category
) {
if (target instanceof Player player) {
actionTo(entity, player, category);
}
}
/**
* Make entity perform custom action to their target (if player).
*/
public static void actionToTarget(
EntityDamsel entity,
LivingEntity target,
String action
) {
if (target instanceof Player player) {
actionTo(entity, player, action);
}
}
// ========================================
// COMPOUND MESSAGES
// ========================================
/**
* Get a dialogue for job assignment with item name.
*/
public static String getJobAssignmentDialogue(String itemName) {
return getDialogue(DialogueCategory.JOB_ASSIGNED) + itemName;
}
/**
* Get a call for help dialogue with player name.
*
* @deprecated Use {@link #callForHelp(EntityDamsel, Player)} which builds proper context.
*/
@Deprecated
public static String getCallForHelpDialogue(String playerName) {
// Legacy fallback - manually substitute {player}
String text = getDialogue(DialogueCategory.DAMSEL_CALL_FOR_HELP);
return text.replace("{player}", playerName);
}
/**
* Get a sale offer dialogue with player name and price.
* The {player} placeholder is the buyer, {target} is the price.
*
* @param playerName The buyer's name
* @param price The price as a display string (e.g., "50 gold")
* @return Formatted dialogue text
*/
public static String getSaleOfferDialogue(String playerName, String price) {
// Substitute placeholders manually since we don't have full context
String text = getDialogue(DialogueCategory.SALE_OFFER);
text = text.replace("{player}", playerName);
text = text.replace("{target}", price); // {target} is used for price in sale dialogues
return text;
}
/**
* Make a tied damsel call for help to a nearby player.
* Pre-conditions (tied, slave, not gagged) should be checked by caller.
*
* @param damsel The damsel calling for help
* @param targetPlayer The player to call for help
*/
public static void callForHelp(EntityDamsel damsel, Player targetPlayer) {
if (damsel == null || targetPlayer == null) return;
// Use context-aware dialogue for proper {player} substitution
String message = getDialogue(
damsel,
targetPlayer,
DialogueCategory.DAMSEL_CALL_FOR_HELP
);
talkTo(damsel, targetPlayer, message);
}
/**
* Get a sale announcement with coordinates.
*/
public static String getSaleAnnouncement(
EntityDamsel entity,
String slaveName
) {
return String.format(
"%s is selling %s at coordinates: %d, %d, %d",
entity.getNpcName(),
slaveName,
(int) entity.getX(),
(int) entity.getY(),
(int) entity.getZ()
);
}
// ========================================
// PERSONALITY SYSTEM HELPERS
// ========================================
/**
* Get personality hint dialogue based on personality type name.
*
* @param personalityTypeName The name of the personality type (from PersonalityType enum)
* @return The hint dialogue category, or null if unknown
*/
public static DialogueCategory getPersonalityHintCategory(
String personalityTypeName
) {
return switch (personalityTypeName.toUpperCase()) {
case "TIMID" -> DialogueCategory.PERSONALITY_HINT_TIMID;
case "GENTLE" -> DialogueCategory.PERSONALITY_HINT_GENTLE;
case "SUBMISSIVE" -> DialogueCategory.PERSONALITY_HINT_SUBMISSIVE;
case "CALM" -> DialogueCategory.PERSONALITY_HINT_CALM;
case "CURIOUS" -> DialogueCategory.PERSONALITY_HINT_CURIOUS;
case "PROUD" -> DialogueCategory.PERSONALITY_HINT_PROUD;
case "FIERCE" -> DialogueCategory.PERSONALITY_HINT_FIERCE;
case "DEFIANT" -> DialogueCategory.PERSONALITY_HINT_DEFIANT;
case "PLAYFUL" -> DialogueCategory.PERSONALITY_HINT_PLAYFUL;
case "MASOCHIST" -> DialogueCategory.PERSONALITY_HINT_MASOCHIST;
case "SADIST" -> DialogueCategory.PERSONALITY_HINT_SADIST;
default -> null;
};
}
/**
* Show a personality hint action to a player.
* Used when discovery level is GLIMPSE to give behavioral cues.
*
* @param entity The entity with the personality
* @param player The player to show the hint to
* @param personalityTypeName The personality type name
*/
public static void showPersonalityHint(
EntityDamsel entity,
Player player,
String personalityTypeName
) {
DialogueCategory hintCategory = getPersonalityHintCategory(
personalityTypeName
);
if (hintCategory != null) {
actionTo(entity, player, hintCategory);
}
}
/**
* Get dialogue for command response based on acceptance.
*
* @param accepted true if command was accepted, false if refused
* @param hesitated true if the NPC hesitated before responding
* @return The appropriate dialogue
*/
public static String getCommandResponseDialogue(
boolean accepted,
boolean hesitated
) {
if (!accepted) {
return getDialogue(DialogueCategory.COMMAND_REFUSE);
}
if (hesitated) {
return getDialogue(DialogueCategory.COMMAND_HESITATE);
}
return getDialogue(DialogueCategory.COMMAND_ACCEPT);
}
/**
* Get dialogue for a specific need when it's critically low.
*
* @param needType The type of need ("hunger", "comfort", "rest", "dignity")
* @return The appropriate dialogue category, or null if unknown
*/
public static DialogueCategory getNeedDialogueCategory(String needType) {
return switch (needType.toLowerCase()) {
case "hunger" -> DialogueCategory.NEED_HUNGRY;
case "rest" -> DialogueCategory.NEED_TIRED;
case "comfort" -> DialogueCategory.NEED_UNCOMFORTABLE;
case "dignity" -> DialogueCategory.NEED_DIGNITY_LOW;
default -> null;
};
}
}