mirror of
https://github.com/ProjectSWGCore/Holocore.git
synced 2026-01-15 23:05:45 -05:00
Fixed dueling and added NPC-NPC combat
This commit is contained in:
Submodule pswgcommon updated: b7afb606fc...e34be46179
@@ -26,15 +26,15 @@
|
||||
***********************************************************************************/
|
||||
package com.projectswg.holocore.intents.gameplay.gcw.faction;
|
||||
|
||||
import com.projectswg.common.data.encodables.tangible.PvpFaction;
|
||||
import com.projectswg.common.data.encodables.tangible.PvpStatus;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.combat.FactionLoader.Faction;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject;
|
||||
import me.joshlarson.jlcommon.control.Intent;
|
||||
|
||||
public class FactionIntent extends Intent {
|
||||
|
||||
private TangibleObject target;
|
||||
private PvpFaction newFaction;
|
||||
private Faction newFaction;
|
||||
private FactionIntentType updateType;
|
||||
private PvpStatus newStatus;
|
||||
|
||||
@@ -47,7 +47,7 @@ public class FactionIntent extends Intent {
|
||||
this.updateType = updateType;
|
||||
}
|
||||
|
||||
public FactionIntent(TangibleObject target, PvpFaction newFaction) {
|
||||
public FactionIntent(TangibleObject target, Faction newFaction) {
|
||||
this(target, FactionIntentType.FACTIONUPDATE);
|
||||
this.newFaction = newFaction;
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public class FactionIntent extends Intent {
|
||||
return target;
|
||||
}
|
||||
|
||||
public PvpFaction getNewFaction() {
|
||||
public Faction getNewFaction() {
|
||||
return newFaction;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public class FactionIntent extends Intent {
|
||||
new FactionIntent(target, FactionIntentType.STATUSUPDATE).broadcast();
|
||||
}
|
||||
|
||||
public static void broadcastUpdateFaction(TangibleObject target, PvpFaction newFaction) {
|
||||
public static void broadcastUpdateFaction(TangibleObject target, Faction newFaction) {
|
||||
new FactionIntent(target, newFaction).broadcast();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (c) 2019 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
|
||||
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
|
||||
* Our goal is to create an emulator which will provide a server for players to *
|
||||
* continue playing a game similar to the one they used to play. We are basing *
|
||||
* it on the final publish of the game prior to end-game events. *
|
||||
* *
|
||||
* This file is part of Holocore. *
|
||||
* *
|
||||
* --------------------------------------------------------------------------------*
|
||||
* *
|
||||
* Holocore is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as *
|
||||
* published by the Free Software Foundation, either version 3 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* Holocore is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.holocore.resources.gameplay.combat
|
||||
|
||||
import com.projectswg.common.data.encodables.tangible.Posture
|
||||
import com.projectswg.common.data.encodables.tangible.PvpStatus
|
||||
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject
|
||||
import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject
|
||||
import com.projectswg.holocore.resources.support.objects.swg.tangible.OptionFlag
|
||||
import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject
|
||||
|
||||
object EnemyProcessor {
|
||||
|
||||
fun isAttackable(source: TangibleObject, target: TangibleObject): Boolean {
|
||||
if (target.hasOptionFlags(OptionFlag.INVULNERABLE))
|
||||
return false
|
||||
if (source !is CreatureObject || target !is CreatureObject)
|
||||
return isValidTangible(source) && isValidTangible(target) && isFactionAttackable(source, target)
|
||||
|
||||
if (!isValidPosture(source) || !isValidPosture(target))
|
||||
return false // If neither of us have a valid posture, nobody can attack
|
||||
if (isAttackAllowedAnyways(source, target))
|
||||
return true
|
||||
|
||||
// NPC-based checks
|
||||
if (source is AIObject) {
|
||||
if (target is AIObject)
|
||||
return isFactionAttackable(source, target)
|
||||
return ((source.hasOptionFlags(OptionFlag.AGGRESSIVE) || source.defenders.contains(target.objectId)) && isFactionAttackable(target, source)) || isFactionAttackable(source, target) // EvP only when aggressive or under attack
|
||||
} else if (target is AIObject) {
|
||||
return isFactionAttackable(source, target) // PvE allowed
|
||||
}
|
||||
return isFactionAttackable(source, target) // PvP
|
||||
}
|
||||
|
||||
private fun isFactionAttackable(source: TangibleObject, target: TangibleObject): Boolean {
|
||||
val ourFaction = source.faction
|
||||
val otherFaction = target.faction
|
||||
|
||||
if (ourFaction == null || ourFaction.name == "neutral" || otherFaction == null || otherFaction.name == "neutral")
|
||||
return source is CreatureObject && source.isPlayer && target is CreatureObject && !target.isPlayer // either neutral = PvE only
|
||||
|
||||
// PvE / EvE - where one is a TangibleObject
|
||||
if (source !is CreatureObject || target !is CreatureObject)
|
||||
return ourFaction.isEnemy(otherFaction) || (source is CreatureObject && source.isPlayer && !ourFaction.isAlly(otherFaction))
|
||||
// PvP
|
||||
if (source.isPlayer && target.isPlayer)
|
||||
return ourFaction.isEnemy(otherFaction) && source.pvpStatus == PvpStatus.SPECIALFORCES && target.pvpStatus == PvpStatus.SPECIALFORCES
|
||||
// PvE
|
||||
if (source.isPlayer)
|
||||
return !ourFaction.isAlly(otherFaction)
|
||||
// EvE
|
||||
return ourFaction.isEnemy(otherFaction)
|
||||
}
|
||||
|
||||
private fun isAttackAllowedAnyways(source: CreatureObject, target: CreatureObject): Boolean {
|
||||
// TODO bounty hunting
|
||||
// TODO pets, vehicles etc having same flagging as their owner
|
||||
// TODO guild wars
|
||||
if (source.isDuelingPlayer(target))
|
||||
return true // If we're dueling, can always attack
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isValidTangible(obj: TangibleObject): Boolean {
|
||||
return obj !is CreatureObject || isValidPosture(obj)
|
||||
}
|
||||
|
||||
private fun isValidPosture(obj: CreatureObject): Boolean {
|
||||
return when (obj.posture) {
|
||||
Posture.DEAD, Posture.INCAPACITATED -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
package com.projectswg.holocore.resources.support.data.server_info.loader
|
||||
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.combat.FactionLoader
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.npc.*
|
||||
import me.joshlarson.jlcommon.log.Log
|
||||
import java.io.IOException
|
||||
@@ -42,6 +43,7 @@ object ServerData {
|
||||
*/
|
||||
val buffs by SoftDataLoaderDelegate(::BuffLoader)
|
||||
val specialLines by SoftDataLoaderDelegate(::SpecialLineLoader)
|
||||
val factions by SoftDataLoaderDelegate(::FactionLoader)
|
||||
|
||||
/*
|
||||
* Skill / Expertise / Collection
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (c) 2019 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
|
||||
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
|
||||
* Our goal is to create an emulator which will provide a server for players to *
|
||||
* continue playing a game similar to the one they used to play. We are basing *
|
||||
* it on the final publish of the game prior to end-game events. *
|
||||
* *
|
||||
* This file is part of Holocore. *
|
||||
* *
|
||||
* --------------------------------------------------------------------------------*
|
||||
* *
|
||||
* Holocore is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as *
|
||||
* published by the Free Software Foundation, either version 3 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* Holocore is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.holocore.resources.support.data.server_info.loader.combat
|
||||
|
||||
import com.projectswg.common.data.encodables.tangible.PvpFaction
|
||||
import com.projectswg.holocore.resources.support.data.server_info.SdbLoader
|
||||
import com.projectswg.holocore.resources.support.data.server_info.SdbLoader.SdbResultSet
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.DataLoader
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import java.util.stream.Collectors
|
||||
|
||||
class FactionLoader : DataLoader() {
|
||||
|
||||
private var factions: Map<String, Faction> = HashMap()
|
||||
|
||||
fun getFaction(faction: String): Faction? {
|
||||
return factions[faction]
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun load() {
|
||||
SdbLoader.load(File("serverdata/faction/faction_datatable.sdb")).use { set ->
|
||||
factions = set.stream { Faction(it) }.collect(Collectors.toMap({ it.name }, { it }))
|
||||
}
|
||||
}
|
||||
|
||||
class Faction(set: SdbResultSet) {
|
||||
|
||||
val name = set.getText("factionName").toLowerCase(Locale.US)
|
||||
val isPlayerAllowed = set.getBoolean("playerAllowed")
|
||||
val isAggressive = set.getBoolean("isAggro")
|
||||
val isPvP = set.getBoolean("isPvP")
|
||||
val pvpFaction = processPvpFaction(set.getText("pvpFaction"))
|
||||
private val enemies = processList(set.getText("enemies"))
|
||||
private val allies = processList(set.getText("allies"))
|
||||
val combatFactor = set.getInt("combatFactor").toInt()
|
||||
|
||||
fun isEnemy(faction: String) = enemies.contains(faction.toLowerCase(Locale.US))
|
||||
fun isEnemy(faction: Faction) = enemies.contains(faction.name)
|
||||
|
||||
fun isAlly(faction: String) = name == faction.toLowerCase() || allies.contains(faction.toLowerCase(Locale.US))
|
||||
fun isAlly(faction: Faction) = name == faction.name || allies.contains(faction.name)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
return name == (other as Faction).name
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return name.hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Faction(name=$name isPlayerAllowed=$isPlayerAllowed isAggressive=$isAggressive isPvP=$isPvP enemies=$enemies allies=$allies)"
|
||||
}
|
||||
|
||||
private fun processPvpFaction(faction: String): PvpFaction {
|
||||
return when (faction.toLowerCase(Locale.US)) {
|
||||
"imperial" -> PvpFaction.IMPERIAL
|
||||
"rebel" -> PvpFaction.REBEL
|
||||
else -> PvpFaction.NEUTRAL
|
||||
}
|
||||
}
|
||||
|
||||
private fun processList(listStr: String): List<String> {
|
||||
if (listStr == "-" || listStr.isBlank())
|
||||
return emptyList()
|
||||
return listStr.toLowerCase(Locale.US).split(';')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,11 +26,12 @@
|
||||
***********************************************************************************/
|
||||
package com.projectswg.holocore.resources.support.data.server_info.loader.npc;
|
||||
|
||||
import com.projectswg.common.data.encodables.tangible.PvpFaction;
|
||||
import com.projectswg.common.data.swgfile.ClientFactory;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.SdbLoader;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.SdbLoader.SdbResultSet;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.DataLoader;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.ServerData;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.combat.FactionLoader.Faction;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -122,7 +123,7 @@ public final class NpcLoader extends DataLoader {
|
||||
private final String name;
|
||||
private final String stfName;
|
||||
private final String niche;
|
||||
private final PvpFaction faction;
|
||||
private final Faction faction;
|
||||
private final boolean specForce;
|
||||
private final double attackSpeed;
|
||||
private final double movementSpeed;
|
||||
@@ -156,6 +157,7 @@ public final class NpcLoader extends DataLoader {
|
||||
this.hue = (int) set.getInt("hue");
|
||||
this.stfName = set.getText("stf_name");
|
||||
this.niche = set.getText("niche").intern();
|
||||
this.faction = ServerData.INSTANCE.getFactions().getFaction(set.getText("faction"));
|
||||
this.iffs = List.of(set.getText("iff_template").split(";")).stream().map(s -> "object/mobile/"+s).map(ClientFactory::formatToSharedFile).collect(Collectors.toUnmodifiableList());
|
||||
this.specForce = set.getBoolean("spec_force");
|
||||
this.attackSpeed = set.getReal("attack_speed");
|
||||
@@ -177,18 +179,6 @@ public final class NpcLoader extends DataLoader {
|
||||
if (scaleMax < scaleMin)
|
||||
throw new IllegalArgumentException("scaleMax must be greater than scaleMin");
|
||||
|
||||
switch (set.getText("faction")) {
|
||||
case "rebel":
|
||||
this.faction = PvpFaction.REBEL;
|
||||
break;
|
||||
case "imperial":
|
||||
this.faction = PvpFaction.IMPERIAL;
|
||||
break;
|
||||
default:
|
||||
this.faction = PvpFaction.NEUTRAL;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (niche) {
|
||||
case "humanoid":
|
||||
this.humanoidInfo = new HumanoidNpcInfo(set);
|
||||
@@ -231,7 +221,7 @@ public final class NpcLoader extends DataLoader {
|
||||
return Collections.unmodifiableList(iffs);
|
||||
}
|
||||
|
||||
public PvpFaction getFaction() {
|
||||
public Faction getFaction() {
|
||||
return faction;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.projectswg.holocore.resources.support.global.commands.callbacks.comb
|
||||
import com.projectswg.common.data.encodables.tangible.PvpFaction;
|
||||
import com.projectswg.holocore.intents.gameplay.gcw.faction.FactionIntent;
|
||||
import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.ServerData;
|
||||
import com.projectswg.holocore.resources.support.global.commands.ICmdCallback;
|
||||
import com.projectswg.holocore.resources.support.global.player.Player;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
|
||||
@@ -19,11 +20,11 @@ public final class CmdPVP implements ICmdCallback {
|
||||
|
||||
if (!args.isEmpty()) {
|
||||
if (args.contains("imperial")) {
|
||||
new FactionIntent(creature, PvpFaction.IMPERIAL).broadcast();
|
||||
new FactionIntent(creature, ServerData.INSTANCE.getFactions().getFaction("imperial")).broadcast();
|
||||
} else if (args.contains("rebel")) {
|
||||
new FactionIntent(creature, PvpFaction.REBEL).broadcast();
|
||||
new FactionIntent(creature, ServerData.INSTANCE.getFactions().getFaction("rebel")).broadcast();
|
||||
} else {
|
||||
new FactionIntent(creature, PvpFaction.NEUTRAL).broadcast();
|
||||
new FactionIntent(creature, ServerData.INSTANCE.getFactions().getFaction("neutral")).broadcast();
|
||||
}
|
||||
} else if (creature.getPvpFaction() != PvpFaction.NEUTRAL) {
|
||||
new FactionIntent(creature, FactionIntent.FactionIntentType.SWITCHUPDATE).broadcast();
|
||||
|
||||
@@ -133,7 +133,7 @@ public class NpcCombatMode extends NpcMode {
|
||||
@Nullable
|
||||
private CreatureObject getPrimaryTarget() {
|
||||
return targets.stream()
|
||||
.filter(creo -> creo.isEnemyOf(getAI()))
|
||||
.filter(creo -> creo.isAttackable(getAI()))
|
||||
.filter(creo -> (creo.getPosture() != Posture.INCAPACITATED || getSpawner().isDeathblow()) && creo.getPosture() != Posture.DEAD) // Don't attack if they're already dead
|
||||
.min(Comparator.comparingInt(CreatureObject::getHealth)).orElse(null);
|
||||
}
|
||||
@@ -145,7 +145,7 @@ public class NpcCombatMode extends NpcMode {
|
||||
.filter(AIObject.class::isInstance) // get nearby AI
|
||||
.filter(ai -> ai.getWorldLocation().distanceTo(myLocation) < assistRange) // that can assist
|
||||
.map(AIObject.class::cast)
|
||||
.filter(ai -> targets.stream().anyMatch(ai::isEnemyOf))
|
||||
.filter(ai -> targets.stream().anyMatch(ai::isAttackable))
|
||||
.forEach(ai -> StartNpcCombatIntent.broadcast(ai, targets));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
***********************************************************************************/
|
||||
package com.projectswg.holocore.resources.support.npc.spawn;
|
||||
|
||||
import com.projectswg.common.data.encodables.tangible.PvpFaction;
|
||||
import com.projectswg.common.data.encodables.tangible.PvpFlag;
|
||||
import com.projectswg.common.data.encodables.tangible.PvpStatus;
|
||||
import com.projectswg.common.data.location.Location;
|
||||
@@ -34,6 +33,7 @@ import com.projectswg.common.data.location.Location.LocationBuilder;
|
||||
import com.projectswg.holocore.intents.gameplay.gcw.faction.FactionIntent;
|
||||
import com.projectswg.holocore.intents.support.objects.swg.ObjectCreatedIntent;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.DataLoader;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.combat.FactionLoader.Faction;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.npc.NpcStatLoader.DetailNpcStatInfo;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.npc.NpcStatLoader.NpcStatInfo;
|
||||
import com.projectswg.holocore.resources.support.npc.ai.NpcLoiterMode;
|
||||
@@ -46,6 +46,7 @@ import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.tangible.OptionFlag;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.weapon.WeaponObject;
|
||||
import me.joshlarson.jlcommon.control.IntentChain;
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
import me.joshlarson.jlcommon.utilities.Arguments;
|
||||
|
||||
@@ -116,10 +117,10 @@ public class NPCCreator {
|
||||
private static void setFlags(AIObject object, Spawner spawner) {
|
||||
switch (spawner.getSpawnerFlag()) {
|
||||
case AGGRESSIVE:
|
||||
object.setPvpFlags(PvpFlag.AGGRESSIVE);
|
||||
object.setPvpFlags(PvpFlag.CAN_ATTACK_YOU);
|
||||
object.addOptionFlags(OptionFlag.AGGRESSIVE);
|
||||
case ATTACKABLE:
|
||||
object.setPvpFlags(PvpFlag.ATTACKABLE);
|
||||
object.setPvpFlags(PvpFlag.YOU_CAN_ATTACK);
|
||||
object.addOptionFlags(OptionFlag.HAM_BAR);
|
||||
break;
|
||||
case INVULNERABLE:
|
||||
@@ -128,19 +129,14 @@ public class NPCCreator {
|
||||
}
|
||||
}
|
||||
|
||||
private static void setNPCFaction(TangibleObject object, PvpFaction faction, boolean specForce) {
|
||||
if (faction == PvpFaction.NEUTRAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear any existing flags that mark them as attackable
|
||||
object.clearPvpFlags(PvpFlag.ATTACKABLE, PvpFlag.AGGRESSIVE);
|
||||
object.removeOptionFlags(OptionFlag.AGGRESSIVE);
|
||||
|
||||
new FactionIntent(object, faction).broadcast();
|
||||
|
||||
private static void setNPCFaction(TangibleObject object, Faction faction, boolean specForce) {
|
||||
if (specForce) {
|
||||
new FactionIntent(object, PvpStatus.SPECIALFORCES).broadcast();
|
||||
IntentChain.broadcastChain(
|
||||
new FactionIntent(object, faction),
|
||||
new FactionIntent(object, PvpStatus.SPECIALFORCES)
|
||||
);
|
||||
} else {
|
||||
new FactionIntent(object, faction).broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
***********************************************************************************/
|
||||
package com.projectswg.holocore.resources.support.npc.spawn;
|
||||
|
||||
import com.projectswg.common.data.encodables.tangible.PvpFaction;
|
||||
import com.projectswg.common.data.location.Location;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.DataLoader;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.combat.FactionLoader.Faction;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.npc.NpcLoader.CreatureNpcInfo;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.npc.NpcLoader.DroidNpcInfo;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.npc.NpcLoader.HumanoidNpcInfo;
|
||||
@@ -194,7 +194,7 @@ public final class Spawner {
|
||||
return npc.getIffs();
|
||||
}
|
||||
|
||||
public PvpFaction getFaction() {
|
||||
public Faction getFaction() {
|
||||
return npc.getFaction();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (c) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
|
||||
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
|
||||
* Our goal is to create an emulator which will provide a server for players to *
|
||||
* continue playing a game similar to the one they used to play. We are basing *
|
||||
* it on the final publish of the game prior to end-game events. *
|
||||
* *
|
||||
* This file is part of Holocore. *
|
||||
* *
|
||||
* --------------------------------------------------------------------------------*
|
||||
* *
|
||||
* Holocore is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as *
|
||||
* published by the Free Software Foundation, either version 3 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* Holocore is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
***********************************************************************************/
|
||||
package com.projectswg.holocore.resources.support.objects.awareness;
|
||||
|
||||
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
class TerrainMapChunk {
|
||||
|
||||
private static final List<SWGObject> EMPTY_AWARENESS = List.of();
|
||||
|
||||
private final CopyOnWriteArrayList<SWGObject> objects;
|
||||
private final CopyOnWriteArrayList<CreatureObject> creatures;
|
||||
private TerrainMapChunk [] neighbors;
|
||||
|
||||
public TerrainMapChunk() {
|
||||
this.objects = new CopyOnWriteArrayList<>();
|
||||
this.creatures = new CopyOnWriteArrayList<>();
|
||||
this.neighbors = new TerrainMapChunk[]{this};
|
||||
}
|
||||
|
||||
public void link(TerrainMapChunk neighbor) {
|
||||
assert this != neighbor;
|
||||
int length = neighbors.length;
|
||||
neighbors = Arrays.copyOf(neighbors, length+1);
|
||||
neighbors[length] = neighbor;
|
||||
}
|
||||
|
||||
public void addObject(@NotNull SWGObject obj) {
|
||||
for (TerrainMapChunk neighbor : neighbors)
|
||||
neighbor.objects.addIfAbsent(obj);
|
||||
|
||||
if (obj instanceof CreatureObject && ((CreatureObject) obj).isPlayer())
|
||||
creatures.add((CreatureObject) obj);
|
||||
}
|
||||
|
||||
public void removeObject(@NotNull SWGObject obj) {
|
||||
for (TerrainMapChunk neighbor : neighbors)
|
||||
neighbor.objects.remove(obj);
|
||||
|
||||
if (obj instanceof CreatureObject)
|
||||
creatures.remove(obj);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
if (creatures.isEmpty())
|
||||
return;
|
||||
List<CreatureAware> aware = new ArrayList<>(creatures.size());
|
||||
for (CreatureObject creature : creatures) {
|
||||
if (creature.isLoggedInPlayer())
|
||||
aware.add(new CreatureAware(creature));
|
||||
else
|
||||
creature.setAware(AwarenessType.OBJECT, EMPTY_AWARENESS);
|
||||
}
|
||||
|
||||
final CreatureAware [] awareCompiled = aware.toArray(new CreatureAware[0]);
|
||||
for (SWGObject test : objects) {
|
||||
for (CreatureAware creatureAware : awareCompiled)
|
||||
creatureAware.test(test);
|
||||
}
|
||||
|
||||
aware.forEach(CreatureAware::commit);
|
||||
}
|
||||
|
||||
private static class CreatureAware {
|
||||
|
||||
private final CreatureObject creature;
|
||||
private final List<SWGObject> aware;
|
||||
|
||||
public CreatureAware(CreatureObject creature) {
|
||||
this.creature = creature;
|
||||
this.aware = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void test(SWGObject test) {
|
||||
if (creature.isWithinAwarenessRange(test))
|
||||
aware.add(test);
|
||||
}
|
||||
|
||||
public void commit() {
|
||||
creature.setAware(AwarenessType.OBJECT, aware);
|
||||
creature.flushAwareness();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (c) 2018 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
|
||||
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
|
||||
* Our goal is to create an emulator which will provide a server for players to *
|
||||
* continue playing a game similar to the one they used to play. We are basing *
|
||||
* it on the final publish of the game prior to end-game events. *
|
||||
* *
|
||||
* This file is part of Holocore. *
|
||||
* *
|
||||
* --------------------------------------------------------------------------------*
|
||||
* *
|
||||
* Holocore is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as *
|
||||
* published by the Free Software Foundation, either version 3 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* Holocore is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with Holocore. If not, see <http:></http:>//www.gnu.org/licenses/>. *
|
||||
*/
|
||||
package com.projectswg.holocore.resources.support.objects.awareness
|
||||
|
||||
import com.projectswg.holocore.resources.support.objects.swg.SWGObject
|
||||
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject
|
||||
import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject
|
||||
import com.projectswg.holocore.resources.support.objects.swg.tangible.OptionFlag
|
||||
import java.util.*
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
internal class TerrainMapChunk {
|
||||
|
||||
private val objects: CopyOnWriteArrayList<SWGObject> = CopyOnWriteArrayList()
|
||||
private val npcObjects: CopyOnWriteArrayList<SWGObject> = CopyOnWriteArrayList()
|
||||
private val creatures: CopyOnWriteArrayList<CreatureAware> = CopyOnWriteArrayList()
|
||||
private val npcs: CopyOnWriteArrayList<NpcAware> = CopyOnWriteArrayList()
|
||||
private var neighbors: Array<TerrainMapChunk?> = arrayOf(this)
|
||||
|
||||
fun link(neighbor: TerrainMapChunk) {
|
||||
assert(this !== neighbor)
|
||||
val length = neighbors.size
|
||||
neighbors = neighbors.copyOf(length + 1)
|
||||
neighbors[length] = neighbor
|
||||
}
|
||||
|
||||
fun addObject(obj: SWGObject) {
|
||||
for (neighbor in neighbors)
|
||||
neighbor!!.objects.addIfAbsent(obj)
|
||||
|
||||
if (obj is AIObject && !obj.hasOptionFlags(OptionFlag.INVULNERABLE)) {
|
||||
for (neighbor in neighbors)
|
||||
neighbor!!.npcObjects.addIfAbsent(obj)
|
||||
npcs.add(NpcAware(obj))
|
||||
} else if (obj is CreatureObject && obj.isPlayer) {
|
||||
for (neighbor in neighbors)
|
||||
neighbor!!.npcObjects.addIfAbsent(obj)
|
||||
creatures.add(CreatureAware(obj))
|
||||
}
|
||||
}
|
||||
|
||||
fun removeObject(obj: SWGObject) {
|
||||
for (neighbor in neighbors) {
|
||||
neighbor!!.objects.remove(obj)
|
||||
neighbor.npcObjects.remove(obj)
|
||||
}
|
||||
|
||||
if (obj is CreatureObject) {
|
||||
creatures.removeIf { it.creature == obj }
|
||||
npcs.removeIf { it.creature == obj }
|
||||
}
|
||||
}
|
||||
|
||||
fun update() {
|
||||
if (creatures.isEmpty())
|
||||
return
|
||||
|
||||
creatures.forEach { it.test(objects) }
|
||||
npcs.forEach { it.test(npcObjects) }
|
||||
}
|
||||
|
||||
private class CreatureAware(val creature: CreatureObject) {
|
||||
|
||||
private val aware = ArrayList<SWGObject>()
|
||||
|
||||
fun test(tests: List<SWGObject>) {
|
||||
aware.clear()
|
||||
for (test in tests) {
|
||||
if (creature.isWithinAwarenessRange(test)) {
|
||||
aware.add(test)
|
||||
}
|
||||
}
|
||||
if (aware.isEmpty())
|
||||
creature.setAware(AwarenessType.OBJECT, EMPTY_AWARENESS)
|
||||
else
|
||||
creature.setAware(AwarenessType.OBJECT, ArrayList(aware))
|
||||
creature.flushAwareness()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class NpcAware(val creature: CreatureObject) {
|
||||
|
||||
private val aware = ArrayList<SWGObject>()
|
||||
|
||||
fun test(tests: List<SWGObject>) {
|
||||
aware.clear()
|
||||
for (test in tests) {
|
||||
if (creature.isWithinAwarenessRange(test)) {
|
||||
aware.add(test)
|
||||
}
|
||||
}
|
||||
if (aware.isEmpty())
|
||||
creature.setAware(AwarenessType.OBJECT, EMPTY_AWARENESS)
|
||||
else
|
||||
creature.setAware(AwarenessType.OBJECT, ArrayList(aware))
|
||||
creature.flushAwareness()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val EMPTY_AWARENESS = listOf<SWGObject>()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1004,6 +1004,22 @@ public abstract class SWGObject extends BaselineObject implements Comparable<SWG
|
||||
return getAware(AwarenessType.OBJECT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an object has entered this object's awareness
|
||||
* @param aware the object that has entered awareness
|
||||
*/
|
||||
public void onObjectEnteredAware(SWGObject aware) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an object has exited this object's awareness
|
||||
* @param aware the object that has entered awareness
|
||||
*/
|
||||
public void onObjectExitedAware(SWGObject aware) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when this object has been moved somehow via awareness. This will
|
||||
* not run on any buildout or snapshot object!
|
||||
|
||||
@@ -30,7 +30,6 @@ import com.projectswg.common.data.CRC;
|
||||
import com.projectswg.common.data.HologramColour;
|
||||
import com.projectswg.common.data.encodables.mongo.MongoData;
|
||||
import com.projectswg.common.data.encodables.tangible.Posture;
|
||||
import com.projectswg.common.data.encodables.tangible.PvpFlag;
|
||||
import com.projectswg.common.data.encodables.tangible.Race;
|
||||
import com.projectswg.common.data.location.Location;
|
||||
import com.projectswg.common.data.location.Terrain;
|
||||
@@ -101,10 +100,12 @@ public class CreatureObject extends TangibleObject {
|
||||
|
||||
public void flushAwareness() {
|
||||
Player owner = getOwnerShallow();
|
||||
if (getTerrain() == Terrain.GONE || owner == null || owner.getPlayerState() == PlayerState.DISCONNECTED)
|
||||
return;
|
||||
awareness.flush(owner);
|
||||
sendAndFlushAllDeltas();
|
||||
if (getTerrain() == Terrain.GONE || owner == null || owner.getPlayerState() == PlayerState.DISCONNECTED) {
|
||||
awareness.flushNoPlayer();
|
||||
} else {
|
||||
awareness.flush(owner);
|
||||
sendAndFlushAllDeltas();
|
||||
}
|
||||
}
|
||||
|
||||
public void resetObjectsAware() {
|
||||
@@ -209,7 +210,7 @@ public class CreatureObject extends TangibleObject {
|
||||
getAllChildren(children, obj);
|
||||
}
|
||||
|
||||
public final boolean isWithinAwarenessRange(SWGObject target) {
|
||||
public boolean isWithinAwarenessRange(SWGObject target) {
|
||||
assert isPlayer();
|
||||
|
||||
Player owner = getOwnerShallow();
|
||||
@@ -957,12 +958,6 @@ public class CreatureObject extends TangibleObject {
|
||||
damageMap.put(attacker, damage);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAttackable(CreatureObject otherObject) {
|
||||
Posture otherPosture = otherObject.getPosture();
|
||||
|
||||
return isEnemyOf(otherObject) && otherPosture != Posture.INCAPACITATED && otherPosture != Posture.DEAD;
|
||||
}
|
||||
|
||||
public boolean hasSentDuelRequestToPlayer(CreatureObject player) {
|
||||
return sentDuels.contains(player);
|
||||
@@ -980,46 +975,6 @@ public class CreatureObject extends TangibleObject {
|
||||
sentDuels.remove(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Members of the same faction might be enemies, if there are players
|
||||
* involved.
|
||||
* @param otherObject
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean isEnemyOf(TangibleObject otherObject) {
|
||||
if (!(otherObject instanceof CreatureObject)) {
|
||||
// If the other object isn't a creature, then our job here's done
|
||||
return super.isEnemyOf(otherObject);
|
||||
}
|
||||
|
||||
// TODO bounty hunting
|
||||
// TODO pets, vehicles etc having same flagging as their owner
|
||||
// TODO guild wars
|
||||
|
||||
if (isDuelingPlayer((CreatureObject)otherObject)) {
|
||||
// Dueling is an exception, since it allows allies to be enemies
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.isEnemyOf(otherObject); // Default
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PvpFlag> getPvpFlagsFor(TangibleObject observer) {
|
||||
Set<PvpFlag> flags = super.getPvpFlagsFor(observer);
|
||||
|
||||
if (observer instanceof CreatureObject) {
|
||||
if (isDuelingPlayer((CreatureObject) observer)) {
|
||||
flags.add(PvpFlag.ATTACKABLE);
|
||||
flags.add(PvpFlag.ENEMY);
|
||||
flags.add(PvpFlag.DUEL);
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
public boolean isBaselinesSent(SWGObject obj) {
|
||||
return awareness.isAware(obj);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,53 @@ public class CreatureObjectAwareness {
|
||||
finalTeleportPacket.set(new DataTransformWithParent(creature.getObjectId(), 0, creature.getNextUpdateCount(), parent.getObjectId(), location, 0));
|
||||
}
|
||||
|
||||
public synchronized void flushNoPlayer() {
|
||||
Set<SWGObject> newAware = creature.getAware();
|
||||
List<SWGObject> create = new ArrayList<>();
|
||||
List<SWGObject> added = new ArrayList<>();
|
||||
|
||||
// Create Deltas
|
||||
for (SWGObject createCandidate : newAware) {
|
||||
if (aware.contains(createCandidate))
|
||||
continue;
|
||||
added.add(createCandidate);
|
||||
}
|
||||
getCreateList(create, added);
|
||||
|
||||
// Server awareness update
|
||||
for (SWGObject add : create) { // Using "create" here because it's filtered to ensure no crashes
|
||||
aware.add(add);
|
||||
awareIds.add(add.getObjectId());
|
||||
add.addObserver(creature);
|
||||
creature.onObjectEnteredAware(add);
|
||||
}
|
||||
|
||||
List<SWGObject> destroy = new ArrayList<>();
|
||||
List<SWGObject> removed = new ArrayList<>();
|
||||
|
||||
// Create Deltas
|
||||
for (SWGObject destroyCandidate : aware) {
|
||||
if (newAware.contains(destroyCandidate))
|
||||
continue;
|
||||
removed.add(destroyCandidate);
|
||||
}
|
||||
getDestroyList(destroy, removed);
|
||||
|
||||
// Remove destroyed objects so that nobody tries to send a packet to us after we send the destroy
|
||||
for (Iterator<SWGObject> it = aware.iterator(); it.hasNext(); ) {
|
||||
SWGObject currentAware = it.next();
|
||||
for (SWGObject remove : destroy) { // Since the "create" is filtered, aware could also have been filtered
|
||||
if (isParent(currentAware, remove)) {
|
||||
it.remove();
|
||||
awareIds.remove(currentAware.getObjectId());
|
||||
remove.removeObserver(creature);
|
||||
creature.onObjectExitedAware(remove);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void flush(@NotNull Player target) {
|
||||
Set<SWGObject> newAware = creature.getAware();
|
||||
flushCreates(target, newAware);
|
||||
@@ -106,6 +153,7 @@ public class CreatureObjectAwareness {
|
||||
aware.add(add);
|
||||
awareIds.add(add.getObjectId());
|
||||
add.addObserver(creature);
|
||||
creature.onObjectEnteredAware(add);
|
||||
}
|
||||
|
||||
// Hope we didn't screw anything up
|
||||
@@ -133,6 +181,7 @@ public class CreatureObjectAwareness {
|
||||
it.remove();
|
||||
awareIds.remove(currentAware.getObjectId());
|
||||
remove.removeObserver(creature);
|
||||
creature.onObjectExitedAware(remove);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,18 +26,15 @@
|
||||
***********************************************************************************/
|
||||
package com.projectswg.holocore.resources.support.objects.swg.custom;
|
||||
|
||||
import com.projectswg.common.data.encodables.tangible.Posture;
|
||||
import com.projectswg.common.data.encodables.tangible.PvpFlag;
|
||||
import com.projectswg.common.network.packets.swg.zone.baselines.Baseline.BaselineType;
|
||||
import com.projectswg.holocore.intents.support.npc.ai.ScheduleNpcModeIntent;
|
||||
import com.projectswg.holocore.intents.support.npc.ai.StartNpcCombatIntent;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.npc.NpcStaticSpawnLoader.SpawnerFlag;
|
||||
import com.projectswg.holocore.resources.support.npc.spawn.Spawner;
|
||||
import com.projectswg.holocore.resources.support.objects.ObjectCreator;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureState;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.tangible.OptionFlag;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.weapon.WeaponObject;
|
||||
import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool;
|
||||
import me.joshlarson.jlcommon.log.Log;
|
||||
@@ -81,47 +78,71 @@ public class AIObject extends CreatureObject {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onObjectMoveInAware(SWGObject aware) {
|
||||
if (aware.getBaselineType() != BaselineType.CREO)
|
||||
public void onObjectEnteredAware(SWGObject aware) {
|
||||
if (aware.getBaselineType() != BaselineType.CREO || hasOptionFlags(OptionFlag.INVULNERABLE))
|
||||
return;
|
||||
|
||||
if (((CreatureObject) aware).isStatesBitmask(CreatureState.MOUNTED_CREATURE)) {
|
||||
aware = aware.getSlottedObject("rider");
|
||||
if (aware == null)
|
||||
if (!(aware instanceof CreatureObject))
|
||||
return;
|
||||
}
|
||||
CreatureObject player = (CreatureObject) aware;
|
||||
if (!player.isLoggedInPlayer())
|
||||
if (player.hasOptionFlags(OptionFlag.INVULNERABLE))
|
||||
return;
|
||||
|
||||
playersNearby.add(player);
|
||||
if (activeMode != null)
|
||||
activeMode.onPlayerEnterAware(player, flatDistanceTo(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onObjectExitedAware(SWGObject aware) {
|
||||
if (aware.getBaselineType() != BaselineType.CREO)
|
||||
return;
|
||||
|
||||
if (((CreatureObject) aware).isStatesBitmask(CreatureState.MOUNTED_CREATURE)) {
|
||||
aware = aware.getSlottedObject("rider");
|
||||
if (!(aware instanceof CreatureObject))
|
||||
return;
|
||||
}
|
||||
playersNearby.remove(aware);
|
||||
if (activeMode != null)
|
||||
activeMode.onPlayerExitAware((CreatureObject) aware);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onObjectMoveInAware(SWGObject aware) {
|
||||
if (aware.getBaselineType() != BaselineType.CREO)
|
||||
return;
|
||||
|
||||
if (((CreatureObject) aware).isStatesBitmask(CreatureState.MOUNTED_CREATURE)) {
|
||||
aware = aware.getSlottedObject("rider");
|
||||
if (!(aware instanceof CreatureObject))
|
||||
return;
|
||||
}
|
||||
CreatureObject player = (CreatureObject) aware;
|
||||
if (player.hasOptionFlags(OptionFlag.INVULNERABLE) || !playersNearby.contains(aware) || !isAttackable(player))
|
||||
return;
|
||||
|
||||
double distance = getLocation().flatDistanceTo(aware.getLocation());
|
||||
NpcMode activeMode = this.activeMode;
|
||||
if (distance <= 100 && player.getPosture() != Posture.INCAPACITATED && player.getPosture() != Posture.DEAD) {
|
||||
if (playersNearby.add(player)) {
|
||||
if (activeMode != null)
|
||||
activeMode.onPlayerEnterAware(player, distance);
|
||||
} else {
|
||||
if (activeMode != null) {
|
||||
activeMode.onPlayerMoveInAware(player, distance);
|
||||
if (getSpawner().getSpawnerFlag() == SpawnerFlag.AGGRESSIVE && distance < getSpawner().getAggressiveRadius() && isEnemyOf(player) && isLineOfSight(player)) {
|
||||
StartNpcCombatIntent.broadcast(this, List.of(player));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (playersNearby.remove(player)) {
|
||||
if (activeMode != null)
|
||||
activeMode.onPlayerExitAware(player);
|
||||
}
|
||||
double maxAggroDistance;
|
||||
if (player.isLoggedInPlayer())
|
||||
maxAggroDistance = getSpawner().getAggressiveRadius();
|
||||
else if (!player.isPlayer())
|
||||
maxAggroDistance = 30;
|
||||
else
|
||||
maxAggroDistance = -1; // Ensures the following if-statement will fail and remove the player from the list
|
||||
|
||||
if (distance <= maxAggroDistance && isLineOfSight(player)) {
|
||||
StartNpcCombatIntent.broadcast(this, List.of(player));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnemyOf(TangibleObject obj) {
|
||||
Posture myPosture = getPosture();
|
||||
if (myPosture == Posture.INCAPACITATED || myPosture == Posture.DEAD || !(obj instanceof CreatureObject))
|
||||
return false;
|
||||
Posture theirPosture = ((CreatureObject) obj).getPosture();
|
||||
return (theirPosture != Posture.INCAPACITATED || hasPvpFlag(PvpFlag.AGGRESSIVE)) && theirPosture != Posture.DEAD && super.isEnemyOf(obj);
|
||||
public boolean isWithinAwarenessRange(SWGObject target) {
|
||||
assert target instanceof CreatureObject;
|
||||
return isAttackable((CreatureObject) target) && flatDistanceTo(target) <= 50;
|
||||
}
|
||||
|
||||
public void setSpawner(Spawner spawner) {
|
||||
|
||||
@@ -38,14 +38,18 @@ import com.projectswg.common.network.packets.swg.zone.baselines.Baseline.Baselin
|
||||
import com.projectswg.holocore.intents.gameplay.gcw.faction.FactionIntent;
|
||||
import com.projectswg.holocore.intents.gameplay.gcw.faction.FactionIntent.FactionIntentType;
|
||||
import com.projectswg.holocore.intents.support.objects.swg.DestroyObjectIntent;
|
||||
import com.projectswg.holocore.resources.gameplay.combat.EnemyProcessor;
|
||||
import com.projectswg.holocore.resources.support.data.collections.SWGMap;
|
||||
import com.projectswg.holocore.resources.support.data.collections.SWGSet;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.ServerData;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.combat.FactionLoader.Faction;
|
||||
import com.projectswg.holocore.resources.support.global.network.BaselineBuilder;
|
||||
import com.projectswg.holocore.resources.support.global.player.Player;
|
||||
import com.projectswg.holocore.resources.support.objects.permissions.ContainerResult;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -58,7 +62,7 @@ public class TangibleObject extends SWGObject {
|
||||
private int condition = 0;
|
||||
private Set<PvpFlag> pvpFlags = EnumSet.noneOf(PvpFlag.class);
|
||||
private PvpStatus pvpStatus = PvpStatus.COMBATANT;
|
||||
private PvpFaction pvpFaction = PvpFaction.NEUTRAL;
|
||||
private Faction faction = null;
|
||||
private boolean visibleGmOnly = true;
|
||||
private byte [] objectEffects = new byte[0];
|
||||
private int optionFlags = 0;
|
||||
@@ -185,13 +189,22 @@ public class TangibleObject extends SWGObject {
|
||||
sendDelta(3, 5, pvpStatus.getValue());
|
||||
}
|
||||
|
||||
public PvpFaction getPvpFaction() {
|
||||
return pvpFaction;
|
||||
@Nullable
|
||||
public Faction getFaction() {
|
||||
return faction;
|
||||
}
|
||||
|
||||
public void setPvpFaction(PvpFaction pvpFaction) {
|
||||
this.pvpFaction = pvpFaction;
|
||||
public PvpFaction getPvpFaction() {
|
||||
Faction faction = this.faction;
|
||||
if (faction == null)
|
||||
return PvpFaction.NEUTRAL;
|
||||
return faction.getPvpFaction();
|
||||
}
|
||||
|
||||
public void setFaction(Faction faction) {
|
||||
this.faction = faction;
|
||||
|
||||
PvpFaction pvpFaction = faction == null ? PvpFaction.NEUTRAL : faction.getPvpFaction();
|
||||
sendDelta(3, 4, pvpFaction.getCrc());
|
||||
}
|
||||
|
||||
@@ -281,6 +294,10 @@ public class TangibleObject extends SWGObject {
|
||||
}
|
||||
sendDelta(3, 8, optionFlags);
|
||||
}
|
||||
|
||||
public boolean hasOptionFlags(OptionFlag option) {
|
||||
return (optionFlags & option.getFlag()) != 0;
|
||||
}
|
||||
|
||||
public boolean hasOptionFlags(OptionFlag ... options) {
|
||||
for (OptionFlag option : options) {
|
||||
@@ -341,63 +358,19 @@ public class TangibleObject extends SWGObject {
|
||||
* @param otherObject
|
||||
* @return true if this object is an enemy of {@code otherObject}
|
||||
*/
|
||||
public boolean isEnemyOf(TangibleObject otherObject) {
|
||||
if (otherObject.hasOptionFlags(OptionFlag.INVULNERABLE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (otherObject.hasPvpFlag(PvpFlag.ATTACKABLE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PvpFaction ourFaction = getPvpFaction();
|
||||
PvpFaction otherFaction = otherObject.getPvpFaction();
|
||||
|
||||
if (ourFaction == PvpFaction.NEUTRAL || otherFaction == PvpFaction.NEUTRAL) {
|
||||
// Neutrals are always excluded from factional combat, unless they're both neutral
|
||||
return ourFaction == otherFaction;
|
||||
}
|
||||
|
||||
// At this point, neither are neutral
|
||||
|
||||
if (ourFaction == otherFaction) {
|
||||
// Members of the same faction are not enemies
|
||||
return false;
|
||||
}
|
||||
|
||||
// At this point, they're members of opposing factions
|
||||
|
||||
PvpStatus ourStatus = getPvpStatus();
|
||||
PvpStatus otherStatus = otherObject.getPvpStatus();
|
||||
|
||||
if (ourStatus == PvpStatus.ONLEAVE || otherStatus == PvpStatus.ONLEAVE) {
|
||||
// They're of opposing factions, but one of them on leave
|
||||
return false;
|
||||
}
|
||||
|
||||
// At this point, they're both either combatant or special forces
|
||||
|
||||
boolean ourPlayer = getSlottedObject("ghost") != null;
|
||||
boolean otherPlayer = otherObject.getSlottedObject("ghost") != null;
|
||||
|
||||
if (ourPlayer && otherPlayer) {
|
||||
// Two players can only attack each other if both are Special Forces
|
||||
return ourStatus == PvpStatus.SPECIALFORCES && otherStatus == PvpStatus.SPECIALFORCES;
|
||||
} else {
|
||||
// At this point, we're dealing with player vs npc or npc vs npc
|
||||
// In this case, they just need to not be on leave and we've already established this
|
||||
return true;
|
||||
}
|
||||
public boolean isAttackable(TangibleObject otherObject) {
|
||||
return EnemyProcessor.INSTANCE.isAttackable(this, otherObject);
|
||||
}
|
||||
|
||||
public Set<PvpFlag> getPvpFlagsFor(TangibleObject observer) {
|
||||
Set<PvpFlag> pvpFlags = EnumSet.copyOf(observer.pvpFlags); // More efficient behind the scenes
|
||||
Set<PvpFlag> pvpFlags = EnumSet.noneOf(PvpFlag.class); // More efficient behind the scenes
|
||||
|
||||
if (isEnemyOf(observer) && getPvpFaction() != PvpFaction.NEUTRAL && observer.getPvpFaction() != PvpFaction.NEUTRAL) {
|
||||
pvpFlags.add(PvpFlag.AGGRESSIVE);
|
||||
pvpFlags.add(PvpFlag.ATTACKABLE);
|
||||
pvpFlags.add(PvpFlag.ENEMY);
|
||||
}
|
||||
if (isAttackable(observer))
|
||||
pvpFlags.add(PvpFlag.YOU_CAN_ATTACK);
|
||||
if (observer.isAttackable(this))
|
||||
pvpFlags.add(PvpFlag.CAN_ATTACK_YOU);
|
||||
if (observer instanceof CreatureObject && ((CreatureObject) observer).isPlayer())
|
||||
pvpFlags.add(PvpFlag.PLAYER);
|
||||
|
||||
return pvpFlags;
|
||||
}
|
||||
@@ -413,7 +386,7 @@ public class TangibleObject extends SWGObject {
|
||||
@Override
|
||||
protected void createBaseline3(Player target, BaselineBuilder bb) {
|
||||
super.createBaseline3(target, bb); // 4 variables - BASE3 (4)
|
||||
bb.addInt(pvpFaction.getCrc()); // Faction - 4
|
||||
bb.addInt(getPvpFaction().getCrc()); // Faction - 4
|
||||
bb.addInt(pvpStatus.getValue()); // Faction Status - 5
|
||||
bb.addObject(appearanceData); // - 6
|
||||
bb.addInt(0); // Component customization (Set, Integer) - 7
|
||||
@@ -445,7 +418,7 @@ public class TangibleObject extends SWGObject {
|
||||
@Override
|
||||
protected void parseBaseline3(NetBuffer buffer) {
|
||||
super.parseBaseline3(buffer);
|
||||
pvpFaction = PvpFaction.getFactionForCrc(buffer.getInt());
|
||||
faction = ServerData.INSTANCE.getFactions().getFaction(PvpFaction.getFactionForCrc(buffer.getInt()).name().toLowerCase(Locale.US));
|
||||
pvpStatus = PvpStatus.getStatusForValue(buffer.getInt());
|
||||
appearanceData.decode(buffer);
|
||||
SWGSet.getSwgSet(buffer, 3, 7, Integer.class);
|
||||
@@ -477,7 +450,8 @@ public class TangibleObject extends SWGObject {
|
||||
stream.addInt(condition);
|
||||
stream.addInt(pvpFlags.stream().mapToInt(PvpFlag::getBitmask).reduce(0, (a, b) -> a | b));
|
||||
stream.addAscii(pvpStatus.name());
|
||||
stream.addAscii(pvpFaction.name());
|
||||
Faction faction = this.faction;
|
||||
stream.addAscii(faction == null ? "neutral" : faction.getName());
|
||||
stream.addBoolean(visibleGmOnly);
|
||||
stream.addArray(objectEffects);
|
||||
stream.addInt(optionFlags);
|
||||
@@ -499,7 +473,7 @@ public class TangibleObject extends SWGObject {
|
||||
condition = stream.getInt();
|
||||
pvpFlags = PvpFlag.getFlags(stream.getInt());
|
||||
pvpStatus = PvpStatus.valueOf(stream.getAscii());
|
||||
pvpFaction = PvpFaction.valueOf(stream.getAscii());
|
||||
faction = ServerData.INSTANCE.getFactions().getFaction(stream.getAscii().toLowerCase(Locale.US));
|
||||
visibleGmOnly = stream.getBoolean();
|
||||
objectEffects = stream.getArray();
|
||||
optionFlags = stream.getInt();
|
||||
@@ -515,7 +489,8 @@ public class TangibleObject extends SWGObject {
|
||||
data.putInteger("condition", condition);
|
||||
data.putInteger("pvpFlags", pvpFlags.stream().mapToInt(PvpFlag::getBitmask).reduce(0, (a, b) -> a | b));
|
||||
data.putString("pvpStatus", pvpStatus.name());
|
||||
data.putString("pvpFaction", pvpFaction.name());
|
||||
Faction faction = this.faction;
|
||||
data.putString("faction", faction == null ? "neutral" : faction.getName());
|
||||
data.putBoolean("visibleGmOnly", visibleGmOnly);
|
||||
data.putByteArray("objectEffects", objectEffects);
|
||||
data.putInteger("optionFlags", optionFlags);
|
||||
@@ -531,7 +506,7 @@ public class TangibleObject extends SWGObject {
|
||||
condition = data.getInteger("condition", 0);
|
||||
pvpFlags.addAll(PvpFlag.getFlags(data.getInteger("pvpFlags", 0)));
|
||||
pvpStatus = PvpStatus.valueOf(data.getString("pvpStatus", "COMBATANT"));
|
||||
pvpFaction = PvpFaction.valueOf(data.getString("pvpFaction", "NEUTRAL"));
|
||||
faction = ServerData.INSTANCE.getFactions().getFaction(data.getString(data.containsKey("pvpFaction") ? "pvpFaction" : "faction", "neutral"));
|
||||
visibleGmOnly = data.getBoolean("visibleGmOnly", false);
|
||||
objectEffects = data.getByteArray("objectEffects");
|
||||
optionFlags = data.getInteger("optionFlags", 0);
|
||||
|
||||
@@ -56,7 +56,7 @@ public class CombatDeathblowService extends Service {
|
||||
}
|
||||
|
||||
// They must be enemies
|
||||
if (!corpse.isEnemyOf(killer)) {
|
||||
if (!corpse.isAttackable(killer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package com.projectswg.holocore.services.gameplay.combat;
|
||||
|
||||
import com.projectswg.holocore.intents.gameplay.combat.EnterCombatIntent;
|
||||
import com.projectswg.holocore.intents.gameplay.combat.ExitCombatIntent;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject;
|
||||
import com.projectswg.holocore.services.support.objects.ObjectStorageService.ObjectLookup;
|
||||
import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool;
|
||||
import me.joshlarson.jlcommon.control.IntentHandler;
|
||||
import me.joshlarson.jlcommon.control.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CombatStatusService extends Service {
|
||||
|
||||
private final Set<CreatureObject> inCombat;
|
||||
private final ScheduledThreadPool executor;
|
||||
|
||||
public CombatStatusService() {
|
||||
this.inCombat = ConcurrentHashMap.newKeySet();
|
||||
this.executor = new ScheduledThreadPool(1, 3, "combat-status-service");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
executor.start();
|
||||
executor.executeWithFixedRate(1000, 1000, this::periodicCombatStatusChecks);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop() {
|
||||
executor.stop();
|
||||
executor.awaitTermination(1000);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void periodicCombatStatusChecks() {
|
||||
for (CreatureObject creature : inCombat) {
|
||||
if (creature.getTimeSinceLastCombat() >= 10E3)
|
||||
ExitCombatIntent.broadcast(creature);
|
||||
}
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleEnterCombatIntent(EnterCombatIntent eci) {
|
||||
CreatureObject source = eci.getSource();
|
||||
CreatureObject target = eci.getTarget();
|
||||
if (source.isInCombat())
|
||||
return;
|
||||
|
||||
source.setInCombat(true);
|
||||
target.addDefender(source);
|
||||
source.addDefender(target);
|
||||
inCombat.add(source);
|
||||
inCombat.add(target);
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private void handleExitCombatIntent(ExitCombatIntent eci) {
|
||||
CreatureObject source = eci.getSource();
|
||||
List<CreatureObject> defenders = source.getDefenders().stream().map(ObjectLookup::getObjectById).map(CreatureObject.class::cast).collect(Collectors.toList());
|
||||
source.clearDefenders();
|
||||
for (CreatureObject defender : defenders) {
|
||||
defender.removeDefender(source);
|
||||
if (!defender.hasDefenders())
|
||||
ExitCombatIntent.broadcast(defender);
|
||||
}
|
||||
if (source.hasDefenders())
|
||||
return;
|
||||
|
||||
source.setInCombat(false);
|
||||
inCombat.remove(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.projectswg.holocore.services.gameplay.combat
|
||||
|
||||
import com.projectswg.holocore.intents.gameplay.combat.EnterCombatIntent
|
||||
import com.projectswg.holocore.intents.gameplay.combat.ExitCombatIntent
|
||||
import com.projectswg.holocore.intents.gameplay.combat.duel.DuelPlayerIntent
|
||||
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject
|
||||
import com.projectswg.holocore.services.support.objects.ObjectStorageService.ObjectLookup
|
||||
import me.joshlarson.jlcommon.concurrency.ScheduledThreadPool
|
||||
import me.joshlarson.jlcommon.control.IntentHandler
|
||||
import me.joshlarson.jlcommon.control.Service
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.stream.Collectors
|
||||
|
||||
class CombatStatusService : Service() {
|
||||
|
||||
private val inCombat: MutableSet<CreatureObject>
|
||||
private val duels: MutableSet<DuelInstance>
|
||||
private val executor: ScheduledThreadPool
|
||||
|
||||
init {
|
||||
this.inCombat = ConcurrentHashMap.newKeySet()
|
||||
this.duels = ConcurrentHashMap.newKeySet()
|
||||
this.executor = ScheduledThreadPool(1, 3, "combat-status-service")
|
||||
}
|
||||
|
||||
override fun start(): Boolean {
|
||||
executor.start()
|
||||
executor.executeWithFixedRate(1000, 1000) { this.periodicCombatStatusChecks() }
|
||||
return true
|
||||
}
|
||||
|
||||
override fun stop(): Boolean {
|
||||
executor.stop()
|
||||
executor.awaitTermination(1000)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun periodicCombatStatusChecks() {
|
||||
for (creature in inCombat) {
|
||||
if (creature.timeSinceLastCombat >= 10E3 && !isDueling(creature))
|
||||
ExitCombatIntent.broadcast(creature)
|
||||
}
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private fun handleEnterCombatIntent(eci: EnterCombatIntent) {
|
||||
val source = eci.source
|
||||
val target = eci.target
|
||||
if (source.isInCombat)
|
||||
return
|
||||
|
||||
source.isInCombat = true
|
||||
target.addDefender(source)
|
||||
source.addDefender(target)
|
||||
inCombat.add(source)
|
||||
inCombat.add(target)
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private fun handleExitCombatIntent(eci: ExitCombatIntent) {
|
||||
val source = eci.source
|
||||
val defenders = source.defenders.stream().map { ObjectLookup.getObjectById(it) }.map { CreatureObject::class.java.cast(it) }.collect(Collectors.toList())
|
||||
source.clearDefenders()
|
||||
for (defender in defenders) {
|
||||
defender.removeDefender(source)
|
||||
if (!defender.hasDefenders())
|
||||
ExitCombatIntent.broadcast(defender)
|
||||
}
|
||||
endDuels(source)
|
||||
if (source.hasDefenders())
|
||||
return
|
||||
|
||||
source.isInCombat = false
|
||||
inCombat.remove(source)
|
||||
}
|
||||
|
||||
@IntentHandler
|
||||
private fun handleDuelPlayerIntent(dpi: DuelPlayerIntent) {
|
||||
when (dpi.eventType) {
|
||||
DuelPlayerIntent.DuelEventType.BEGINDUEL -> {
|
||||
val duel = if (dpi.sender.objectId < dpi.reciever.objectId) DuelInstance(dpi.sender, dpi.reciever) else DuelInstance(dpi.reciever, dpi.sender)
|
||||
if (duels.add(duel)) {
|
||||
EnterCombatIntent.broadcast(duel.playerA, duel.playerB)
|
||||
EnterCombatIntent.broadcast(duel.playerB, duel.playerA)
|
||||
}
|
||||
}
|
||||
DuelPlayerIntent.DuelEventType.END -> {
|
||||
duels.remove(if (dpi.sender.objectId < dpi.reciever.objectId) DuelInstance(dpi.sender, dpi.reciever) else DuelInstance(dpi.reciever, dpi.sender))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isDueling(player: CreatureObject): Boolean {
|
||||
for (duel in duels) {
|
||||
if ((duel.playerA == player || duel.playerB == player) && duel.isDueling)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun endDuels(player: CreatureObject) {
|
||||
for (duel in duels) {
|
||||
if (duel.playerA == player || duel.playerB == player) {
|
||||
DuelPlayerIntent(duel.playerA, duel.playerB, DuelPlayerIntent.DuelEventType.END).broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class DuelInstance(val playerA: CreatureObject, val playerB: CreatureObject) {
|
||||
|
||||
val isDueling: Boolean
|
||||
get() = playerA.isDuelingPlayer(playerB)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -34,10 +34,7 @@ import com.projectswg.common.data.encodables.oob.StringId;
|
||||
import com.projectswg.common.network.packets.swg.zone.object_controller.ShowFlyText;
|
||||
import com.projectswg.common.network.packets.swg.zone.object_controller.ShowFlyText.Scale;
|
||||
import com.projectswg.common.network.packets.swg.zone.object_controller.combat.CombatAction;
|
||||
import com.projectswg.common.network.packets.swg.zone.object_controller.combat.CombatAction.Defender;
|
||||
import com.projectswg.common.network.packets.swg.zone.object_controller.combat.CombatSpam;
|
||||
import com.projectswg.holocore.intents.gameplay.combat.EnterCombatIntent;
|
||||
import com.projectswg.holocore.intents.gameplay.combat.RequestCreatureDeathIntent;
|
||||
import com.projectswg.holocore.intents.gameplay.combat.buffs.BuffIntent;
|
||||
import com.projectswg.holocore.resources.support.global.commands.CombatCommand;
|
||||
import com.projectswg.holocore.resources.support.global.commands.Command;
|
||||
@@ -46,11 +43,7 @@ import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureOb
|
||||
import com.projectswg.holocore.resources.support.objects.swg.tangible.TangibleObject;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.weapon.WeaponObject;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CombatCommandCommon {
|
||||
|
||||
@@ -98,7 +91,7 @@ public class CombatCommandCommon {
|
||||
if (!(target instanceof TangibleObject))
|
||||
return CombatStatus.INVALID_TARGET;
|
||||
|
||||
if (!source.isEnemyOf((TangibleObject) target)) {
|
||||
if (!source.isAttackable((TangibleObject) target)) {
|
||||
return CombatStatus.INVALID_TARGET;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ import com.projectswg.common.network.packets.swg.zone.object_controller.ShowFlyT
|
||||
import com.projectswg.common.network.packets.swg.zone.object_controller.ShowFlyText.Scale;
|
||||
import com.projectswg.common.network.packets.swg.zone.object_controller.combat.CombatAction;
|
||||
import com.projectswg.common.network.packets.swg.zone.object_controller.combat.CombatAction.Defender;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.StandardLog;
|
||||
import com.projectswg.holocore.resources.support.global.commands.CombatCommand;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject;
|
||||
@@ -84,7 +83,7 @@ enum CombatCommandHeal implements CombatCommandHitType {
|
||||
|
||||
CreatureObject creatureTarget = (CreatureObject) target;
|
||||
|
||||
if (source.isEnemyOf(creatureTarget)) {
|
||||
if (source.isAttackable(creatureTarget)) {
|
||||
doHeal(source, source, healAmount, combatCommand);
|
||||
} else {
|
||||
doHeal(source, creatureTarget, healAmount, combatCommand);
|
||||
|
||||
@@ -119,25 +119,14 @@ public class DuelService extends Service {
|
||||
sendSystemMessage(accepter, target, "accept_self");
|
||||
sendSystemMessage(target, accepter, "accept_target");
|
||||
|
||||
EnterCombatIntent.broadcast(accepter, target);
|
||||
EnterCombatIntent.broadcast(target, accepter);
|
||||
|
||||
FactionIntent.broadcastUpdateFlags(accepter);
|
||||
FactionIntent.broadcastUpdateFlags(target);
|
||||
|
||||
new DuelPlayerIntent(accepter, target, DuelPlayerIntent.DuelEventType.BEGINDUEL).broadcast();
|
||||
|
||||
}
|
||||
|
||||
private void endDuel(CreatureObject ender, CreatureObject target) {
|
||||
ender.removePlayerFromSentDuels(target);
|
||||
target.removePlayerFromSentDuels(ender);
|
||||
|
||||
ExitCombatIntent.broadcast(ender);
|
||||
ExitCombatIntent.broadcast(target);
|
||||
|
||||
FactionIntent.broadcastUpdateFlags(ender);
|
||||
FactionIntent.broadcastUpdateFlags(target);
|
||||
new DuelPlayerIntent(ender, target, DuelPlayerIntent.DuelEventType.END).broadcast();
|
||||
}
|
||||
|
||||
private void handleEndDuel(CreatureObject ender, CreatureObject target) {
|
||||
|
||||
@@ -429,7 +429,7 @@ public class CivilWarService extends Service {
|
||||
|
||||
PlayerObject playerObject = creature.getPlayerObject();
|
||||
|
||||
if (fi.getNewFaction() == PvpFaction.NEUTRAL) {
|
||||
if (fi.getNewFaction() == null || fi.getNewFaction().getPvpFaction() == PvpFaction.NEUTRAL) {
|
||||
// They've left the imperials or rebels and must have rank removed
|
||||
playerObject.setCurrentGcwRank(0);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.projectswg.holocore.intents.support.global.chat.SystemMessageIntent;
|
||||
import com.projectswg.holocore.intents.support.global.zone.PlayerTransformedIntent;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.DataLoader;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.StaticPvpZoneLoader;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.combat.FactionLoader.Faction;
|
||||
import com.projectswg.holocore.resources.support.global.player.Player;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.cell.CellObject;
|
||||
@@ -174,9 +175,9 @@ public class FactionFlagService extends Service {
|
||||
|
||||
private void handleTypeChange(FactionIntent fi) {
|
||||
TangibleObject target = fi.getTarget();
|
||||
PvpFaction newFaction = fi.getNewFaction();
|
||||
Faction newFaction = fi.getNewFaction();
|
||||
|
||||
target.setPvpFaction(newFaction);
|
||||
target.setFaction(newFaction);
|
||||
handleFlagChange(target);
|
||||
}
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ public class PlayerMountService extends Service {
|
||||
CreatureObject mount = (CreatureObject) ObjectCreator.createObjectFromTemplate(template);
|
||||
mount.systemMove(null, player.getLocation());
|
||||
mount.addOptionFlags(OptionFlag.MOUNT); // The mount won't appear properly if this isn't set
|
||||
mount.setPvpFaction(player.getPvpFaction());
|
||||
mount.setFaction(player.getFaction());
|
||||
mount.setOwnerId(player.getObjectId()); // Client crash if this isn't set before making anyone aware
|
||||
|
||||
// TODO after combat there's a delay
|
||||
|
||||
@@ -55,7 +55,7 @@ public class DeveloperService extends Service {
|
||||
|
||||
private void setupDeveloperArea() {
|
||||
AIObject dummy = spawnObject("object/mobile/shared_target_dummy_blacksun.iff", new Location(3500, 5, -4800, Terrain.DEV_AREA), AIObject.class);
|
||||
dummy.setPvpFlags(PvpFlag.ATTACKABLE);
|
||||
dummy.setPvpFlags(PvpFlag.YOU_CAN_ATTACK);
|
||||
}
|
||||
|
||||
private void setupCharacterBuilders() {
|
||||
|
||||
@@ -193,7 +193,7 @@ public class CommandQueueService extends Service {
|
||||
if (command.getTarget() == null) {
|
||||
target = command.getSource();
|
||||
} else if (command.getTarget() instanceof CreatureObject) {
|
||||
target = command.getSource().isEnemyOf((CreatureObject) command.getTarget()) ? command.getSource() : command.getTarget();
|
||||
target = command.getSource().isAttackable((CreatureObject) command.getTarget()) ? command.getSource() : command.getTarget();
|
||||
} else {
|
||||
target = null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (c) 2019 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
|
||||
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
|
||||
* Our goal is to create an emulator which will provide a server for players to *
|
||||
* continue playing a game similar to the one they used to play. We are basing *
|
||||
* it on the final publish of the game prior to end-game events. *
|
||||
* *
|
||||
* This file is part of Holocore. *
|
||||
* *
|
||||
* --------------------------------------------------------------------------------*
|
||||
* *
|
||||
* Holocore is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as *
|
||||
* published by the Free Software Foundation, either version 3 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* Holocore is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
***********************************************************************************/
|
||||
|
||||
package com.projectswg.holocore.resources.gameplay.combat
|
||||
|
||||
import com.projectswg.common.data.encodables.tangible.PvpStatus
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.ServerData
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.combat.FactionLoader
|
||||
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureObject
|
||||
import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject
|
||||
import com.projectswg.holocore.resources.support.objects.swg.tangible.OptionFlag
|
||||
import com.projectswg.holocore.test.resources.GenericCreatureObject
|
||||
import com.projectswg.holocore.test.runners.TestRunnerNoIntents
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
class TestEnemyProcessor: TestRunnerNoIntents() {
|
||||
|
||||
private val neutral = ServerData.factions.getFaction("neutral") ?: throw AssertionError("Failed to lookup 'neutral'")
|
||||
private val rebel = ServerData.factions.getFaction("rebel") ?: throw AssertionError("Failed to lookup 'rebel'")
|
||||
private val imperial = ServerData.factions.getFaction("imperial") ?: throw AssertionError("Failed to lookup 'imperial'")
|
||||
private val gungan = ServerData.factions.getFaction("gungan") ?: throw AssertionError("Failed to lookup 'gungan'")
|
||||
private val townsperson = ServerData.factions.getFaction("townsperson") ?: throw AssertionError("Failed to lookup 'townsperson'")
|
||||
|
||||
@Test
|
||||
fun testPvP() {
|
||||
val player1 = GenericCreatureObject(1)
|
||||
val player2 = GenericCreatureObject(2)
|
||||
val statusList = PvpStatus.values()
|
||||
val factions = listOf(neutral, rebel, imperial, gungan, townsperson)
|
||||
|
||||
for (player1Faction in factions) {
|
||||
for (player2Faction in factions) {
|
||||
val enemies = (player1Faction == rebel && player2Faction == imperial) || (player1Faction == imperial && player2Faction == rebel)
|
||||
for (player1Status in statusList) {
|
||||
player1.pvpStatus = player1Status
|
||||
for (player2Status in statusList) {
|
||||
player2.pvpStatus = player2Status
|
||||
val canAttack = enemies && player1Status == PvpStatus.SPECIALFORCES && player2Status == PvpStatus.SPECIALFORCES
|
||||
testAttackable(player1, player2, player1Faction, player2Faction, playerAttackable = canAttack, npcAttackable = canAttack)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDuel() {
|
||||
val player1 = GenericCreatureObject(1)
|
||||
val player2 = GenericCreatureObject(2)
|
||||
|
||||
testAttackable(player1, player2, neutral, neutral, playerAttackable = false, npcAttackable = false)
|
||||
player1.addPlayerToSentDuels(player2)
|
||||
player2.addPlayerToSentDuels(player1)
|
||||
testAttackable(player1, player2, neutral, neutral, playerAttackable = true, npcAttackable = true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPvE() {
|
||||
val player = GenericCreatureObject(1)
|
||||
val npc = AIObject(2)
|
||||
val factions = listOf(neutral, rebel, imperial, gungan, townsperson)
|
||||
|
||||
for (player1Faction in factions) {
|
||||
for (player2Faction in factions) {
|
||||
val enemies = (player1Faction == rebel && player2Faction == imperial) || (player1Faction == imperial && player2Faction == rebel)
|
||||
testAttackable(player, npc, player1Faction, player2Faction, playerAttackable = player1Faction == neutral || player2Faction == neutral || !player1Faction.isAlly(player2Faction), npcAttackable = enemies)
|
||||
}
|
||||
}
|
||||
|
||||
npc.addOptionFlags(OptionFlag.AGGRESSIVE)
|
||||
for (player1Faction in factions) {
|
||||
for (player2Faction in factions) {
|
||||
val friends = player1Faction != neutral && player2Faction != neutral && player1Faction.isAlly(player2Faction)
|
||||
testAttackable(player, npc, player1Faction, player2Faction, playerAttackable = !friends, npcAttackable = !friends)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEvE() {
|
||||
val npc1 = AIObject(1)
|
||||
val npc2 = AIObject(2)
|
||||
val factions = listOf(neutral, rebel, imperial, gungan, townsperson)
|
||||
|
||||
for (npc1Faction in factions) {
|
||||
for (npc2Faction in factions) {
|
||||
val enemies = (npc1Faction == rebel && npc2Faction == imperial) || (npc1Faction == imperial && npc2Faction == rebel)
|
||||
testAttackable(npc1, npc2, npc1Faction, npc2Faction, playerAttackable = enemies, npcAttackable = enemies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun testAttackable(player: CreatureObject, npc: CreatureObject, playerFaction: FactionLoader.Faction, npcFaction: FactionLoader.Faction, playerAttackable: Boolean, npcAttackable: Boolean) {
|
||||
player.faction = playerFaction; npc.faction = npcFaction
|
||||
Assert.assertEquals("${player.faction} ${if (playerAttackable) "is not" else "is"} able to attack ${npc.faction}", playerAttackable, EnemyProcessor.isAttackable(player, npc))
|
||||
Assert.assertEquals("${npc.faction} ${if (npcAttackable) "is not" else "is"} able to attack ${player.faction}", npcAttackable, EnemyProcessor.isAttackable(npc, player))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -143,7 +143,7 @@ public class TestSWGPersistence {
|
||||
"condition", obj.getCondition(),
|
||||
"pvpFlags", obj.getPvpFlags().stream().mapToInt(PvpFlag::getBitmask).reduce(0, (a, b) -> a | b),
|
||||
"pvpStatus", obj.getPvpStatus().name(),
|
||||
"pvpFaction", obj.getPvpFaction().name(),
|
||||
"faction", obj.getFaction() == null ? "neutral" : obj.getFaction().getName(),
|
||||
"visibleGmOnly", obj.isVisibleGmOnly(),
|
||||
"objectEffects", new Binary(obj.getObjectEffects()),
|
||||
"optionFlags", obj.getOptionFlags().stream().map(OptionFlag::getFlag).reduce(0, (a, b) -> a | b)
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.projectswg.common.network.packets.swg.zone.insertion.CmdStartScene;
|
||||
import com.projectswg.common.network.packets.swg.zone.object_controller.DataTransform;
|
||||
import com.projectswg.common.network.packets.swg.zone.object_controller.DataTransformWithParent;
|
||||
import com.projectswg.common.network.packets.swg.zone.object_controller.ObjectController;
|
||||
import com.projectswg.holocore.resources.support.data.server_info.loader.ServerData;
|
||||
import com.projectswg.holocore.resources.support.global.player.Player;
|
||||
import com.projectswg.holocore.resources.support.objects.ObjectCreator;
|
||||
import com.projectswg.holocore.resources.support.objects.swg.SWGObject;
|
||||
@@ -275,7 +276,7 @@ public class GenericPlayer extends Player {
|
||||
SWGObject obj = objects.get(p.getObjectId());
|
||||
assertNotNull(obj);
|
||||
assertTrue(obj instanceof CreatureObject);
|
||||
((CreatureObject) obj).setPvpFaction(p.getPlayerFaction());
|
||||
((CreatureObject) obj).setFaction(ServerData.INSTANCE.getFactions().getFaction(p.getPlayerFaction().name().toLowerCase(Locale.US)));
|
||||
((CreatureObject) obj).setPvpFlags(p.getPvpFlags());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user