Remove build artifacts, dev tool configs, unused dependencies, and third-party source dumps. Add proper README, update .gitignore, clean up Makefile.
636 lines
21 KiB
Java
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;
|
|
};
|
|
}
|
|
}
|