How to Create Multiplayer Mods
See the Following Books Before Proceeding
All players may execute console commands but only the lobby host may execute entity triggers.
Loot Drops
Keep in mind that loot drops in multiplayer is different than single player. In the idAI2 entity, there is a lootDropComponent parameter for single player and pvpLootDropComponent for multiplayer. For co-op play, it is recommended that pvpLootDropComponent is identical to the lootDropComponent parameter.
Lag could be an issue if 3 Slayers are constantly generating loot drops from enemies. It is recommended to limit how much total loot drops an attack can create. Each individual loot drop value can be changed to balance out the loot drop quantity.
Music
If you want an entity to activate for all players, they must have active network replication. Both entities and decls need this set to true. Here is an example.
entity {
entityDef music_heavy {
inherit = "sound/musicentity";
class = "idMusicEntity";
expandInheritance = false;
poolCount = 0;
poolGranularity = 2;
networkReplicated = true;
disableAIPooling = false;
edit = {
spawnPosition = {
x = 1;
y = 1;
}
initialState = "music_ghost_states/main_heavy";
initialSwitchGroup = "music_ghost_switch";
initialSwitchState = "slayer_city_music";
}
}
}
In this entity, heavy music is supposed to play for each player. As long as each player has the same entity-affecting mod(s) installed, the entity should trigger.
Instanced Spawners
In BATTLEMODE maps, the idTarget_Spawn
, idTargetSpawnGroup
, idTarget_Spawn_Parent
, and idAI2
entities would require the same instanceId as the idEncounterManager entity they are used in. There is generally in originalName attached to the entityDef, which does not contain the _########## after its name. Here is an example.
entity {
instanceId = 1807099635;
originalName = "multiplayer_spawn_1";
entityDef multiplayer_spawn_1_1807099635 {
inherit = "target/spawn";
class = "idTarget_Spawn";
expandInheritance = false;
poolCount = 0;
poolGranularity = 2;
networkReplicated = false;
disableAIPooling = false;
edit = {
flags = {
noFlood = true;
}
spawnConditions = {
maxCount = 0;
reuseDelaySec = 0;
doBoundsTest = false;
boundsTestType = "BOUNDSTEST_NONE";
fovCheck = 0;
minDistance = 0;
maxDistance = 0;
neighborSpawnerDistance = -1;
LOS_Test = "LOS_NONE";
playerToTest = "PLAYER_SP";
conditionProxy = "";
}
spawnEditableShared = {
groupName = "";
deathTrigger = "";
coverRadius = 0;
maxEnemyCoverDistance = 0;
}
entityDefs = {
num = 0;
}
conductorEntityAIType = "SPAWN_AI_TYPE_ANY";
initialEntityDefs = {
num = 0;
}
spawnEditable = {
spawnAt = "";
copyTargets = false;
additionalTargets = {
num = 0;
}
overwriteTraversalFlags = true;
traversalClassFlags = "CLASS_A";
combatHintClass = "CLASS_ALL";
spawnAnim = "";
aiStateOverride = "AIOVERRIDE_DEFAULT";
initialTargetOverride = "";
}
portal = "";
targetSpawnParent = "";
disablePooling = false;
spawnPosition = {
x = -13;
y = 3.58;
z = -95.04;
}
spawnOrientation = {
mat = {
mat[0] = {
x = 1;
y = 0;
z = 0;
}
mat[1] = {
x = 0;
y = 1;
z = 0;
}
mat[2] = {
x = 0;
y = 0;
z = 1;
}
}
}
renderModelInfo = {
scale = {
x = 1;
y = 1;
z = 1;
}
}
}
}
}
1807099635 is the instanceId. They must all be identical.
Network replication is not important so long as only the instanceId is the same as the default BATTLEMODE encounter.
Instance IDs are typically useful for managing timelines such as INVASION portals.
idPlayerStart & idDemonPlayerStart
Both the idPlayerStart
and idDemonPlayerStart
are required for BATTLEMODE or INVASION. There must be a TEAM_ONE
and TEAM_TWO
as well. Here is an example of a Demon Player initial spawn position.
entity {
entityDef game_online_battle_arena_demon_start_1 {
inherit = "online/battle_arena/demon_start";
class = "idDemonPlayerStart";
expandInheritance = false;
poolCount = 0;
poolGranularity = 2;
networkReplicated = true; // Keep this true
disableAIPooling = false;
edit = {
spawnPosition = { // Change spawn position
x = 1;
y = 1;
z = 1;
}
spawnOrientation = {
mat = {
mat[0] = {
x = -0.707106709;
y = 0.707106829;
}
mat[1] = {
x = -0.707106829;
y = -0.707106709;
}
}
}
team = "TEAM_TWO";
}
}
}
Apply the respawnOnly = true;
parameter in the edit
field to only use the position for respawning.
idGameChallenge
The idGameChallenge entity must be idGameChallenge_PVP
, here is a template from pvp_laser_patch1
(Forsaken).
entity {
entityDef game_info_game_challenge_battlearena_1 {
inherit = "info/game_challenge/battlearena";
class = "idGameChallenge_PVP";
expandInheritance = false;
poolCount = 0;
poolGranularity = 2;
networkReplicated = true; // Keep this true
disableAIPooling = false;
edit = {
networkSerializeTransforms = false;
modeFlags = {
allowRespawning = true;
allowBulletPenetration = false;
}
scoreLimit = 3; // 3 rounds to win
numLives = 9; // Demons can respawn at most 9 times per round, 0 for unlimited
numRounds = 5; // 5 rounds total
actorModifierListDecl = "actormodifiers_pvp"; // Contains each playable character
gcGameEventCallouts = { // Top-right side tickers
slayerPowerWeapon = "pvp/combat_events/slayer_power_weapon";
demonSummonedCallout = "pvp/combat_events/general_card_event";
demonEffectCallout = "pvp/combat_events/general_card_event";
demonEffectStatusTimer = "pvp/status_timers/demon_effect";
demonQuickUse1Callout = "pvp/combat_events/general_card_event";
demonQuickUse2Callout = "pvp/combat_events/general_card_event";
damageBoostCallout = "pvp/combat_events/general_card_event";
damageBoostStatusTimer = "pvp/status_timers/timer_damage_boost";
hasteCallout = "pvp/combat_events/general_card_event";
hasteStatusTimer = "pvp/status_timers/timer_haste";
mitigationCallout = "pvp/combat_events/general_card_event";
mitigationStatusTimer = "pvp/status_timers/timer_damage_mitigation";
invulnerableCallout = "pvp/combat_events/general_card_event";
invulnerableStatusTimer = "pvp/status_timers/timer_invulnerable";
berserkCallout = "pvp/combat_events/general_card_event";
berserkStatusTimer = "pvp/status_timers/timer_berserk";
regenCallout = "pvp/combat_events/general_card_event";
regenStatusTimer = "pvp/status_timers/timer_regeneration";
lootBlockedCallout = "pvp/voiced/loot_blocked";
lootBlockedStatusTimer = "pvp/status_timers/timer_loot_blocked";
extraLifeCallout = "pvp/combat_events/general_card_event";
demonCriticalHealth = "pvp/voiced/demon_health_critical";
demonCriticalRecovery = "pvp/voiced/demon_healed";
slayerCriticalHealth = "pvp/voiced/slayer_health_critical";
everyoneCritical = "pvp_sudden_death";
}
hitConfirmSoundsInfo = "default";
characterStatusEventText = {
invulnerableText = {
textId = "#str_decl_powerup_statuseffect_GHOST45053";
color = {
r = 0.87450999;
g = 0.87450999;
b = 0.87450999;
}
}
toughenedText = {
textId = "#str_decl_powerup_statuseffect_GHOST45054";
color = {
r = 0.87450999;
g = 0.87450999;
b = 0.87450999;
}
}
vulnerableText = {
textId = "#str_decl_powerup_statuseffect_GHOST45055";
color = {
r = 0.87450999;
g = 0.87450999;
b = 0.87450999;
}
}
strengthenedText = {
textId = "#str_decl_powerup_statuseffect_GHOST45056";
color = {
r = 0.87450999;
g = 0.87450999;
b = 0.87450999;
}
}
hastedText = {
textId = "#str_decl_powerup_statuseffect_GHOST45057";
color = {
r = 0.87450999;
g = 0.87450999;
b = 0.87450999;
}
}
slowedText = {
textId = "#str_decl_powerup_statuseffect_GHOST45058";
color = {
r = 0.87450999;
g = 0.87450999;
b = 0.87450999;
}
}
berserkingText = {
textId = "#str_decl_powerup_statuseffect_GHOST45059";
color = {
r = 0.87450999;
g = 0.87450999;
b = 0.87450999;
}
}
lootBlockedText = {
textId = "#str_decl_powerup_statuseffect_GHOST45060";
color = {
r = 0.87450999;
g = 0.87450999;
b = 0.87450999;
}
}
}
aiSpawnPoolDecl = "maps/game/pvp/battlemode";
enableBrinkOfDeath = false;
desummonKillDamage = "damage/hazard/pvp_round_kill";
slayerHighlightDecl = "pvp/slayer_view_demon_outline";
demonHighlightDecl = "pvp/demon_view_slayer_outline";
teammateHighlightDecl = "pvp/demon_view_teammate_outline";
slayerHighlightLOSBoxDecl = "highlight_los_slayer";
demonSpawnTargetEntity = "online/summon_target";
demonSpawnTargetEntityOneHit = "online/summon_target_onehit";
playerSpawnDef = "player";
teamSuperSettings = {
damageFactorAI = 9.99999975e-05;
tickFactor = 0.00329999998;
}
playerAlwaysFullBodyGibs = true;
maxPlayersPerTeam = {
ptr = {
ptr[0] = 1; // 1 Slayer
ptr[1] = 2; // 2 Demons
}
}
demonOutlineColor = {
g = 0.501960993;
}
demonAllyOutlineColor = {
r = 1;
g = 0.501960993;
b = 0;
}
fillColorDemonSees = {
r = 0.199999988;
g = 0.199999988;
b = 0.199999988;
a = 0.850000024;
}
fillColorSlayerSees = {
r = 0.199999988;
g = 0.199999988;
b = 0.199999988;
a = 0.850000024;
}
fillColorHitFlash = {
g = 0;
b = 0;
a = 0.501960993;
}
slayerHighlightOptions = {
enemyDemon = "EHM_ONLY_IN_VIEW";
}
demonHighlightOptions = {
allyDemon = "EHM_ALWAYS";
enemySlayer = "EHM_ALWAYS";
}
raceToStyle = true; // Set scoreLimit to indicate how many rounds a side must win to take the match
pvpGameEventCallouts = { // Announcer voice lines
roundOne = "pvp/voiced/round_one";
roundTwo = "pvp/voiced/round_two";
roundThree = "pvp/voiced/round_three";
roundFour = "pvp/voiced/round_four";
roundFinal = "pvp/voiced/final_round";
preMatchFiveSecondsRemaining = "pvp/chimes/five";
preMatchFourSecondsRemaining = "pvp/chimes/four";
preMatchThreeSecondsRemaining = "pvp/chimes/three";
preMatchTwoSecondsRemaining = "pvp/chimes/two";
preMatchOneSecondRemaining = "pvp/chimes/one";
roundStart = "pvp/voiced/fight";
finalRound = "pvp/voiced/final_round";
demonsRoundLost = "pvp/voiced/demons_round_lost";
slayerRoundLost = "pvp/voiced/slayer_round_lost";
slayerKilled = "pvp/voiced/slayer_killed";
demonKilled = "pvp/voiced/demon_killed";
demonsKilled = "pvp/voiced/demons_killed";
slayerVictory = "pvp/voiced/slayer_won_the_match";
demonVictory = "pvp/voiced/demons_won_the_match";
demonRespawningSoon = "pvp/voiced/demon_resurrecting_soon";
respawnFiveSecondsRemaining = "pvp/chimes/resurrect_five";
respawnFourSecondsRemaining = "pvp/chimes/resurrect_four";
respawnThreeSecondsRemaining = "pvp/chimes/resurrect_three";
respawnTwoSecondsRemaining = "pvp/chimes/resurrect_two";
respawnOneSecondRemaining = "pvp/chimes/resurrect_one";
demonRespawned = "pvp/voiced/demon_resurrected";
delayDuringSync = {
num = 3;
item[0] = "pvp/voiced/slayer_killed";
item[1] = "pvp/voiced/demons_won_the_match";
item[2] = "pvp/voiced/slayer_round_lost";
}
}
jockeyTimeDuration = {
value = 5;
}
roundCalloutTimeDuration = {
value = 3;
}
roundStartCalloutDelaySec = {
value = 2;
}
pvpProgressionScoringDecl = "battle_arena";
pvpLifecycleManager = {
podiumAvatarEntDefs = {
num = 7;
item[0] = "podiums/avatars/archvile";
item[1] = "podiums/avatars/doom_marine";
item[2] = "podiums/avatars/mancubus";
item[3] = "podiums/avatars/pain_elemental";
item[4] = "podiums/avatars/revenant";
item[5] = "podiums/avatars/marauder";
item[6] = "podiums/avatars/dreadknight";
}
podiumLayers = {
num = 1;
item[0] = "game/pvp/podium_stage";
}
slayerPodiumEntities = "pvp_match_slayer_podium_";
demonPodiumEntities = "pvp_match_demon_podium_slot_";
characterAnimBlendMS = 0;
playerAppearSound = "play_pvp_staging_spawnin";
}
slayerPVPLoadoutDecl = "pvploadout/default";
demonPVPLoadoutDecl = "pvpdemonloadout/default";
respawnTimeSec = {
branchPairs = {
num = 1;
item[0] = {
branchKey = "CONTROLLERPAD_DECL";
branchResult = {
value = 22;
}
}
}
}
respawnTimeCapSec = {
defaultValue = {
value = 20;
}
branchPairs = {
num = 1;
item[0] = {
branchKey = "CONTROLLERPAD_DECL";
branchResult = {
value = 22;
}
}
}
}
respawnStatusEffects = {
num = 2;
item[0] = "statuseffect/pvp/demon_action/respawn_protection";
item[1] = "statuseffect/pvp/demon_action/respawn_haste";
}
idealRespawnDistanceFromSlayer = 100;
slayerLayersToActivate = {
num = 1;
item[0] = "game/pvp/slayer_team";
}
demonLayersToActivate = {
num = 1;
item[0] = "game/pvp/demon_team";
}
layersToDeactivate = {
num = 1;
item[0] = "game/pvp/permanently_hidden";
}
musicFirstLoadedState = "music_ghost_states/pvp_lobby";
musicIntroState = "music_ghost_states/pvp_lobby_player_ready";
musicMatchState = "music_ghost_states/pvp_lobby_end";
musicWinState = "music_ghost_states/pvp_win";
musicLoseState = "music_ghost_states/pvp_lose";
musicHealthCriticalState = "music_ghost_states/pvp_sudden_death";
musicRoundWinState = "music_ghost_states/pvp_win_round";
musicRoundLoseState = "music_ghost_states/pvp_lose_round";
closeCallHealthThreshold = 50;
clutchThreshold = 5;
comebackThreshold = 3;
surpriseTimeLimit = {
value = 3;
}
surviveThreshold = 4;
requiredPlayerCount = 2; // Requires at least 2 players to start the game
soundOcclusionBypass = false; // Sound occlusion is enabled
spawnPosition = { // The spawn position does not matter
x = 1;
y = 1;
z = 1;
}
}
}
}
No Comments