Add experimental support for larger cooperative games

- Raise the player limit to 8.
- Remove the logic that forces player counts up/down when switching
  between cooperative and deathmatch game modes.
- Add heuristics to add start positions for the extra players, since
  standard maps will not have the required number of starts.
This commit is contained in:
Kp 2018-12-03 04:25:11 +00:00
parent 24cc62caa3
commit 5a64ee5132
9 changed files with 174 additions and 44 deletions

View file

@ -232,7 +232,7 @@ void obj_unlink(fvmobjptr &vmobjptr, fvmsegptr &vmsegptr, object_base &obj);
imobjptridx_t obj_create(object_type_t type, ubyte id, vmsegptridx_t segnum, const vms_vector &pos, const vms_matrix *orient, fix size, ubyte ctype, ubyte mtype, ubyte rtype);
// make a copy of an object. returs num of new object
imobjptridx_t obj_create_copy(const object &srcobj, const vms_vector &new_pos, vmsegptridx_t newsegnum);
imobjptridx_t obj_create_copy(const object &srcobj, vmsegptridx_t newsegnum);
// remove object from the world
void obj_delete(d_level_object_state &ObjectState, segment_array &Segments, vmobjptridx_t objnum);

View file

@ -96,7 +96,7 @@ extern int multi_protocol; // set and determinate used protocol
#define MULTI_PROTO_UDP 1 // UDP protocol
// What version of the multiplayer protocol is this? Increment each time something drastic changes in Multiplayer without the version number changes. Reset to 0 each time the version of the game changes
#define MULTI_PROTO_VERSION static_cast<uint16_t>(8)
#define MULTI_PROTO_VERSION static_cast<uint16_t>(9)
// PROTOCOL VARIABLES AND DEFINES - END
// limits for Packets (i.e. positional updates) per sec

View file

@ -1,6 +1,12 @@
RELEASE NOTES
=============
What's new in 0.61
------------------
* Added experimental support for generating extra start positions in cooperative games. If the number of starts requested exceeds the number provided, the game will attempt to generate extra starts near the original ones. Users are strongly cautioned to save before entering a new level, in case the start position synthesis causes problems. Additionally, users are cautioned that the engine has never before permitted more than 4 players in cooperative games, so any cooperative-specific code paths may not handle large games correctly. Reports, both of success and of failure, are welcome.
* Raised limit on cooperative players from 4 to 8. The limit cannot be raised further without changes to the savegame format.
* When more than 4 players are permitted, the per-player marker limit is reduced to 2, regardless of whether more than 4 players are actually in the game. Due to savegame limitations, at most 16 markers can be in use. The 0.60.x limit of 4 per-player is retained when there are at most 4 players permitted in the game.
What's new in 0.60
------------------
* Merged code bases and massive code refactorization. [Massive thanks to kp]
@ -77,5 +83,5 @@ What's new in 0.60
Known issues
------------
* On Windows the mouse is not correctly released if using ALT+TAB to minimize the game. This is not a bug in the program but rather the SDL library. It can be worked around by pausing the game and using ALT+ENTER to get the game to windowed mode. The mosue should not be stuck then anymore.
* On Windows the mouse is not correctly released if using ALT+TAB to minimize the game. This is not a bug in the program but rather the SDL library. It can be worked around by pausing the game and using ALT+ENTER to get the game to windowed mode. The mouse should not be stuck then anymore.
* Mac builds may suffer from crashes (or other random glitches) when playing MIDI music. This is not a bug in the program but rather an issue in timidity which SDL_mixer uses to play these files. Using a Soundtrack AddOn (which contains OGG files) should fix that problem.

View file

