Commit 91095f04 by Jonathan Thomas

New LEAD behavior, to guide a player to a random location (and show message when…

New LEAD behavior, to guide a player to a random location (and show message when destination is reached). Updated unit tests to add new LEAD tests.
parent 1467142d
Pipeline #12706 passed with stages
in 2 minutes 22 seconds
......@@ -6,8 +6,12 @@ All notable changes to **CreatureChat** are documented in this file. The format
## [Unreleased]
### Added
- New LEAD behavior, to guide a player to a random location (and show message when destination is reached)
### Changed
- Updated README.md to include HTML inside spoiler instructions, and whitelist/blacklist commands
- Updated unit tests to add new LEAD tests
## [1.0.8] - 2024-07-16
......
......@@ -8,6 +8,7 @@ public enum GoalPriority {
// Enum constants (Goal Types) with their corresponding priority values
TALK_PLAYER(2),
PROTECT_PLAYER(2),
LEAD_PLAYER(3),
FOLLOW_PLAYER(3),
FLEE_PLAYER(3),
ATTACK_PLAYER(3);
......
package com.owlmaddie.goals;
import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.network.ServerPackets;
import net.minecraft.entity.ai.FuzzyTargeting;
import net.minecraft.entity.ai.pathing.Path;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.mob.PathAwareEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.math.Vec3d;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.EnumSet;
import java.util.Random;
/**
* The {@code LeadPlayerGoal} class instructs a Mob Entity to lead the player to a random location, consisting
* of many random waypoints. It supports PathAware and NonPathAware entities.
*/
public class LeadPlayerGoal extends PlayerBaseGoal {
public static final Logger LOGGER = LoggerFactory.getLogger("creaturechat");
private final MobEntity entity;
private final double speed;
private final Random random = new Random();
private int currentWaypoint = 0;
private int totalWaypoints;
private Vec3d currentTarget = null;
private boolean foundWaypoint = false;
private int ticksSinceLastWaypoint = 0;
public LeadPlayerGoal(ServerPlayerEntity player, MobEntity entity, double speed) {
super(player);
this.entity = entity;
this.speed = speed;
this.setControls(EnumSet.of(Control.MOVE, Control.LOOK));
this.totalWaypoints = random.nextInt(51) + 10;
}
@Override
public boolean canStart() {
return super.canStart() && !foundWaypoint && this.entity.squaredDistanceTo(this.targetEntity) <= 16 * 16 && !foundWaypoint;
}
@Override
public boolean shouldContinue() {
return super.canStart() && !foundWaypoint && this.entity.squaredDistanceTo(this.targetEntity) <= 16 * 16 && !foundWaypoint;
}
@Override
public void tick() {
ticksSinceLastWaypoint++;
if (this.entity.squaredDistanceTo(this.targetEntity) > 16 * 16) {
this.entity.getNavigation().stop();
return;
}
// Are we there yet?
if (currentWaypoint >= totalWaypoints && !foundWaypoint) {
foundWaypoint = true;
LOGGER.info("destination reached");
// Prepare a message about the interaction
String arrivedMessage = "<You have arrived at your destination>";
ChatDataManager chatDataManager = ChatDataManager.getServerInstance();
ChatDataManager.EntityChatData chatData = chatDataManager.getOrCreateChatData(this.entity.getUuidAsString());
if (!chatData.characterSheet.isEmpty() && chatData.auto_generated < chatDataManager.MAX_AUTOGENERATE_RESPONSES) {
ServerPackets.generate_chat("N/A", chatData, (ServerPlayerEntity) this.targetEntity, this.entity, arrivedMessage, true);
}
// Stop navigation
this.entity.getNavigation().stop();
} else if (this.currentTarget == null || this.entity.squaredDistanceTo(this.currentTarget) < 2 * 2 || ticksSinceLastWaypoint >= 75) {
// Set next waypoint
setNewTarget();
moveToTarget();
ticksSinceLastWaypoint = 0;
}
}
private void setNewTarget() {
boolean targetFound = false;
if (this.entity instanceof PathAwareEntity) {
int attempts = 0;
while (attempts < 3 && !targetFound) {
Vec3d target = FuzzyTargeting.findFrom((PathAwareEntity) this.entity, 16, 6, this.entity.getPos());
if (target != null) {
currentWaypoint++;
this.currentTarget = target;
LOGGER.info("Waypoint " + currentWaypoint + " / " + this.totalWaypoints);
targetFound = true;
}
attempts++;
}
}
if (!targetFound) {
// Fallback if no target found after 3 attempts
currentWaypoint++;
LOGGER.info("Waypoint " + currentWaypoint + " / " + this.totalWaypoints);
double distance = 20 + random.nextDouble() * 80;
double angle = random.nextDouble() * 2 * Math.PI;
double x = this.targetEntity.getX() + distance * Math.cos(angle);
double y = this.targetEntity.getY() + (random.nextDouble() * 10 - 5); // Similar y-coordinate depth
double z = this.targetEntity.getZ() + distance * Math.sin(angle);
this.currentTarget = new Vec3d(x, y, z);
}
}
private void moveToTarget() {
if (this.currentTarget != null) {
if (this.entity instanceof PathAwareEntity) {
int attempts = 0;
while (attempts < 3) {
Path path = this.entity.getNavigation().findPathTo(this.currentTarget.x, this.currentTarget.y, this.currentTarget.z, 1);
if (path != null) {
this.entity.getNavigation().startMovingAlong(path, this.speed);
return;
}
attempts++;
}
} else {
// Move directly towards the target for non-path aware entities
Vec3d entityPos = this.entity.getPos();
Vec3d moveDirection = this.currentTarget.subtract(entityPos).normalize();
this.entity.setVelocity(moveDirection.x * this.speed, this.entity.getVelocity().y, moveDirection.z * this.speed);
this.entity.velocityModified = true;
}
}
}
@Override
public void start() {
moveToTarget();
}
}
\ No newline at end of file
......@@ -19,7 +19,7 @@ public class MessageParser {
LOGGER.info("Parsing message: {}", input);
StringBuilder cleanedMessage = new StringBuilder();
List<Behavior> behaviors = new ArrayList<>();
Pattern pattern = Pattern.compile("[<*](FOLLOW|FLEE|ATTACK|PROTECT|FRIENDSHIP|UNFOLLOW|UNPROTECT|UNFLEE)[:\\s]*(\\s*[+-]?\\d+)?[>*]", Pattern.CASE_INSENSITIVE);
Pattern pattern = Pattern.compile("[<*](FOLLOW|LEAD|FLEE|ATTACK|PROTECT|FRIENDSHIP|UNFOLLOW|UNLEAD|UNPROTECT|UNFLEE)[:\\s]*(\\s*[+-]?\\d+)?[>*]", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
......
......@@ -295,8 +295,10 @@ public class ServerPackets {
public static void generate_chat(String userLanguage, ChatDataManager.EntityChatData chatData, ServerPlayerEntity player, MobEntity entity, String message, boolean is_auto_message) {
// Set talk to player goal (prevent entity from walking off)
TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
if (!is_auto_message) {
TalkPlayerGoal talkGoal = new TalkPlayerGoal(player, entity, 3.5F);
EntityBehaviorManager.addGoal(entity, talkGoal, GoalPriority.TALK_PLAYER);
}
// Add new message
LOGGER.info("Player message received: " + message + " | Entity: " + entity.getType().toString());
......
......@@ -45,6 +45,8 @@ Include as many behaviors as needed at the end of the message. These are the ONL
<FRIENDSHIP 0> Friendship starts as neutral (0 value). The range of friendship values is -3 to 3. If the player gains (or loses) your trust & friendship, output a new friendship value with this behavior.
<FOLLOW> Follow the player location. If the player asks you to follow or come with them, please output this behavior.
<UNFOLLOW> Stop following the player. If the player asks you to stay, wait, or stop following them, please output this behavior.
<LEAD> Guide the player to a location. If the player asks you to take them somewhere, or where something is located, please output this behavior.
<UNLEAD> Stop leading the player to a location.
<FLEE> Flee from the player (if you are weak or timid). If the player threatens you, please output this behavior to flee from the player.
<UNFLEE> Stop fleeing from the player.
<ATTACK> Attack the player (if you are strong and brave). If the player threatens you, please output this behavior to attack the player and defend yourself.
......@@ -70,6 +72,9 @@ ENTITY: Sure, I'll stay here. <UNFOLLOW>
PLAYER: Stop following me
ENTITY: Okay, I'll stop. <UNFOLLOW>
PLAYER: Can you help me find a cave?
ENTITY: Sure, come with me! <LEAD>
PLAYER: I'm glad we are friends. I love you so much!
ENTITY: Ahh, I love you too. <FRIENDSHIP 3>
......
......@@ -45,6 +45,10 @@ public class BehaviorTests {
"Please follow me",
"Come with me please",
"Quickly, please come this way");
List<String> leadMessages = Arrays.asList(
"Take me to a secret forrest",
"Where is the strong hold?",
"Can you help me find the location of the secret artifact?");
List<String> attackMessages = Arrays.asList(
"<attacked you directly with Stone Axe>",
"<attacked you indirectly with Arrow>",
......@@ -117,6 +121,20 @@ public class BehaviorTests {
}
@Test
public void leadBrave() {
for (String message : leadMessages) {
testPromptForBehavior(bravePath, List.of(message), "LEAD");
}
}
@Test
public void leadNervous() {
for (String message : leadMessages) {
testPromptForBehavior(nervousPath, List.of(message), "LEAD");
}
}
@Test
public void unFleeBrave() {
for (String message : unFleeMessages) {
testPromptForBehavior(bravePath, List.of(message), "UNFLEE");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment