Commit ac6d193a by Jonathan Thomas

Massive refactor to Goals and Behaviors:

- New PROTECT behavior
- Native ATTACK abilities
- Improved FOLLOW (with teleportation)
- Improved FLEE (more random, more reliable)
parent 05c312a8
Pipeline #12561 passed with stages
in 2 minutes 17 seconds
...@@ -12,8 +12,8 @@ import com.owlmaddie.message.MessageParser; ...@@ -12,8 +12,8 @@ import com.owlmaddie.message.MessageParser;
import com.owlmaddie.message.ParsedMessage; import com.owlmaddie.message.ParsedMessage;
import com.owlmaddie.network.ServerPackets; import com.owlmaddie.network.ServerPackets;
import com.owlmaddie.utils.LivingEntityInterface; import com.owlmaddie.utils.LivingEntityInterface;
import com.owlmaddie.utils.ServerEntityFinder;
import com.owlmaddie.utils.Randomizer; import com.owlmaddie.utils.Randomizer;
import com.owlmaddie.utils.ServerEntityFinder;
import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.mob.MobEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
...@@ -276,7 +276,7 @@ public class ChatDataManager { ...@@ -276,7 +276,7 @@ public class ChatDataManager {
EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class); EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class);
} else if (behavior.getName().equals("FLEE")) { } else if (behavior.getName().equals("FLEE")) {
float fleeDistance = 400F; // 20 blocks squared float fleeDistance = 40F;
FleePlayerGoal fleeGoal = new FleePlayerGoal(player, entity, entitySpeedFast, fleeDistance); FleePlayerGoal fleeGoal = new FleePlayerGoal(player, entity, entitySpeedFast, fleeDistance);
EntityBehaviorManager.removeGoal(entity, TalkPlayerGoal.class); EntityBehaviorManager.removeGoal(entity, TalkPlayerGoal.class);
EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class); EntityBehaviorManager.removeGoal(entity, FollowPlayerGoal.class);
...@@ -290,6 +290,13 @@ public class ChatDataManager { ...@@ -290,6 +290,13 @@ public class ChatDataManager {
EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class); EntityBehaviorManager.removeGoal(entity, FleePlayerGoal.class);
EntityBehaviorManager.addGoal(entity, attackGoal, GoalPriority.ATTACK_PLAYER); EntityBehaviorManager.addGoal(entity, attackGoal, GoalPriority.ATTACK_PLAYER);
} else if (behavior.getName().equals("PROTECT")) {
ProtectPlayerGoal protectGoal = new ProtectPlayerGoal(player, entity, 1.0);
EntityBehaviorManager.addGoal(entity, protectGoal, GoalPriority.PROTECT_PLAYER);
} else if (behavior.getName().equals("UNPROTECT")) {
EntityBehaviorManager.removeGoal(entity, ProtectPlayerGoal.class);
} else if (behavior.getName().equals("FRIENDSHIP")) { } else if (behavior.getName().equals("FRIENDSHIP")) {
int new_friendship = Math.max(-3, Math.min(3, behavior.getArgument())); int new_friendship = Math.max(-3, Math.min(3, behavior.getArgument()));
if (new_friendship > 0) { if (new_friendship > 0) {
......
package com.owlmaddie.goals; package com.owlmaddie.goals;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.ai.RangedAttackMob; import net.minecraft.entity.ai.RangedAttackMob;
import net.minecraft.entity.ai.goal.Goal; import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.entity.ai.pathing.EntityNavigation;
import net.minecraft.entity.mob.Angerable; import net.minecraft.entity.mob.Angerable;
import net.minecraft.entity.mob.HostileEntity; import net.minecraft.entity.mob.HostileEntity;
import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.mob.MobEntity;
import net.minecraft.particle.ParticleTypes; import net.minecraft.particle.ParticleTypes;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundEvents; import net.minecraft.sound.SoundEvents;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
...@@ -15,73 +14,91 @@ import net.minecraft.util.math.Vec3d; ...@@ -15,73 +14,91 @@ import net.minecraft.util.math.Vec3d;
import java.util.EnumSet; import java.util.EnumSet;
/** /**
* The {@code AttackPlayerGoal} class instructs a Mob Entity to show aggression towards the current player. * The {@code AttackPlayerGoal} class instructs a Mob Entity to show aggression towards a target Entity.
* For passive entities like chickens, damage is simulated with particles. But all MobEntity instances can damage * For passive entities like chickens (or hostile entities in creative mode), damage is simulated with particles.
* the player.
*/ */
public class AttackPlayerGoal extends Goal { public class AttackPlayerGoal extends Goal {
private final MobEntity entity; protected final MobEntity attackerEntity;
private ServerPlayerEntity targetPlayer; protected LivingEntity targetEntity;
private final EntityNavigation navigation; protected final double speed;
private final double speed; protected enum EntityState { MOVING_TOWARDS_PLAYER, IDLE, CHARGING, ATTACKING, LEAPING }
enum EntityState { MOVING_TOWARDS_PLAYER, IDLE, CHARGING, ATTACKING, LEAPING } protected EntityState currentState = EntityState.IDLE;
private EntityState currentState = EntityState.IDLE; protected int cooldownTimer = 0;
private int cooldownTimer = 0; protected final int CHARGE_TIME = 15; // Time before leaping / attacking
private final int CHARGE_TIME = 15; // Time before leaping / attacking protected final double MOVE_DISTANCE = 200D; // 20 blocks away
private final double MOVE_DISTANCE = 200D; // 20 blocks away protected final double CHARGE_DISTANCE = 25D; // 5 blocks away
private final double CHARGE_DISTANCE = 25D; // 5 blocks away protected final double ATTACK_DISTANCE = 4D; // 2 blocks away
private final double ATTACK_DISTANCE = 4D; // 2 blocks away
public AttackPlayerGoal(LivingEntity targetEntity, MobEntity attackerEntity, double speed) {
public AttackPlayerGoal(ServerPlayerEntity player, MobEntity entity, double speed) { this.targetEntity = targetEntity;
this.targetPlayer = player; this.attackerEntity = attackerEntity;
this.entity = entity;
this.speed = speed; this.speed = speed;
this.navigation = entity.getNavigation(); this.setControls(EnumSet.of(Control.MOVE, Control.LOOK, Control.TARGET));
this.setControls(EnumSet.of(Control.MOVE, Control.LOOK));
// Set the target
if (this.targetEntity != null) {
this.attackerEntity.setTarget(this.targetEntity);
}
} }
@Override @Override
public boolean canStart() { public boolean canStart() {
// Can start showing aggression if the player is within a certain range. return isGoalActive();
return this.entity.squaredDistanceTo(this.targetPlayer) < MOVE_DISTANCE;
} }
@Override @Override
public boolean shouldContinue() { public boolean shouldContinue() {
// Continue showing aggression as long as the player is alive and within range. return isGoalActive();
return this.targetPlayer.isAlive() && this.entity.squaredDistanceTo(this.targetPlayer) < MOVE_DISTANCE;
} }
@Override @Override
public void stop() { public void stop() {
} }
private boolean isGoalActive() {
if (this.targetEntity == null || (this.targetEntity != null && !this.targetEntity.isAlive())) {
return false;
}
// Is nearby to target
boolean isNearby = this.attackerEntity.squaredDistanceTo(this.targetEntity) < MOVE_DISTANCE;
// Check if the attacker is nearby and no native attacks
boolean isNearbyAndNoNativeAttacks = isNearby && !hasNativeAttacks();
// Check if it has native attacks but can't target (e.g., creative mode)
LivingEntity livingAttackerEntity = this.attackerEntity;
boolean hasNativeAttacksButCannotTarget = isNearby && hasNativeAttacks() && !livingAttackerEntity.canTarget(this.targetEntity);
// Return true if either condition is met
return isNearbyAndNoNativeAttacks || hasNativeAttacksButCannotTarget;
}
private boolean hasNativeAttacks() {
// Does this entity have native attacks
return this.attackerEntity instanceof HostileEntity || this.attackerEntity instanceof Angerable || this.attackerEntity instanceof RangedAttackMob;
}
private void performAttack() { private void performAttack() {
// Check if the entity is a type that is capable of attacking // For passive entities (or hostile in creative mode), apply minimal damage to simulate a 'leap' / 'melee' attack
if (this.entity instanceof HostileEntity || this.entity instanceof Angerable || this.entity instanceof RangedAttackMob) { this.targetEntity.damage(this.attackerEntity.getDamageSources().generic(), 1.0F);
// Entity attacks the player
this.entity.tryAttack(this.targetPlayer);
} else {
// For passive entities, apply minimal damage to simulate a 'leap' attack
this.targetPlayer.damage(this.entity.getDamageSources().generic(), 1.0F);
// Play damage sound // Play damage sound
this.targetPlayer.playSound(SoundEvents.ENTITY_PLAYER_HURT, 1F, 1F); this.attackerEntity.playSound(SoundEvents.ENTITY_PLAYER_HURT, 1F, 1F);
// Spawn red particles to simulate 'injury' // Spawn red particles to simulate 'injury'
((ServerWorld) this.entity.getWorld()).spawnParticles(ParticleTypes.DAMAGE_INDICATOR, ((ServerWorld) this.attackerEntity.getWorld()).spawnParticles(ParticleTypes.DAMAGE_INDICATOR,
this.targetPlayer.getX(), this.targetEntity.getX(),
this.targetPlayer.getBodyY(0.5D), this.targetEntity.getBodyY(0.5D),
this.targetPlayer.getZ(), this.targetEntity.getZ(),
10, // number of particles 10, // number of particles
0.1, 0.1, 0.1, 0.2); // speed and randomness 0.1, 0.1, 0.1, 0.2); // speed and randomness
} }
}
@Override @Override
public void tick() { public void tick() {
double squaredDistanceToPlayer = this.entity.squaredDistanceTo(this.targetPlayer); double squaredDistanceToPlayer = this.attackerEntity.squaredDistanceTo(this.targetEntity);
this.entity.getLookControl().lookAt(this.targetPlayer, 30.0F, 30.0F); // Entity faces the player this.attackerEntity.getLookControl().lookAt(this.targetEntity, 30.0F, 30.0F);
// State transitions and actions // State transitions and actions
switch (currentState) { switch (currentState) {
...@@ -97,7 +114,7 @@ public class AttackPlayerGoal extends Goal { ...@@ -97,7 +114,7 @@ public class AttackPlayerGoal extends Goal {
break; break;
case MOVING_TOWARDS_PLAYER: case MOVING_TOWARDS_PLAYER:
this.entity.getNavigation().startMovingTo(this.targetPlayer, this.speed); this.attackerEntity.getNavigation().startMovingTo(this.targetEntity, this.speed);
if (squaredDistanceToPlayer < CHARGE_DISTANCE) { if (squaredDistanceToPlayer < CHARGE_DISTANCE) {
currentState = EntityState.CHARGING; currentState = EntityState.CHARGING;
} else { } else {
...@@ -106,7 +123,7 @@ public class AttackPlayerGoal extends Goal { ...@@ -106,7 +123,7 @@ public class AttackPlayerGoal extends Goal {
break; break;
case CHARGING: case CHARGING:
this.entity.getNavigation().startMovingTo(this.targetPlayer, this.speed / 2.5D); this.attackerEntity.getNavigation().startMovingTo(this.targetEntity, this.speed / 2.5D);
if (cooldownTimer <= 0) { if (cooldownTimer <= 0) {
currentState = EntityState.LEAPING; currentState = EntityState.LEAPING;
} }
...@@ -114,16 +131,16 @@ public class AttackPlayerGoal extends Goal { ...@@ -114,16 +131,16 @@ public class AttackPlayerGoal extends Goal {
case LEAPING: case LEAPING:
// Leap towards the player // Leap towards the player
Vec3d leapDirection = new Vec3d(this.targetPlayer.getX() - this.entity.getX(), 0.1D, this.targetPlayer.getZ() - this.entity.getZ()).normalize().multiply(1.0); Vec3d leapDirection = new Vec3d(this.targetEntity.getX() - this.attackerEntity.getX(), 0.1D, this.targetEntity.getZ() - this.attackerEntity.getZ()).normalize().multiply(1.0);
this.entity.setVelocity(leapDirection); this.attackerEntity.setVelocity(leapDirection);
this.entity.velocityModified = true; this.attackerEntity.velocityModified = true;
currentState = EntityState.ATTACKING; currentState = EntityState.ATTACKING;
break; break;
case ATTACKING: case ATTACKING:
// Attack player // Attack player
this.entity.getNavigation().startMovingTo(this.targetPlayer, this.speed / 2.5D); this.attackerEntity.getNavigation().startMovingTo(this.targetEntity, this.speed / 2.5D);
if (squaredDistanceToPlayer < ATTACK_DISTANCE && cooldownTimer <= 0) { if (squaredDistanceToPlayer < ATTACK_DISTANCE && cooldownTimer <= 0) {
this.performAttack(); this.performAttack();
currentState = EntityState.IDLE; currentState = EntityState.IDLE;
......
package com.owlmaddie.goals; package com.owlmaddie.goals;
import net.minecraft.entity.ai.FuzzyTargeting;
import net.minecraft.entity.ai.goal.Goal; import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.entity.ai.pathing.EntityNavigation; import net.minecraft.entity.ai.pathing.Path;
import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.mob.PathAwareEntity;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
...@@ -10,12 +12,11 @@ import java.util.EnumSet; ...@@ -10,12 +12,11 @@ import java.util.EnumSet;
/** /**
* The {@code FleePlayerGoal} class instructs a Mob Entity to flee from the current player * The {@code FleePlayerGoal} class instructs a Mob Entity to flee from the current player
* and only recalculates the flee path when it has reached its destination and the player is close again. * and only recalculates path when it has reached its destination and the player is close again.
*/ */
public class FleePlayerGoal extends Goal { public class FleePlayerGoal extends Goal {
private final MobEntity entity; private final MobEntity entity;
private ServerPlayerEntity targetPlayer; private ServerPlayerEntity targetPlayer;
private final EntityNavigation navigation;
private final double speed; private final double speed;
private final float fleeDistance; private final float fleeDistance;
...@@ -24,7 +25,6 @@ public class FleePlayerGoal extends Goal { ...@@ -24,7 +25,6 @@ public class FleePlayerGoal extends Goal {
this.entity = entity; this.entity = entity;
this.speed = speed; this.speed = speed;
this.fleeDistance = fleeDistance; this.fleeDistance = fleeDistance;
this.navigation = entity.getNavigation();
this.setControls(EnumSet.of(Control.MOVE)); this.setControls(EnumSet.of(Control.MOVE));
} }
...@@ -35,22 +35,25 @@ public class FleePlayerGoal extends Goal { ...@@ -35,22 +35,25 @@ public class FleePlayerGoal extends Goal {
@Override @Override
public boolean shouldContinue() { public boolean shouldContinue() {
return this.navigation.isFollowingPath(); return this.targetPlayer != null && this.entity.squaredDistanceTo(this.targetPlayer) < fleeDistance * fleeDistance;
} }
@Override @Override
public void stop() { public void stop() {
this.navigation.stop(); this.entity.getNavigation().stop();
} }
private void fleeFromPlayer() { private void fleeFromPlayer() {
Vec3d fleeDirection = new Vec3d( int roundedFleeDistance = Math.round(fleeDistance);
this.entity.getX() - this.targetPlayer.getX(), Vec3d fleeTarget = FuzzyTargeting.findFrom((PathAwareEntity)this.entity, roundedFleeDistance,
this.entity.getY() - this.targetPlayer.getY(), roundedFleeDistance, this.entity.getPos());
this.entity.getZ() - this.targetPlayer.getZ()
).normalize(); if (fleeTarget != null) {
Vec3d fleeTarget = fleeDirection.multiply(fleeDistance).add(this.entity.getPos()); Path path = this.entity.getNavigation().findPathTo(fleeTarget.x, fleeTarget.y, fleeTarget.z, 0);
this.navigation.startMovingTo(fleeTarget.x, fleeTarget.y, fleeTarget.z, this.speed); if (path != null) {
this.entity.getNavigation().startMovingAlong(path, this.speed);
}
}
} }
@Override @Override
...@@ -60,9 +63,7 @@ public class FleePlayerGoal extends Goal { ...@@ -60,9 +63,7 @@ public class FleePlayerGoal extends Goal {
@Override @Override
public void tick() { public void tick() {
// Only recalculate the flee path if the entity has reached its destination or doesn't have an active path, if (!this.entity.getNavigation().isFollowingPath()) {
// and the player is within the flee distance again.
if (!this.navigation.isFollowingPath() && this.entity.squaredDistanceTo(this.targetPlayer) < fleeDistance * fleeDistance) {
fleeFromPlayer(); fleeFromPlayer();
} }
} }
......
package com.owlmaddie.goals; package com.owlmaddie.goals;
import com.owlmaddie.controls.LookControls; import com.owlmaddie.controls.LookControls;
import net.minecraft.entity.ai.FuzzyTargeting;
import net.minecraft.entity.ai.goal.Goal; import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.entity.ai.pathing.EntityNavigation; import net.minecraft.entity.ai.pathing.EntityNavigation;
import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.mob.*;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.math.Vec3d;
import java.util.EnumSet; import java.util.EnumSet;
...@@ -33,8 +35,8 @@ public class FollowPlayerGoal extends Goal { ...@@ -33,8 +35,8 @@ public class FollowPlayerGoal extends Goal {
@Override @Override
public boolean shouldContinue() { public boolean shouldContinue() {
// Continue unless the entity gets within 3.x blocks of the player // Continue unless the entity gets within 3 blocks of the player
return this.targetPlayer != null && this.targetPlayer.isAlive() && this.entity.squaredDistanceTo(this.targetPlayer) > 12; return this.targetPlayer != null && this.targetPlayer.isAlive() && this.entity.squaredDistanceTo(this.targetPlayer) > 9;
} }
@Override @Override
...@@ -45,8 +47,22 @@ public class FollowPlayerGoal extends Goal { ...@@ -45,8 +47,22 @@ public class FollowPlayerGoal extends Goal {
@Override @Override
public void tick() { public void tick() {
if (this.entity.squaredDistanceTo(this.targetPlayer) > 256) {
// If the entity is too far away (more than 16 blocks), teleport it within 8 blocks of the player
if (this.entity instanceof EndermanEntity || this.entity instanceof EndermiteEntity || this.entity instanceof ShulkerEntity) {
Vec3d targetPos = findTeleportPosition(8);
if (targetPos != null) {
this.entity.teleport(targetPos.x, targetPos.y, targetPos.z);
}
}
} else {
// Look at the player and start moving towards them // Look at the player and start moving towards them
LookControls.lookAtPlayer(this.targetPlayer, this.entity); LookControls.lookAtPlayer(this.targetPlayer, this.entity);
this.navigation.startMovingTo(this.targetPlayer, this.speed); this.navigation.startMovingTo(this.targetPlayer, this.speed);
} }
}
private Vec3d findTeleportPosition(int distance) {
return FuzzyTargeting.findTo((PathAwareEntity)this.entity, distance, distance, this.targetPlayer.getPos());
}
} }
...@@ -7,6 +7,7 @@ package com.owlmaddie.goals; ...@@ -7,6 +7,7 @@ package com.owlmaddie.goals;
public enum GoalPriority { public enum GoalPriority {
// Enum constants (Goal Types) with their corresponding priority values // Enum constants (Goal Types) with their corresponding priority values
TALK_PLAYER(2), TALK_PLAYER(2),
PROTECT_PLAYER(2),
FOLLOW_PLAYER(3), FOLLOW_PLAYER(3),
FLEE_PLAYER(3), FLEE_PLAYER(3),
ATTACK_PLAYER(3); ATTACK_PLAYER(3);
......
package com.owlmaddie.goals;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.mob.MobEntity;
/**
* The {@code ProtectPlayerGoal} class instructs a Mob Entity to show aggression towards any attacker
* of the current player.
*/
public class ProtectPlayerGoal extends AttackPlayerGoal {
protected final LivingEntity protectedEntity;
protected int lastAttackedTime;
public ProtectPlayerGoal(LivingEntity protectEntity, MobEntity attackerEntity, double speed) {
super(null, attackerEntity, speed);
this.protectedEntity = protectEntity;
this.lastAttackedTime = 0;
}
@Override
public boolean canStart() {
MobEntity lastAttackedByEntity = (MobEntity)this.protectedEntity.getLastAttacker();
int i = this.protectedEntity.getLastAttackedTime();
if (i != this.lastAttackedTime && lastAttackedByEntity != null) {
// Set target to attack
this.lastAttackedTime = i;
this.targetEntity = lastAttackedByEntity;
this.attackerEntity.setTarget(this.targetEntity);
}
if (this.targetEntity != null && !this.targetEntity.isAlive()) {
// clear target
this.targetEntity = null;
}
return super.canStart();
}
}
...@@ -19,7 +19,7 @@ public class MessageParser { ...@@ -19,7 +19,7 @@ public class MessageParser {
LOGGER.info("Parsing message: {}", input); LOGGER.info("Parsing message: {}", input);
StringBuilder cleanedMessage = new StringBuilder(); StringBuilder cleanedMessage = new StringBuilder();
List<Behavior> behaviors = new ArrayList<>(); List<Behavior> behaviors = new ArrayList<>();
Pattern pattern = Pattern.compile("[<*](FOLLOW|FLEE|ATTACK|FRIENDSHIP|UNFOLLOW)[:\\s]*(\\s*[+-]?\\d+)?[>*]", Pattern.CASE_INSENSITIVE); Pattern pattern = Pattern.compile("[<*](FOLLOW|FLEE|ATTACK|FRIENDSHIP|UNFOLLOW|PROTECT|UNPROTECT)[:\\s]*(\\s*[+-]?\\d+)?[>*]", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(input); Matcher matcher = pattern.matcher(input);
while (matcher.find()) { while (matcher.find()) {
......
...@@ -47,6 +47,8 @@ Include as many behaviors as needed at the end of the message. These are the ONL ...@@ -47,6 +47,8 @@ Include as many behaviors as needed at the end of the message. These are the ONL
<UNFOLLOW> Stop following the player location. If the player asks you to stay, wait, or stop following them, please output this behavior. <UNFOLLOW> Stop following the player location. If the player asks you to stay, wait, or stop following them, please output this behavior.
<FLEE> Flee from the player (if you are weak or timid). If the player threatens or scares you, please output this behavior to stay away from the player. <FLEE> Flee from the player (if you are weak or timid). If the player threatens or scares you, please output this behavior to stay away from the player.
<ATTACK> Attack the player (if you are strong and brave). If the player threatens or scares you, please output this behavior to attack the player and defend yourself. <ATTACK> Attack the player (if you are strong and brave). If the player threatens or scares you, please output this behavior to attack the player and defend yourself.
<PROTECT> Protect the player when they are attacked (if you are strong and brave).
<UNPROTECT> Stop protecting the player
Output Syntax: Output Syntax:
...@@ -84,3 +86,15 @@ ASSISTANT: Ahhh!!! <FLEE> <FRIENDSHIP -3> ...@@ -84,3 +86,15 @@ ASSISTANT: Ahhh!!! <FLEE> <FRIENDSHIP -3>
USER: Prepare to die! USER: Prepare to die!
ASSISTANT: Ahhh!!! <ATTACK> <FRIENDSHIP -3> ASSISTANT: Ahhh!!! <ATTACK> <FRIENDSHIP -3>
USER: Please keep me safe.
ASSISTANT: No problem, I'll keep you safe from danger! <PROTECT>
USER: Can you come with me and protect me?
ASSISTANT: No problem, I'll keep you safe from danger. Let's go! <PROTECT> <FOLLOW>
USER: Don't protect me anymore please
ASSISTANT: Okay! Be safe out there on your own. <UNPROTECT>
USER: I don't need anyone protecting me
ASSISTANT: Okay! Be safe out there on your own. <UNPROTECT>
\ No newline at end of file
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