mirror of
https://github.com/OMGeeky/andors-trail.git
synced 2026-02-23 15:38:29 +01:00
Merge branch 'pathfinder' into v070
This commit is contained in:
@@ -504,7 +504,7 @@
|
||||
<string name="skill_longdescription_concussion">When making an attack on a target whose block chance (BC) is at least %1$d lower than your attack chance (AC), there is a %2$d %% chance that the hit will cause a concussion on the target. A concussion will severely lower the target\'s offensive combat abilities, making the target less able to land successful attacks.</string>
|
||||
|
||||
<string name="about_button4">About</string>
|
||||
|
||||
|
||||
<!-- =========================================== -->
|
||||
<!-- Added in v0.6.12 -->
|
||||
|
||||
@@ -562,6 +562,7 @@
|
||||
|
||||
<string name="heroinfo_reequip_cost">Re-equip cost (AP):</string>
|
||||
<string name="heroinfo_useitem_cost">Use item cost (AP):</string>
|
||||
<string name="combat_result_monstermoved">%1$s moves.</string>
|
||||
<string name="combat_log_title">Combat log</string>
|
||||
<string name="conversation_reward_quest_finished">[Quest completed: \"%1$s\"]</string>
|
||||
<string name="conversation_reward_quest_updated">[Quest updated: \"%1$s\"]</string>
|
||||
|
||||
@@ -323,6 +323,12 @@ public final class MainActivity extends Activity implements PlayerMovementListen
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMonsterMovedDuringCombat(Monster m) {
|
||||
String monsterName = m.getName();
|
||||
message(getString(R.string.combat_result_monstermoved, monsterName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerKilledMonster(Monster target) { }
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.gpl.rpg.AndorsTrail.model.AttackResult;
|
||||
import com.gpl.rpg.AndorsTrail.model.ability.SkillCollection;
|
||||
import com.gpl.rpg.AndorsTrail.model.actor.Actor;
|
||||
import com.gpl.rpg.AndorsTrail.model.actor.Monster;
|
||||
import com.gpl.rpg.AndorsTrail.model.actor.MonsterType;
|
||||
import com.gpl.rpg.AndorsTrail.model.actor.Player;
|
||||
import com.gpl.rpg.AndorsTrail.model.item.ItemTraits_OnUse;
|
||||
import com.gpl.rpg.AndorsTrail.model.item.Loot;
|
||||
@@ -296,34 +297,83 @@ public final class CombatController implements VisualEffectCompletedCallback {
|
||||
handleNextMonsterAction();
|
||||
}
|
||||
|
||||
private Monster determineNextMonster(Monster previousMonster) {
|
||||
if (previousMonster != null) {
|
||||
if (previousMonster.hasAPs(previousMonster.getAttackCost())) return previousMonster;
|
||||
private static final int ACTION_NONE = 0;
|
||||
private static final int ACTION_ATTACK = 1;
|
||||
private static final int ACTION_MOVE = 2;
|
||||
private int determineNextMonsterAction(Coord playerPosition) {
|
||||
if (currentActiveMonster != null) {
|
||||
if (shouldAttackWithMonsterInCombat(currentActiveMonster, playerPosition)) return ACTION_ATTACK;
|
||||
}
|
||||
|
||||
for (MonsterSpawnArea a : world.model.currentMap.spawnAreas) {
|
||||
for (Monster m : a.monsters) {
|
||||
if (!m.isAgressive()) continue;
|
||||
|
||||
if (m.isAdjacentTo(world.model.player)) {
|
||||
if (m.hasAPs(m.getAttackCost())) return m;
|
||||
if (shouldAttackWithMonsterInCombat(m, playerPosition)) {
|
||||
currentActiveMonster = m;
|
||||
return ACTION_ATTACK;
|
||||
} else if (shouldMoveMonsterInCombat(m, a, playerPosition)) {
|
||||
currentActiveMonster = m;
|
||||
return ACTION_MOVE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return ACTION_NONE;
|
||||
}
|
||||
|
||||
private static boolean shouldAttackWithMonsterInCombat(Monster m, Coord playerPosition) {
|
||||
if (!m.hasAPs(m.getAttackCost())) return false;
|
||||
if (!m.rectPosition.isAdjacentTo(playerPosition)) return false;
|
||||
return true;
|
||||
}
|
||||
private static boolean shouldMoveMonsterInCombat(Monster m, MonsterSpawnArea a, Coord playerPosition) {
|
||||
final int movementAggressionType = m.getMovementAggressionType();
|
||||
if (movementAggressionType == MonsterType.AGGRESSIONTYPE_NONE) return false;
|
||||
|
||||
if (!m.hasAPs(m.getMoveCost())) return false;
|
||||
if (m.position.isAdjacentTo(playerPosition)) return false;
|
||||
|
||||
if (movementAggressionType == MonsterType.AGGRESSIONTYPE_PROTECT_SPAWN) {
|
||||
if (a.area.contains(playerPosition)) return true;
|
||||
} else if (movementAggressionType == MonsterType.AGGRESSIONTYPE_HELP_OTHERS) {
|
||||
for (Monster o : a.monsters) {
|
||||
if (o == m) continue;
|
||||
if (o.rectPosition.isAdjacentTo(playerPosition)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleNextMonsterAction() {
|
||||
if (!world.model.uiSelections.isMainActivityVisible) return;
|
||||
|
||||
currentActiveMonster = determineNextMonster(currentActiveMonster);
|
||||
if (currentActiveMonster == null) {
|
||||
int nextMonsterAction = determineNextMonsterAction(world.model.player.position);
|
||||
if (nextMonsterAction == ACTION_NONE) {
|
||||
endMonsterTurn();
|
||||
} else if (nextMonsterAction == ACTION_ATTACK) {
|
||||
attackWithCurrentMonster();
|
||||
} else if (nextMonsterAction == ACTION_MOVE) {
|
||||
moveCurrentMonster();
|
||||
}
|
||||
}
|
||||
|
||||
private void moveCurrentMonster() {
|
||||
controllers.actorStatsController.useAPs(currentActiveMonster, currentActiveMonster.getMoveCost());
|
||||
if (!controllers.monsterMovementController.findPathFor(currentActiveMonster, world.model.player.position)) {
|
||||
// Couldn't find a path to move on.
|
||||
handleNextMonsterAction();
|
||||
return;
|
||||
}
|
||||
|
||||
controllers.monsterMovementController.moveMonsterToNextPosition(currentActiveMonster, world.model.currentMap);
|
||||
combatActionListeners.onMonsterMovedDuringCombat(currentActiveMonster);
|
||||
waitForNextMonsterAction();
|
||||
}
|
||||
|
||||
private void attackWithCurrentMonster() {
|
||||
controllers.actorStatsController.useAPs(currentActiveMonster, currentActiveMonster.getAttackCost());
|
||||
|
||||
combatTurnListeners.onMonsterIsAttacking(currentActiveMonster);
|
||||
|
||||
combatTurnListeners.onMonsterIsAttacking(currentActiveMonster);
|
||||
AttackResult attack = monsterAttacks(currentActiveMonster);
|
||||
this.lastAttackResult = attack;
|
||||
|
||||
@@ -337,7 +387,7 @@ public final class CombatController implements VisualEffectCompletedCallback {
|
||||
waitForNextMonsterAction();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final int CALLBACK_MONSTERATTACK = 0;
|
||||
private static final int CALLBACK_PLAYERATTACK = 1;
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ public final class Constants {
|
||||
public static final float EXP_FACTOR_SCALING = 0.7f;
|
||||
public static final int FLEE_FAIL_CHANCE_PERCENT = 20;
|
||||
public static final long MINIMUM_INPUT_INTERVAL = AndorsTrailApplication.DEVELOPMENT_DEBUGBUTTONS ? 50 : 200;
|
||||
public static final int MAX_MAP_WIDTH = 33;
|
||||
public static final int MAX_MAP_HEIGHT = 33;
|
||||
|
||||
public static final int MONSTER_MOVEMENT_TURN_DURATION_MS = 1200;
|
||||
public static final int ATTACK_ANIMATION_FPS = 10;
|
||||
|
||||
@@ -3,16 +3,18 @@ package com.gpl.rpg.AndorsTrail.controller;
|
||||
import com.gpl.rpg.AndorsTrail.context.ControllerContext;
|
||||
import com.gpl.rpg.AndorsTrail.context.WorldContext;
|
||||
import com.gpl.rpg.AndorsTrail.controller.listeners.MonsterMovementListeners;
|
||||
import com.gpl.rpg.AndorsTrail.controller.PathFinder.EvaluateWalkable;
|
||||
import com.gpl.rpg.AndorsTrail.model.ability.SkillCollection;
|
||||
import com.gpl.rpg.AndorsTrail.model.actor.Monster;
|
||||
import com.gpl.rpg.AndorsTrail.model.map.LayeredTileMap;
|
||||
import com.gpl.rpg.AndorsTrail.model.map.MapObject;
|
||||
import com.gpl.rpg.AndorsTrail.model.actor.MonsterType;
|
||||
import com.gpl.rpg.AndorsTrail.model.map.MonsterSpawnArea;
|
||||
import com.gpl.rpg.AndorsTrail.model.map.PredefinedMap;
|
||||
import com.gpl.rpg.AndorsTrail.util.Coord;
|
||||
import com.gpl.rpg.AndorsTrail.util.CoordRect;
|
||||
|
||||
public final class MonsterMovementController {
|
||||
public final class MonsterMovementController implements EvaluateWalkable {
|
||||
private final ControllerContext controllers;
|
||||
private final WorldContext world;
|
||||
public final MonsterMovementListeners monsterMovementListeners = new MonsterMovementListeners();
|
||||
@@ -79,11 +81,7 @@ public final class MonsterMovementController {
|
||||
// Monster has been moving and arrived at the destination.
|
||||
cancelCurrentMonsterMovement(m);
|
||||
} else {
|
||||
// Monster is moving.
|
||||
m.nextPosition.topLeft.set(
|
||||
m.position.x + sgn(m.movementDestination.x - m.position.x)
|
||||
,m.position.y + sgn(m.movementDestination.y - m.position.y)
|
||||
);
|
||||
determineMonsterNextPosition(m, area, world.model.player.position);
|
||||
|
||||
if (!monsterCanMoveTo(map, tileMap, m.nextPosition)) {
|
||||
cancelCurrentMonsterMovement(m);
|
||||
@@ -97,14 +95,26 @@ public final class MonsterMovementController {
|
||||
monsterMovementListeners.onMonsterSteppedOnPlayer(m);
|
||||
controllers.combatController.monsterSteppedOnPlayer(m);
|
||||
} else {
|
||||
CoordRect previousPosition = new CoordRect(new Coord(m.position), m.rectPosition.size);
|
||||
m.position.set(m.nextPosition.topLeft);
|
||||
monsterMovementListeners.onMonsterMoved(map, m, previousPosition);
|
||||
moveMonsterToNextPosition(m, map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void cancelCurrentMonsterMovement(final Monster m) {
|
||||
private void determineMonsterNextPosition(Monster m, MonsterSpawnArea area, Coord playerPosition) {
|
||||
if (m.getMovementAggressionType() == MonsterType.AGGRESSIONTYPE_PROTECT_SPAWN) {
|
||||
if (area.area.contains(playerPosition)) {
|
||||
if (findPathFor(m, playerPosition)) return;
|
||||
}
|
||||
}
|
||||
|
||||
// Monster is moving in a straight line.
|
||||
m.nextPosition.topLeft.set(
|
||||
m.position.x + sgn(m.movementDestination.x - m.position.x)
|
||||
,m.position.y + sgn(m.movementDestination.y - m.position.y)
|
||||
);
|
||||
}
|
||||
|
||||
private static void cancelCurrentMonsterMovement(final Monster m) {
|
||||
m.movementDestination = null;
|
||||
m.nextActionTime += getMillisecondsPerMove(m) * Constants.rollValue(Constants.monsterWaitTurns);
|
||||
}
|
||||
@@ -118,4 +128,20 @@ public final class MonsterMovementController {
|
||||
if (i >= 1) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private final PathFinder pathfinder = new PathFinder(Constants.MAX_MAP_WIDTH, Constants.MAX_MAP_HEIGHT, this);
|
||||
public boolean findPathFor(Monster m, Coord to) {
|
||||
return pathfinder.findPathBetween(m.rectPosition, to, m.nextPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWalkable(CoordRect r) {
|
||||
return monsterCanMoveTo(world.model.currentMap, world.model.currentTileMap, r);
|
||||
}
|
||||
|
||||
public void moveMonsterToNextPosition(Monster m, PredefinedMap map) {
|
||||
CoordRect previousPosition = new CoordRect(new Coord(m.position), m.rectPosition.size);
|
||||
m.position.set(m.nextPosition.topLeft);
|
||||
monsterMovementListeners.onMonsterMoved(map, m, previousPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.gpl.rpg.AndorsTrail.controller;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.gpl.rpg.AndorsTrail.util.Coord;
|
||||
import com.gpl.rpg.AndorsTrail.util.CoordRect;
|
||||
|
||||
public class PathFinder {
|
||||
private final int maxWidth;
|
||||
private final int maxHeight;
|
||||
private final boolean visited[];
|
||||
private final ListOfCoords visitQueue;
|
||||
private final EvaluateWalkable map;
|
||||
public int iterations = 0;
|
||||
|
||||
public PathFinder(int maxWidth, int maxHeight, EvaluateWalkable map) {
|
||||
this.maxWidth = maxWidth;
|
||||
this.maxHeight = maxHeight;
|
||||
this.map = map;
|
||||
this.visited = new boolean[maxWidth*maxHeight];
|
||||
this.visitQueue = new ListOfCoords(maxWidth*maxHeight);
|
||||
}
|
||||
|
||||
public interface EvaluateWalkable {
|
||||
public boolean isWalkable(CoordRect r);
|
||||
}
|
||||
|
||||
public boolean findPathBetween(final CoordRect from, final Coord to, CoordRect nextStep) {
|
||||
iterations = 0;
|
||||
if (from.equals(to)) return false;
|
||||
|
||||
Coord measureDistanceTo = from.topLeft;
|
||||
Coord p = nextStep.topLeft;
|
||||
Arrays.fill(visited, false);
|
||||
visitQueue.reset();
|
||||
|
||||
visitQueue.push(to.x, to.y, 0);
|
||||
visited[(to.y * maxWidth) + to.x] = true;
|
||||
|
||||
while (!visitQueue.isEmpty()) {
|
||||
visitQueue.popFirst(p);
|
||||
++iterations;
|
||||
|
||||
if (iterations > 100) return false;
|
||||
|
||||
if (from.isAdjacentTo(p)) return true;
|
||||
|
||||
p.x -= 1; visit(nextStep, measureDistanceTo);
|
||||
p.x += 2; visit(nextStep, measureDistanceTo);
|
||||
p.x -= 1; p.y -= 1; visit(nextStep, measureDistanceTo);
|
||||
p.y += 2; visit(nextStep, measureDistanceTo);
|
||||
p.x -= 1; visit(nextStep, measureDistanceTo);
|
||||
p.x += 2; visit(nextStep, measureDistanceTo);
|
||||
p.y -= 2; visit(nextStep, measureDistanceTo);
|
||||
p.x -= 2; visit(nextStep, measureDistanceTo);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void visit(CoordRect r, Coord measureDistanceTo) {
|
||||
final int x = r.topLeft.x;
|
||||
final int y = r.topLeft.y;
|
||||
|
||||
if (x < 0) return;
|
||||
if (y < 0) return;
|
||||
if (x >= maxWidth) return;
|
||||
if (y >= maxHeight) return;
|
||||
|
||||
final int i = (y * maxWidth) + x;
|
||||
if (visited[i]) return;
|
||||
visited[i] = true;
|
||||
if (!map.isWalkable(r)) return;
|
||||
|
||||
int dx = (measureDistanceTo.x - x);
|
||||
int dy = (measureDistanceTo.y - y);
|
||||
visitQueue.push(x, y, dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
private static final class ListOfCoords {
|
||||
private final int coords[];
|
||||
private final int weights[];
|
||||
private final int maxIndex;
|
||||
private int lastIndex; // Index of the last coord that was inserted
|
||||
private int frontIndex; // Index to the first coord that is not discarded
|
||||
private static final int DISCARDED = -1;
|
||||
|
||||
public ListOfCoords(int maxSize) {
|
||||
this.maxIndex = maxSize-1;
|
||||
this.coords = new int[maxSize];
|
||||
this.weights = new int[maxSize];
|
||||
}
|
||||
public void reset() {
|
||||
lastIndex = -1;
|
||||
frontIndex = 0;
|
||||
}
|
||||
private static int coordsToInt(int x, int y) {
|
||||
return ((y << 8) & 0xff00) | (x & 0xff);
|
||||
}
|
||||
private static void intToCoords(int c, Coord dest) {
|
||||
dest.x = c & 0xff;
|
||||
dest.y = (c >> 8) & 0xff;
|
||||
}
|
||||
|
||||
public void push(int x, int y, int weight) {
|
||||
if (lastIndex == maxIndex) return;
|
||||
++lastIndex;
|
||||
coords[lastIndex] = coordsToInt(x, y);
|
||||
weights[lastIndex] = weight;
|
||||
}
|
||||
|
||||
public int popFirst(Coord dest) {
|
||||
int i = frontIndex;
|
||||
int lowestWeightIndex = i;
|
||||
int lowestWeight = weights[i];
|
||||
++i;
|
||||
for(;i <= lastIndex; ++i) {
|
||||
if (weights[i] == DISCARDED) continue;
|
||||
if (weights[i] < lowestWeight) {
|
||||
lowestWeightIndex = i;
|
||||
lowestWeight = weights[i];
|
||||
}
|
||||
}
|
||||
intToCoords(coords[lowestWeightIndex], dest);
|
||||
weights[lowestWeightIndex] = DISCARDED;
|
||||
|
||||
// Increase frontIndex to the first index that is not discarded.
|
||||
while (frontIndex <= lastIndex) {
|
||||
if (weights[frontIndex] == DISCARDED) ++frontIndex;
|
||||
else break;
|
||||
}
|
||||
|
||||
return lowestWeight;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return frontIndex > lastIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ public interface CombatActionListener {
|
||||
void onPlayerAttackSuccess(Monster target, AttackResult attackResult);
|
||||
void onMonsterAttackMissed(Monster attacker, AttackResult attackResult);
|
||||
void onMonsterAttackSuccess(Monster attacker, AttackResult attackResult);
|
||||
void onMonsterMovedDuringCombat(Monster m);
|
||||
void onPlayerKilledMonster(Monster target);
|
||||
void onPlayerStartedFleeing();
|
||||
void onPlayerFailedFleeing();
|
||||
|
||||
@@ -21,8 +21,12 @@ public final class CombatActionListeners extends ListOfListeners<CombatActionLis
|
||||
private final Function2<CombatActionListener, Monster, AttackResult> onMonsterAttackSuccess = new Function2<CombatActionListener, Monster, AttackResult>() {
|
||||
@Override public void call(CombatActionListener listener, Monster attacker, AttackResult attackResult) { listener.onMonsterAttackSuccess(attacker, attackResult); }
|
||||
};
|
||||
|
||||
private final Function1<CombatActionListener, Monster> onPlayerKilledMonster = new Function1<CombatActionListener, Monster>() {
|
||||
|
||||
private final Function1<CombatActionListener, Monster> onMonsterMovedDuringCombat = new Function1<CombatActionListener, Monster>() {
|
||||
@Override public void call(CombatActionListener listener, Monster m) { listener.onMonsterMovedDuringCombat(m); }
|
||||
};
|
||||
|
||||
private final Function1<CombatActionListener, Monster> onPlayerKilledMonster = new Function1<CombatActionListener, Monster>() {
|
||||
@Override public void call(CombatActionListener listener, Monster target) { listener.onPlayerKilledMonster(target); }
|
||||
};
|
||||
|
||||
@@ -58,6 +62,11 @@ public final class CombatActionListeners extends ListOfListeners<CombatActionLis
|
||||
callAllListeners(this.onMonsterAttackSuccess, attacker, attackResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMonsterMovedDuringCombat(Monster m) {
|
||||
callAllListeners(this.onMonsterMovedDuringCombat, m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerKilledMonster(Monster target) {
|
||||
callAllListeners(this.onPlayerKilledMonster, target);
|
||||
|
||||
@@ -16,20 +16,20 @@ import com.gpl.rpg.AndorsTrail.util.CoordRect;
|
||||
import com.gpl.rpg.AndorsTrail.util.Range;
|
||||
|
||||
public final class Monster extends Actor {
|
||||
|
||||
|
||||
public Coord movementDestination = null;
|
||||
public long nextActionTime = 0;
|
||||
public final CoordRect nextPosition;
|
||||
|
||||
private boolean forceAggressive = false;
|
||||
private ItemContainer shopItems = null;
|
||||
|
||||
|
||||
private final MonsterType monsterType;
|
||||
|
||||
|
||||
public Monster(MonsterType monsterType) {
|
||||
super(monsterType.tileSize, false, monsterType.isImmuneToCriticalHits());
|
||||
this.monsterType = monsterType;
|
||||
this.iconID = monsterType.iconID;
|
||||
this.iconID = monsterType.iconID;
|
||||
this.nextPosition = new CoordRect(new Coord(), monsterType.tileSize);
|
||||
resetStatsToBaseTraits();
|
||||
this.ap.setMax();
|
||||
@@ -58,7 +58,8 @@ public final class Monster extends Actor {
|
||||
public String getMonsterTypeID() { return monsterType.id; }
|
||||
public String getFaction() { return monsterType.faction; }
|
||||
public int getMonsterClass() { return monsterType.monsterClass; }
|
||||
|
||||
public int getMovementAggressionType() { return monsterType.aggressionType; }
|
||||
|
||||
public void createLoot(Loot container, Player player) {
|
||||
int exp = this.getExp();
|
||||
exp += exp * player.getSkillLevel(SkillCollection.SKILL_MORE_EXP) * SkillCollection.PER_SKILLPOINT_INCREASE_MORE_EXP_PERCENT / 100;
|
||||
|
||||
@@ -25,6 +25,8 @@ public final class MonsterType {
|
||||
public final boolean isUnique; // Unique monsters are not respawned.
|
||||
public final String faction;
|
||||
public final int monsterClass;
|
||||
public final int aggressionType;
|
||||
|
||||
public final Size tileSize;
|
||||
public final int iconID;
|
||||
public final int maxAP;
|
||||
@@ -49,6 +51,7 @@ public final class MonsterType {
|
||||
boolean isUnique,
|
||||
String faction,
|
||||
int monsterClass,
|
||||
int aggressionType,
|
||||
Size tileSize,
|
||||
int iconID,
|
||||
int maxAP,
|
||||
@@ -71,6 +74,7 @@ public final class MonsterType {
|
||||
this.faction = faction;
|
||||
this.isUnique = isUnique;
|
||||
this.monsterClass = monsterClass;
|
||||
this.aggressionType = aggressionType;
|
||||
this.tileSize = tileSize;
|
||||
this.iconID = iconID;
|
||||
this.maxAP = maxAP;
|
||||
@@ -85,11 +89,34 @@ public final class MonsterType {
|
||||
this.damageResistance = damageResistance;
|
||||
this.onHitEffects = onHitEffects;
|
||||
}
|
||||
|
||||
public static final int AGGRESSIONTYPE_NONE = 0;
|
||||
public static final int AGGRESSIONTYPE_HELP_OTHERS = 1; // Will move to help if the player attacks some other monster in the same spawn.
|
||||
public static final int AGGRESSIONTYPE_PROTECT_SPAWN = 2; // Will move to attack if the player stands inside the spawn.
|
||||
|
||||
private static int getSuggestedAggressionType(int monsterClass) {
|
||||
switch (monsterClass) {
|
||||
case MONSTERCLASS_CONSTRUCT:
|
||||
case MONSTERCLASS_GIANT:
|
||||
case MONSTERCLASS_GHOST:
|
||||
return AGGRESSIONTYPE_NONE;
|
||||
case MONSTERCLASS_DEMON:
|
||||
case MONSTERCLASS_ANIMAL:
|
||||
case MONSTERCLASS_REPTILE:
|
||||
case MONSTERCLASS_INSECT:
|
||||
return AGGRESSIONTYPE_PROTECT_SPAWN;
|
||||
case MONSTERCLASS_UNDEAD:
|
||||
case MONSTERCLASS_HUMANOID:
|
||||
return AGGRESSIONTYPE_HELP_OTHERS;
|
||||
default:
|
||||
return AGGRESSIONTYPE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isImmuneToCriticalHits() {
|
||||
if (monsterClass == MONSTERCLASS_GHOST) return true;
|
||||
else if (monsterClass == MONSTERCLASS_UNDEAD) return true;
|
||||
else if (monsterClass == MONSTERCLASS_DEMON) return true;
|
||||
if (monsterClass == MONSTERCLASS_UNDEAD) return true;
|
||||
if (monsterClass == MONSTERCLASS_DEMON) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ public final class MonsterTypeParser extends JsonCollectionParserFor<MonsterType
|
||||
, o.optInt(JsonFieldNames.Monster.unique, 0) > 0
|
||||
, o.optString(JsonFieldNames.Monster.faction, null)
|
||||
, o.optInt(JsonFieldNames.Monster.monsterClass, MonsterType.MONSTERCLASS_HUMANOID)
|
||||
, o.optInt(JsonFieldNames.Monster.movementAggressionType, MonsterType.AGGRESSIONTYPE_NONE)
|
||||
, ResourceParserUtils.parseSize(o.optString(JsonFieldNames.Monster.size, null), size1x1) //TODO: This could be loaded from the tileset size instead.
|
||||
, ResourceParserUtils.parseImageID(tileLoader, o.getString(JsonFieldNames.Monster.iconID))
|
||||
, maxAP
|
||||
|
||||
@@ -109,6 +109,7 @@ public final class JsonFieldNames {
|
||||
public static final String spawnGroup = "spawnGroup";
|
||||
public static final String size = "size";
|
||||
public static final String monsterClass = "monsterClass";
|
||||
public static final String movementAggressionType = "movementAggressionType";
|
||||
public static final String unique = "unique";
|
||||
public static final String faction = "faction";
|
||||
public static final String maxHP = "maxHP";
|
||||
|
||||
@@ -455,8 +455,13 @@ public final class MainView extends SurfaceView
|
||||
@Override
|
||||
public void onMonsterMoved(PredefinedMap map, Monster m, CoordRect previousPosition) {
|
||||
if (map != currentMap) return;
|
||||
if (!mapViewArea.intersects(m.rectPosition)) return;
|
||||
redrawNextTick = true;
|
||||
if (!mapViewArea.intersects(m.rectPosition) && !mapViewArea.intersects(previousPosition)) return;
|
||||
if (model.uiSelections.isInCombat) {
|
||||
redrawArea(previousPosition, REDRAW_AREA_MONSTER_MOVED);
|
||||
redrawArea(m.rectPosition, REDRAW_AREA_MONSTER_MOVED);
|
||||
} else {
|
||||
redrawNextTick = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -34,6 +34,7 @@ var ATEditor = (function(ATEditor, _) {
|
||||
,moveCost: 10
|
||||
,unique: 0
|
||||
,monsterClass: 0
|
||||
,movementAggressionType: 0
|
||||
,attackDamage: {}
|
||||
,hitEffect: { increaseCurrentHP: {}, increaseCurrentAP: {}, conditionsSource: [], conditionsTarget: [] }
|
||||
}
|
||||
|
||||
@@ -65,6 +65,14 @@
|
||||
<label for="faction" class="hint hint--top" data-hint="Used for making whole groups of monsters aggressive from dialogue. Dialogue replies may affect the player's faction ratings, and having rating less than 0 for a faction will make all monsters in a faction automatically attack.">Faction:</label>
|
||||
<input type="text" size="30" id="faction" ng-model="obj.faction" class="field at-input-id"/>
|
||||
</div>
|
||||
<div class="fieldWithLabel" ng-ds-fade="obj.showAdvanced">
|
||||
<label for="movementAggressionType">Movement in combat:</label>
|
||||
<select class="field" id="movementAggressionType" ng-model="obj.movementAggressionType">
|
||||
<option value="0">Does not move in combat</option>
|
||||
<option value="1">Help others in same spawn area</option>
|
||||
<option value="2">Move towards player if inside spawn area</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="fieldWithLabel">
|
||||
<label class="checkbox"><input type="checkbox" id="hasConversation" ng-model="obj.hasConversation" />Has conversation</label>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user