As most of you know, it was originally planned for Strife to ship with a coop mode, similar to its contemporaries Doom, Heretic & Hexen. However, that plan was scrapped pretty late in development. Because of that, a netgame in Strife is by default inherently always a deathmatch game and the -deathmatch x switch parameter doesn't exist. But, since this applies to vanilla/chocolate and the veteran edition only, I wanted to know if it's possible or if anybody has ever tried setting up a coop session in GZDoom. Out of curiosity, I started the game in "fake-coop" mode ("map x coop/dm") and discovered that all maps actually do have 4 coop player starts. The question now is, do all scripts account for more than one player? How do conversations work, if at all? Would someone mind sharing some insights/their experience?
Someone would probably have to actually go in and manually design and balance the gameplay, story and quests to be coop-friendly. There's probably a reason why it didn't ship with official coop support - it would have taken extra development time and costs. :)
To cite another example - Morrowind is a single player first person RPG. Then OpenMW came a long - which is an open source port recreation of the game - and added client/server multiplayer to the game. After trying online Morrowind - something which fans have asked for for so many years - I am not convinced at all that the main MW game itself is suitable for online play. A huge chunk of the entire game would have to be redesigned to fully utilize multiplayerisms.
The main issue with Strife coop would be the map transitions. The Doom engine has never been too friendly towards multiplayer hubs - if a player crosses a level transition, all players are yanked into the new level regardless of their position or what they were doing.
This is particularly bad for a game like Strife where there's so many places you need to go, and not everyone wants to do everything in the same order.
Also, those annoying exit blocker pillars would need to be removed in coop, as well.
And then, you also have to make sure that all players get any quest items, which means going over all of the scripts to ensure that.
Solving these problems is ultimately only the beginning. Like Nash said - then you have to get the game balanced, and you have to make sure the systems all work for multiplayer and that nothing really breaks. That's hours of testing.
Once you've nailed that, then maybe you will have a viable coop Strife.
The level exit scripts would have to be edited so that perhaps all players need to be in close proximity before triggering the level change. Like the Baldur's Gate games.
Of course, allowing simultaenous multi-level gameplay would be technically more superior but from a design standpoint, that's even harder to get right - you now have to make sure nothing breaks in the town map while the other player triggered something in the Sanctuary, for example.
Nash wrote:Of course, allowing simultaenous multi-level gameplay would be technically more superior but from a design standpoint, that's even harder to get right - you now have to make sure nothing breaks in the town map while the other player triggered something in the Sanctuary, for example.
Neverwinter Nights allowed this - and it was fucking awesome.
Unless, of course, every single level was running looping scripts every tic... then it was a nightmare.
I’ve concluded that Strife multiplayer is technically possible, but not at all ideal. The second player would have to act as support more than anything without having any major role in the story:
If one player speaks to any character, the other player won’t be able to speak to the same character until both players change levels and return to the same map
Quests that the first player receives do not appear in the second player’s log
Multiplayer-only items appear, which tears up the balance—I’ve addressed this here
If only one player provokes enemies, they don’t attack the second one—could be a pro or con
Undead wrote:I’ve concluded that Strife multiplayer is technically possible, but not at all ideal. The second player would have to act as support more than anything without having any major role in the story:
If one player speaks to any character, the other player won’t be able to speak to the same character until both players change levels and return to the same map
Quests that the first player receives do not appear in the second player’s log
There aren't engine bugs but simply a setup that is broken by design. Strife's entire story progression handling was essentially one huge lousy hack. It is simply impossible to shoehorn that into a multiplayer game without hitting some barriers.
That said, the dialogue system itself was made multiplayer-capable, but to pull off a real working multiplayer game with it, it needs to be designed with multiplayer support in mind from the ground up. Fixing what is there after the fact can only result in stacking even more hacks on top of the hacky setup.
If one player speaks to any character, the other player won’t be able to speak to the same character until both players change levels and return to the same map
This one genuinely sounds like an engine bug. I get that NPCs not being able to simultaneously talk to multiple players might be intentional, but if I stop speaking to a character, other players should then be able to talk to that character, without changing levels. That obviously sounds like a bug.
You have no idea how badly that stuff is implemented... It is surely broken but not due to bugs but simply because the underlying system was not properly built to handle conversation state in a multiplayer setup.
Oh, okay I see what you mean now. It just wasn't written to be multiplayer-proof because, well, the original game didn't have it. And over the years, features were just short-sightedly tacked on and duct taped on it over and over again. Kinda like a lot of things in the engine I suppose...
So the last few hours I've been hacking away at making a Strife coop patch. With some very powerful tools at our disposal via ZScript, I've managed to tackle some of the initial issues and got a basic coop game somewhat-working. Have a video!
- Using LevelPostProcessor, I've removed the exit barriers and the horribly-unbalanced deathmatch weapon spawns in coop (thanks to phantombeta for help with the latter - figuring out those MTF_ flags on my own was a pain in the butt!). - Quest status is now shared by all players. If a player advances a quest, all players will receive the same progress. This includes - getting the correct QuestItems and any keys given through dialog choices. You can see in the video, player 2 turning in Beldin's ring, and as player 1 walks out of the room, he receives Blackbird's transmission. - Quest log status isn't synced across all players yet. I'm still working on that. - I've managed to do all this, so far, without introducing any new Inventory items, so the entire game is working off the base classes. This should, in theory, make it compatible with gameplay mods (assuming those mods are also coded to be MP-friendly)
There's still quite a ways to go before I think it'd be ready for a full playthrough, but I've laid out some basic battle plans:
Spoiler: "The Plan!"
+ means done - means not done yet!
Repair Phase ------------
+ QuestItems, keys and the Communicator are not given simultaneously to all players - Quest log is not updated for all players - Implement custom player start placement code that prevents telefragging each other - Player starts are inconsistent throughout the levels - some levels don't have 4 SP player starts, some levels have a player 7 start, etc. The player start cleanup code is in LevelPostProcessor - Handle player death and respawn properly: player respawn code should remember where the last level entry spot was, and place the revived player at that spot, rather than the default player start spots - Handle level transitions properly: make sure dead players who travel carry over data correctly - Check for all potential one-way, progress-blocking/point of no returns and try to work around them - If a talked-to NPC is attacked mid-conversation, the dialog should close (equivalent of pressing Esc/Goodbye) - If choosing a reply from NPC A that will change NPC B's conversation, make sure this doesn't break if another player happens to be talking to/talked to NPC B first - Allow soft "reset quest" in case of dead ends (like making a bad choice that blocks the main quest). Transition to a new hub to flush the cluster data, clear all player data and inventory then warp back to MAP02 - When town alert phase ends, the game will eventually run out of Acolytes to spawn in. Replenish the Acolytes! - If only one player provokes enemies, they don’t attack the other ones. Look into making the enemies periodically switch targets?
Enhance Phase -------------
- Balance money and monster spawns for coop? - Merge Gather Your Party - players must be in close proximity of each other to be able to travel - Enhanced skybox? - Enhanced ambient sounds? - Replace the janky-looking teleport fog sprites? - Replace the river texture in town with the one from SVE (oh god that one really annoys me LOL) - Allow shorter alert phases so that shops can reopen faster? - ????
Misc Thoughts and Issues ------------------------
Communicator output should be heard by all players simultaneously
Make NPC voice dialog emit as world sounds so that they can be heard by everyone
Add a "<NPC Name> is currently talking to <Player #>" message when activating in-conversation NPCs. Probably have to build a table of known NPC names because the MObjs have no proper character names
Should I edit the ConversationMenu to sync all items given from replies (and logs?) to all players? But don't do this for shop-bought items! [UPDATE] this seems to be impossible because HandleReply is done in the C++. There doesn't seem to be a way to intercept the item-giving stuff in ZScript
Some minor sequence breaking is possible. Example: Right before turning in Beldin's ring, have player 2 stand next to Geoff. Have player 1 turn in the ring. Player 2 is now able to tell Geoff the password, even before Player 1 walks out of Rowan's room to trigger the first Communicator transmission. Player 2 can also trigger the various Communicator transmissions out-of-sequence around the town hall area. Solution is to probably hard-code individual fixes on a case-by-case basis (yikes)
And now I'd like to reply to some old points raised in the past.
Undead wrote:I’ve concluded that Strife multiplayer is technically possible, but not at all ideal. The second player would have to act as support more than anything without having any major role in the story:
If one player speaks to any character, the other player won’t be able to speak to the same character until both players change levels and return to the same map
Quests that the first player receives do not appear in the second player’s log
Multiplayer-only items appear, which tears up the balance—I’ve addressed this here
If only one player provokes enemies, they don’t attack the second one—could be a pro or con
- I've sent a pull request to fix it on the engine side. With the fix, NPCs can be freely talked to by any player, as long as they aren't already in a conversation with some player. - I'm working on getting the quest log to sync up for all players - Already fixed - Not yet addressed, will have to figure out how to handle this
Quest log and Blackbird transmissions are now synced to all players. When the quest log updates, all players will receive it. When Blackbird sends a transmission, all players will hear it at the same time. I had to do this in a roundabout way, which results in a bit of a mess in the console (I am basically re-sending the quest log update and Blackbird transmissions [players] number of times).
That aside, I started working on the player start issues. It seems that there ARE 4 player starts in the maps so it seems the devs had planned it at some point but abandoned it. But here's the problem. The game doesn't use those player starts in netplay, instead teleporting all players into a single teleport location. This is making players telefrag each other.
Using the power of LevelPostProcessor, I have went through the first few maps to hand-place true player starts (while deleting those teleportation points). This feat is no joke. Take a look...
//=========================================================================== // // Player Start Cleanup // This has to be done on a per-map basis // //===========================================================================
case '92DD353943BE589C175FEFC969597950':// strife1.wad map01 - Sanctuary { //=========================================================================== // // Fix Bad Player Starts // //===========================================================================
// There's an extra player 2 start here, change it to a missing player 4 start SetThingEdNum(38, 4);
// Remove Teleport New Map Spot 1 because there are proper player starts already SetThingEdNum(155, 0);
// Existing player starts need to be given the proper args SetThingArgument(0, 0, 1); SetThingArgument(156, 0, 1); SetThingArgument(39, 0, 1); SetThingArgument(39, 0, 1);
//=========================================================================== // // Setup Coop-Friendly Player Starts // //===========================================================================
int t = 0;
// Town drain entrance // (Teleport New Map Spot 3) SetThingEdNum(86, 0); t = AddThing(1,(1864, 5664, 0), 0); SetThingArgument(t, 0, 3); t = AddThing(2,(1912, 5690, 0), 23); SetThingArgument(t, 0, 3); t = AddThing(3,(1967, 5740, 0), 31); SetThingArgument(t, 0, 3); t = AddThing(4,(1974, 5690, 0), 35); SetThingArgument(t, 0, 3);
// Front entrance // (Teleport New Map Spot 4) SetThingEdNum(85, 0); t = AddThing(1,(3225, 5185, 0), 90); SetThingArgument(t, 0, 4); t = AddThing(2,(3176, 5205, 0), 90); SetThingArgument(t, 0, 4); t = AddThing(3,(3270, 5205, 0), 90); SetThingArgument(t, 0, 4); t = AddThing(4,(3222, 5227, 0), 90); SetThingArgument(t, 0, 4);
break; }
case '22DC1F95CD9A03D23467FD638264FFBC':// strife1.wad map02 - Town { //=========================================================================== // // Setup Coop-Friendly Player Starts // //===========================================================================
int t = 0;
// Power Station entrance // (Teleport New Map Spot 1) SetThingEdNum(7, 0); t = AddThing(1,(-224, 7536, 0), 270); SetThingArgument(t, 0, 1); t = AddThing(2,(-288, 7472, 0), 270); SetThingArgument(t, 0, 1); t = AddThing(3,(-160, 7472, 0), 270); SetThingArgument(t, 0, 1); t = AddThing(4,(224, 7408, 0), 270); SetThingArgument(t, 0, 1);
// Prison entrance // (Teleport New Map Spot 2) SetThingEdNum(157, 0); t = AddThing(1,(-736, 5056, 0), 0); SetThingArgument(t, 0, 2); t = AddThing(2,(-696, 5096, 0), 0); SetThingArgument(t, 0, 2); t = AddThing(3,(-696, 5016, 0), 0); SetThingArgument(t, 0, 2); t = AddThing(4,(-656, 5056, 0), 0); SetThingArgument(t, 0, 2);
// Front base entrance // (Teleport New Map Spot 3) SetThingEdNum(170, 0); t = AddThing(1,(-360, 3600, 0), 90); SetThingArgument(t, 0, 3); t = AddThing(2,(-384, 3640, 0), 90); SetThingArgument(t, 0, 3); t = AddThing(3,(-336, 3640, 0), 90); SetThingArgument(t, 0, 3); t = AddThing(4,(-360, 3680, 0), 90); SetThingArgument(t, 0, 3);
// Castle entrance // (Teleport New Map Spot 4) SetThingEdNum(8, 0); t = AddThing(1,(4632, 4928, 0), 180); SetThingArgument(t, 0, 4); t = AddThing(2,(4592, 4880, 0), 180); SetThingArgument(t, 0, 4); t = AddThing(3,(4592, 4976, 0), 180); SetThingArgument(t, 0, 4); t = AddThing(4,(4576, 4928, 0), 180); SetThingArgument(t, 0, 4);
// Sewer entrance // (Teleport New Map Spot 5) SetThingEdNum(9, 0); t = AddThing(1,(1155, 2727, 0), 180); SetThingArgument(t, 0, 5); t = AddThing(2,(1101, 2714, 0), 180); SetThingArgument(t, 0, 5); t = AddThing(3,(1022, 2729, 0), 180); SetThingArgument(t, 0, 5); t = AddThing(4,(973, 2712, 0), 180); SetThingArgument(t, 0, 5);
// Sanctuary back entrance // (Teleport New Map Spot 6) SetThingEdNum(264, 0); t = AddThing(1,(1801, 5997, 0), 296); SetThingArgument(t, 0, 6); t = AddThing(2,(1772, 5955, 0), 337); SetThingArgument(t, 0, 6); t = AddThing(3,(1825, 5941, 0), 337); SetThingArgument(t, 0, 6); t = AddThing(4,(1791, 5901, 0), 347); SetThingArgument(t, 0, 6);
// Badlands entrance // (Teleport New Map Spot 8) SetThingEdNum(168, 0); t = AddThing(1,(4200, 6808, 0), 270); SetThingArgument(t, 0, 8); t = AddThing(2,(4160, 6784, 0), 270); SetThingArgument(t, 0, 8); t = AddThing(3,(4240, 6784, 0), 270); SetThingArgument(t, 0, 8); t = AddThing(4,(4200, 6760, 0), 270); SetThingArgument(t, 0, 8);
// Sanctuary drain entrance // (Teleport New Map Spot 9) SetThingEdNum(265, 0); t = AddThing(1,(1760, 6224, 0), 180); SetThingArgument(t, 0, 9); t = AddThing(2,(1712, 6240, 0), 180); SetThingArgument(t, 0, 9); t = AddThing(3,(1664, 6224, 0), 180); SetThingArgument(t, 0, 9); t = AddThing(4,(1616, 6240, 0), 180); SetThingArgument(t, 0, 9);
// Sanctuary front entrance // (Teleport New Map Spot 10) SetThingEdNum(266, 0); t = AddThing(1,(3224, 5672, 0), 270); SetThingArgument(t, 0, 10); t = AddThing(2,(3184, 5656, 0), 270); SetThingArgument(t, 0, 10); t = AddThing(3,(3264, 5656, 0), 270); SetThingArgument(t, 0, 10); t = AddThing(4,(3224, 5632, 0), 270); SetThingArgument(t, 0, 10);
break; }
case '20C20E1FC803F192613A52A62872BCDE':// strife1.wad map03 - Front Base { //=========================================================================== // // Fix Bad Player Starts // //===========================================================================
// Remove Teleport New Map Spot 1 because there are proper player starts already SetThingEdNum(1, 0);
// Existing player starts need to be given the proper args SetThingArgument(0, 0, 1); SetThingArgument(12, 0, 1); SetThingArgument(13, 0, 1); SetThingArgument(14, 0, 1);
//=========================================================================== // // Setup Coop-Friendly Player Starts // //===========================================================================
int t = 0;
// Prison teleporter entrance // (Teleport New Map Spot 2) SetThingEdNum(63, 0); t = AddThing(1,(488, 1760, 0), 180); SetThingArgument(t, 0, 2); t = AddThing(2,(385, 1758, 0), 90); SetThingArgument(t, 0, 2); t = AddThing(3,(306, 1636, 0), 180); SetThingArgument(t, 0, 2); t = AddThing(4,(409, 1854, 0), 90); SetThingArgument(t, 0, 2);
break; }
case '8AB955C176204FC3DC8008A01BDDEE87':// strife1.wad map04 - Power Station { //=========================================================================== // // Fix Bad Player Starts // //===========================================================================
// why the shit is there a Player 7 start here lol SetThingEdNum(461, 0);
// Remove Teleport New Map Spot 1 because there are proper player starts already SetThingEdNum(23, 0);
// Existing player starts need to be given the proper args SetThingArgument(0, 0, 1); SetThingArgument(375, 0, 1); SetThingArgument(376, 0, 1); SetThingArgument(377, 0, 1);
break; }
case '8DC5B4A41E35089C9789F7EC6393A015':// strife1.wad map05 - Prison { //=========================================================================== // // Fix Bad Player Starts // //===========================================================================
// Remove Teleport New Map Spot 1 because there are proper player starts already SetThingEdNum(357, 0);
// Existing player starts need to be given the proper args SetThingArgument(0, 0, 1); SetThingArgument(194, 0, 1); SetThingArgument(195, 0, 1); SetThingArgument(196, 0, 1);
// Move the player starts to all be behind the first door SetThingXY(0, 1062,-737); SetThingXY(194, 1041,-697); SetThingXY(195, 1039,-778); SetThingXY(196, 1024,-738);
break; }
case 'ED6BA7CD9388DBC9F30363EF24DBACDF':// strife1.wad map06 - Sewers { break; }
case '4A79D4C48352804FAB3557EE423ADAFE':// strife1.wad map07 - Castle { break; }
case 'D1377FAF42EA65955466169020CE5744':// strife1.wad map08 - Audience Chamber { break; }
case '8E2AA70FBF70C9C68A39A7B0A45480AD':// strife1.wad map33 - Town (demo version) { break; }
case 'B8B1BFA787E35850EAA95430B0713B88':// strife1.wad map34 - Movement Base (demo version) { break; }
It works beautifully (player starts and respawns now work exactly like in Hexen), but damn if this isn't burning me out. I may seek help with regards to patching the player starts soon. This is something I have little patience for...