@ -452,7 +452,7 @@ static void duplicate_group(array<uint8_t, MAX_VERTICES> &vertex_ids, group::seg
range_for (const auto objp, objects_in(segp, vmobjptridx, vmsegptr))
{
if (objp->type != OBJ_PLAYER) {
const auto &&new_obj_id = obj_create_copy(objp, objp->pos, vmsegptridx(new_segment_id));
const auto &&new_obj_id = obj_create_copy(objp, vmsegptridx(new_segment_id));
(void)new_obj_id; // FIXME!
}
}

View file

@ -230,7 +230,7 @@ static void automap_build_edge_list(automap *am, int add_all_edges);
/* MAX_DROP_MULTI_* must be a power of 2 for LastMarkerDropped to work
* properly.
*/
#define MAX_DROP_MULTI_COOP 4
#define MAX_DROP_MULTI_COOP (Netgame.max_numplayers > 4 ? 2 : 4)
#define MAX_DROP_MULTI_COMPETITIVE 2
#define MAX_DROP_SINGLE 9

View file

@ -1537,9 +1537,6 @@ window *game_setup(void)
if (vcsegptr(ConsoleObject->segnum)->segnum == segment_none) //segment no longer exists
obj_relink(vmobjptr, vmsegptr, vmobjptridx(ConsoleObject), Cursegp);
if (!check_obj_seg(vcvertptr, ConsoleObject))
move_player_2_segment(Cursegp,Curside);
#endif
Viewer = ConsoleObject;

View file

@ -179,8 +179,6 @@ PHYSFSX_gets_line_t<LEVEL_NAME_LEN> Current_level_name;
// Global variables describing the player
unsigned N_players=1; // Number of players ( >1 means a net game, eh?)
playernum_t Player_num; // The player number who is on the console.
}
namespace dcx {
fix StartingShields=INITIAL_SHIELDS;
array<obj_position, MAX_PLAYERS> Player_init;
@ -217,6 +215,149 @@ static unsigned count_number_of_objects_of_type(fvcobjptr &vcobjptr)
#define count_number_of_robots count_number_of_objects_of_type<OBJ_ROBOT>
#define count_number_of_hostages count_number_of_objects_of_type<OBJ_HOSTAGE>
static bool operator!=(const vms_vector &a, const vms_vector &b)
{
return a.x != b.x || a.y != b.y || a.z != b.z;
}
static unsigned generate_extra_starts_by_displacement_within_segment(const unsigned preplaced_starts, const unsigned total_required_num_starts)
{
array<uint8_t, MAX_PLAYERS> player_init_segment_capacity_flag{};
DXX_MAKE_VAR_UNDEFINED(player_init_segment_capacity_flag);
static_assert(WRIGHT + 1 == WBOTTOM, "side ordering error");
static_assert(WBOTTOM + 1 == WBACK, "side ordering error");
constexpr uint8_t capacity_x = 1 << WRIGHT;
constexpr uint8_t capacity_y = 1 << WBOTTOM;
constexpr uint8_t capacity_z = 1 << WBACK;
/* When players are displaced, they are moved by their size
* multiplied by this constant. Larger values provide more
* separation between the player starts, but increase the chance
* that the player will be too close to a wall or that the segment
* will be deemed too small to support displacement.
*/
constexpr fix size_scalar = 0x18000; // 1.5 in fixed point
unsigned segments_with_spare_capacity = 0;
for (unsigned i = 0; i < preplaced_starts; ++i)
{
/* For each existing Player_init, compute whether the segment is
* large enough in each dimension to support adding more ships.
*/
const auto &pi = Player_init[i];
const auto segnum = pi.segnum;
auto &seg = *vcsegptr(segnum);
auto &plr = *Players.vcptr(i);
auto &old_player_obj = *vcobjptr(plr.objnum);
const vm_distance_squared size2(fixmul64(old_player_obj.size * old_player_obj.size, size_scalar));
auto &v0 = *vcvertptr(seg.verts[0]);
uint8_t capacity_flag = 0;
if (vm_vec_dist2(v0, vcvertptr(seg.verts[1])) > size2)
capacity_flag |= capacity_x;
if (vm_vec_dist2(v0, vcvertptr(seg.verts[3])) > size2)
capacity_flag |= capacity_y;
if (vm_vec_dist2(v0, vcvertptr(seg.verts[4])) > size2)
capacity_flag |= capacity_z;
player_init_segment_capacity_flag[i] = capacity_flag;
con_printf(CON_NORMAL, "Original player %u has size %u and segment capacity flags %x.", i, old_player_obj.size, capacity_flag);
if (capacity_flag)
++segments_with_spare_capacity;
}
if (!segments_with_spare_capacity)
return preplaced_starts;
unsigned k = preplaced_starts;
for (unsigned old_player_idx = -1, side = WRIGHT; ++ old_player_idx != preplaced_starts || (old_player_idx = 0, side ++ != WBACK);)
{
auto &old_player_ref = *Players.vcptr(old_player_idx);
const auto &&old_player_ptridx = Objects.vcptridx(old_player_ref.objnum);
auto &old_player_obj = *old_player_ptridx;
if (player_init_segment_capacity_flag[old_player_idx] & (1 << side))
{
auto &&segp = vmsegptridx(old_player_obj.segnum);
/* Copy the start exactly. The next loop will fix the
* collisions caused by placing the clone on top of the
* original.
*
* Currently, there is no handling for the case that the
* level author already put two players too close together.
* If this is a problem, more logic can be added to suppress
* cloning in that case.
*/
const auto &&extra_player_ptridx = obj_create_copy(old_player_obj, segp);
if (extra_player_ptridx == object_none)
{
con_printf(CON_URGENT, "%s:%u: warning: failed to copy start object %hu", __FILE__, __LINE__, old_player_ptridx.get_unchecked_index());
continue;
}
Players.vmptr(k)->objnum = extra_player_ptridx;
auto &extra_player_obj = *extra_player_ptridx;
set_player_id(extra_player_obj, k);
con_printf(CON_NORMAL, "Copied player %u (object %hu at {%i, %i, %i}) to create player %u (object %hu).", old_player_idx, old_player_ptridx.get_unchecked_index(), old_player_obj.pos.x, old_player_obj.pos.y, old_player_obj.pos.z, k, extra_player_ptridx.get_unchecked_index());
if (++ k >= total_required_num_starts)
break;
}
}
for (unsigned old_player_idx = 0; old_player_idx < preplaced_starts; ++old_player_idx)
{
auto &old_player_init = Player_init[old_player_idx];
const auto old_player_pos = old_player_init.pos;
auto &old_player_obj = *vmobjptr(Players.vcptr(old_player_idx)->objnum);
array<vms_vector, 3> vec_displacement{};
DXX_MAKE_VAR_UNDEFINED(vec_displacement);
auto &seg = *vcsegptr(old_player_init.segnum);
/* For each of [right, bottom, back], compute the vector between
* the center of that side and the reference player's start
* point. This will be used in the next loop.
*/
for (unsigned side = WRIGHT; side != WBACK + 1; ++side)
{
const auto &&center_on_side = compute_center_point_on_side(vcvertptr, seg, side);
const auto &&vec_pos_to_center_on_side = vm_vec_sub(center_on_side, old_player_init.pos);
const unsigned idxside = side - WRIGHT;
assert(idxside < vec_displacement.size());
vec_displacement[idxside] = vec_pos_to_center_on_side;
}
const auto displace_player = [&](const unsigned plridx, object_base &plrobj, const unsigned displacement_direction) {
vms_vector disp{};
unsigned dimensions = 0;
for (unsigned i = 0, side = WRIGHT; side != WBACK + 1; ++side, ++i)
{
if (!(player_init_segment_capacity_flag[old_player_idx] & (1 << side)))
{
con_printf(CON_NORMAL, "Cannot displace player %u at {%i, %i, %i}: not enough room in dimension %u.", plridx, plrobj.pos.x, plrobj.pos.y, plrobj.pos.z, side);
continue;
}
const auto &v = vec_displacement[i];
const auto &va = (displacement_direction & (1 << i)) ? v : vm_vec_negated(v);
con_printf(CON_NORMAL, "Add displacement of {%i, %i, %i} for dimension %u for player %u.", va.x, va.y, va.z, side, plridx);
++ dimensions;
vm_vec_add2(disp, va);
}
if (!dimensions)
return;
vm_vec_normalize(disp);
vm_vec_scale(disp, fixmul(old_player_obj.size, size_scalar >> 1));
con_printf(CON_NORMAL, "Displace player %u at {%i, %i, %i} by {%i, %i, %i}.", plridx, plrobj.pos.x, plrobj.pos.y, plrobj.pos.z, disp.x, disp.y, disp.z);
vm_vec_add2(Player_init[plridx].pos, disp);
plrobj.pos = plrobj.last_pos = Player_init[plridx].pos;
};
for (unsigned extra_player_idx = preplaced_starts, displacements = 0; extra_player_idx < k; ++extra_player_idx)
{
auto &extra_player_obj = *vmobjptr(Players.vcptr(extra_player_idx)->objnum);
if (old_player_pos != extra_player_obj.pos)
/* This clone is associated with some other player.
* Skip it here. It will be handled in a different pass
* of the loop.
*/
continue;
auto &extra_player_init = Player_init[extra_player_idx];
extra_player_init = old_player_init;
if (!displacements++)
displace_player(old_player_idx, old_player_obj, 0);
displace_player(extra_player_idx, extra_player_obj, displacements);
}
}
return k;
}
//added 10/12/95: delete buddy bot if coop game. Probably doesn't really belong here. -MT
static void gameseq_init_network_players(object_array &objects)
{
@ -236,15 +377,17 @@ static void gameseq_init_network_players(object_array &objects)
const auto type = o->type;
if (type == OBJ_PLAYER || type == OBJ_GHOST || type == OBJ_COOP)
{
if (multiplayer_coop
if (likely(k < Player_init.size()) &&
multiplayer_coop
? (j == 0 || type == OBJ_COOP)
: (type == OBJ_PLAYER || type == OBJ_GHOST)
)
{
o->type=OBJ_PLAYER;
Player_init[k].pos = o->pos;
Player_init[k].orient = o->orient;
Player_init[k].segnum = o->segnum;
auto &pi = Player_init[k];
pi.pos = o->pos;
pi.orient = o->orient;
pi.segnum = o->segnum;
vmplayerptr(k)->objnum = o;
set_player_id(o, k);
k++;
@ -262,6 +405,19 @@ static void gameseq_init_network_players(object_array &objects)
}
#endif
}
const unsigned total_required_num_starts = Netgame.max_numplayers;
if (k < total_required_num_starts)
{
con_printf(CON_NORMAL, "Insufficient cooperative starts found in mission \"%s\" level %u (need %u, found %u). Generating extra starts...", Current_mission_filename, Current_level_num, total_required_num_starts, k);
/*
* First, try displacing the starts within the existing segment.
*/
const unsigned preplaced_starts = k;
k = generate_extra_starts_by_displacement_within_segment(preplaced_starts, total_required_num_starts);
con_printf(CON_NORMAL, "Generated %u starts by displacement within the original segment.", k - preplaced_starts);
}
else
con_printf(CON_NORMAL, "Found %u cooperative starts in mission \"%s\" level %u.", k, Current_mission_filename, Current_level_num);
NumNetPlayerPositions = k;
}

View file

@ -3755,20 +3755,8 @@ static int net_udp_game_param_handler( newmenu *menu,const d_event &event, param
}
#endif
auto &slider = menus[opt->maxnet].slider();
if (menus[opt->coop].value)
{
if (menus[opt->maxnet].value>2)
{
menus[opt->maxnet].value=2;
}
if (slider.max_value > 2)
{
slider.max_value = 2;
}
opt->update_netgame_max_players();
Netgame.game_flag.show_on_map = 1;
if (Netgame.PlayTimeAllowed || Netgame.KillGoal)
@ -3778,16 +3766,6 @@ static int net_udp_game_param_handler( newmenu *menu,const d_event &event, param
}
}
else // if !Coop game
{
if (slider.max_value < 6)
{
menus[opt->maxnet].value=6;
slider.max_value = 6;
opt->update_netgame_max_players();
}
}
if (citem == opt->level)
{
auto &slevel = opt->slevel;
@ -3925,8 +3903,6 @@ window_event_result net_udp_setup_game()
read_netgame_profile(&Netgame);
if (Netgame.gamemode == NETGAME_COOPERATIVE) // did we restore Coop as default? then fix max players right now!
Netgame.max_numplayers = 4;
#if defined(DXX_BUILD_DESCENT_II)
if (!HoardEquipped() && (Netgame.gamemode == NETGAME_HOARD || Netgame.gamemode == NETGAME_TEAM_HOARD)) // did we restore a hoard mode but don't have hoard installed right now? then fall back to anarchy!
Netgame.gamemode = NETGAME_ANARCHY;

View file

@ -1181,9 +1181,8 @@ imobjptridx_t obj_create(object_type_t type, ubyte id,vmsegptridx_t segnum,const
return obj;
}
#if DXX_USE_EDITOR
//create a copy of an object. returns new object number
imobjptridx_t obj_create_copy(const object &srcobj, const vms_vector &new_pos, const vmsegptridx_t newsegnum)
imobjptridx_t obj_create_copy(const object &srcobj, const vmsegptridx_t newsegnum)
{
// Find next free object
const auto &&obj = obj_allocate(ObjectState);
@ -1193,8 +1192,6 @@ imobjptridx_t obj_create_copy(const object &srcobj, const vms_vector &new_pos, c
*obj = srcobj;
obj->pos = obj->last_pos = new_pos;
obj_link_unchecked(Objects.vmptr, obj, newsegnum);
obj->signature = obj_get_signature();
@ -1202,9 +1199,7 @@ imobjptridx_t obj_create_copy(const object &srcobj, const vms_vector &new_pos, c
//we probably should initialize sub-structures here
return obj;
}
#endif
//remove object from the world
void obj_delete(d_level_object_state &ObjectState, segment_array &Segments, const vmobjptridx_t obj)