Add support for shuffling powerups in anarchy games

This commit is contained in:
Kp 2018-04-12 04:19:35 +00:00
parent ce5d1005dd
commit 8096af91da
5 changed files with 177 additions and 15 deletions

View file

@ -95,7 +95,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>(6)
#define MULTI_PROTO_VERSION static_cast<uint16_t>(7)
// PROTOCOL VARIABLES AND DEFINES - END
// limits for Packets (i.e. positional updates) per sec
@ -485,7 +485,6 @@ void multi_process_bigdata(playernum_t pnum, const ubyte *buf, uint_fast32_t len
void multi_do_death(int objnum);
#ifdef dsx
namespace dsx {
int multi_delete_extra_objects();
void multi_make_ghost_player(playernum_t);
void multi_make_player_ghost(playernum_t);
}
@ -786,6 +785,7 @@ struct netgame_info : prohibit_void_ptr<netgame_info>, ignore_window_pointer_t
uint32_t AllowedItems;
packed_spawn_granted_items SpawnGrantedItems;
packed_netduplicate_items DuplicatePowerups;
unsigned ShufflePowerupSeed;
#if defined(DXX_BUILD_DESCENT_II)
uint8_t Allow_marker_view;
uint8_t AlwaysLighting;

View file

@ -51,6 +51,7 @@ What's new in 0.60
* In multiplayer anarchy games, the host can now configure the game to prefer to spawn players at sites farther from the currently live players (Github issue #108).
* In multiplayer games, the host can now choose to duplicate preplaced primary weapons, secondary weapons, or powerups.
* Added experimental support for uncapped ship turning (yawing, pitching, and rolling), popularly known as mouselook. This is runtime-disabled by default. Players who wish to use it must enable it in Options -> Controls under the heading "Uncapped Turning In:". Separate knobs are provided for single player, multiplayer cooperative, and multiplayer anarchy. For multiplayer games, mouselook must also be enabled by the host in "Game Setup" -> "Advanced Netgame Options" -> "Misc. Options" as "Allow coop mouselook" or "Allow anarchy mouselook", depending on game type. For this feature, any multiplayer game other than cooperative is considered anarchy: "Anarchy", "Team Anarchy", "Robo-Anarchy", "Capture the Flag", and "Hoard", and "Bounty" are all grouped as "anarchy" since all those modes are competitive against other players. If the host does not enable mouselook in the game setup menu, no players in that game will be permitted to use mouselook. A host may permit mouselook for guests while keeping its own controls set to non-mouselook mode. Although the feature is popularly known as mouselook, the implementation controls turning for all input types: keyboard, mouse, and joystick.
* Added experimental support for shuffling powerups in non-cooperative games. Shuffling must be enabled by the host in Advanced Netgame Options. When enabled, all powerups are randomly shuffled at level startup. For this purpose, hostages are not considered powerups and do not participate in the shuffle process, even though anarchy converts hostages into shields before play begins.
* Added experimental briefing directive to show Descent 1 style rotating robots, rather than Descent 2 style robot movies. Invoke with a line consisting solely of "$:Rebirth.rotate.robot N" or "$:$F:Rebirth.rotate.robot N" where N is the decimal index of the robot to show, and all other characters must be present as shown. The first form is visible to legacy clients. The second form abuses a parsing defect in legacy clients to hide the unknown directive, at the expense of toggling the blinking cursor. Use a regular "$F" on an adjacent line to reverse the toggle.
* Players protected by spawn invulnerability are now able to acquire an invulnerability powerup, which will upgrade their invulnerability time from the 0.5-4.0 seconds of spawn invulnerability to the full 30 seconds of an invulnerability powerup. Previously, a player protected by spawn invulnerability was not allowed to acquire an invulnerability powerup and instead received the error "You already are Invulnerable!"; players protected by an invulnerability powerup are still prohibited from collecting a new one until the old one runs out.
* Drastically improved positional accuracy and less latency in Multiplayer games.

View file

@ -25,6 +25,7 @@ COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
#include <bitset>
#include <stdexcept>
#include <random>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -95,6 +96,7 @@ namespace dsx {
static void multi_reset_object_texture(object_base &objp);
static void multi_new_bounty_target(playernum_t pnum);
static void multi_process_data(playernum_t pnum, const ubyte *dat, uint_fast32_t type);
static void multi_update_objects_for_non_cooperative();
}
static void multi_add_lifetime_killed();
static void multi_send_heartbeat();
@ -3189,6 +3191,22 @@ public:
void process_powerup(fvmsegptridx &, const object &, powerup_type_t);
};
class powerup_shuffle_state
{
unsigned count = 0;
unsigned seed;
union {
std::array<vmobjptridx_t, MAX_OBJECTS> ptrs;
};
public:
powerup_shuffle_state(const unsigned s) :
seed(s)
{
}
void record_powerup(vmobjptridx_t);
void shuffle() const;
};
void update_item_state::process_powerup(fvmsegptridx &vmsegptridx, const object &o, const powerup_type_t id)
{
uint_fast32_t count;
@ -3297,7 +3315,9 @@ public:
void multi_prep_level_objects()
{
if (!(Game_mode & GM_MULTI_COOP))
multi_delete_extra_objects(); // Removes monsters from level
{
multi_update_objects_for_non_cooperative(); // Removes monsters from level
}
constexpr unsigned MAX_ALLOWED_INVULNERABILITY = 3;
constexpr unsigned MAX_ALLOWED_CLOAK = 3;
@ -3515,34 +3535,142 @@ static int object_allowed_in_anarchy(const object_base &objp)
return 0;
}
int multi_delete_extra_objects()
void powerup_shuffle_state::record_powerup(const vmobjptridx_t o)
{
int nnp=0;
if (!seed)
return;
const auto id = get_powerup_id(o);
switch (id)
{
/* record_powerup runs before object conversion or duplication,
* so object types that anarchy converts still have their
* original type when this switch runs. Therefore,
* POW_EXTRA_LIFE and the key powerups must be handled here,
* even though they are converted to other objects before play
* begins. If they were not handled, no object could exchange
* places with a converted object.
*/
case POW_EXTRA_LIFE:
case POW_ENERGY:
case POW_SHIELD_BOOST:
case POW_LASER:
case POW_KEY_BLUE:
case POW_KEY_RED:
case POW_KEY_GOLD:
case POW_MISSILE_1:
case POW_MISSILE_4:
case POW_QUAD_FIRE:
case POW_VULCAN_WEAPON:
case POW_SPREADFIRE_WEAPON:
case POW_PLASMA_WEAPON:
case POW_FUSION_WEAPON:
case POW_PROXIMITY_WEAPON:
case POW_HOMING_AMMO_1:
case POW_HOMING_AMMO_4:
case POW_SMARTBOMB_WEAPON:
case POW_MEGA_WEAPON:
case POW_VULCAN_AMMO:
case POW_CLOAK:
case POW_INVULNERABILITY:
#if defined(DXX_BUILD_DESCENT_II)
case POW_GAUSS_WEAPON:
case POW_HELIX_WEAPON:
case POW_PHOENIX_WEAPON:
case POW_OMEGA_WEAPON:
case POW_SUPER_LASER:
case POW_FULL_MAP:
case POW_CONVERTER:
case POW_AMMO_RACK:
case POW_AFTERBURNER:
case POW_HEADLIGHT:
case POW_SMISSILE1_1:
case POW_SMISSILE1_4:
case POW_GUIDED_MISSILE_1:
case POW_GUIDED_MISSILE_4:
case POW_SMART_MINE:
case POW_MERCURY_MISSILE_1:
case POW_MERCURY_MISSILE_4:
case POW_EARTHSHAKER_MISSILE:
#endif
break;
default:
return;
}
if (count >= ptrs.size())
return;
ptrs[count++] = o;
}
void powerup_shuffle_state::shuffle() const
{
if (!count)
return;
std::minstd_rand mr(seed);
for (unsigned j = count; --j;)
{
const auto oi = std::uniform_int_distribution<unsigned>(0u, j)(mr);
if (oi == j)
/* Swapping an object with itself is a no-op. Skip the
* work. Do not re-roll, both to avoid the potential for an
* infinite loop on unlucky rolls and to ensure a uniform
* distribution of swaps.
*/
continue;
const auto o0 = ptrs[j];
const auto o1 = ptrs[oi];
const auto os0 = o0->segnum;
const auto os1 = o1->segnum;
/* Disconnect both objects from their original segments. Swap
* their positions. Link each object to the segment that the
* other object previously used. This is necessary instead of
* using std::swap on object::segnum, since the segment's linked
* list of objects needs to be updated.
*/
obj_unlink(vmobjptr, vmsegptr, *o0);
obj_unlink(vmobjptr, vmsegptr, *o1);
std::swap(o0->pos, o1->pos);
obj_link_unchecked(vmobjptr, o0, vmsegptridx(os1));
obj_link_unchecked(vmobjptr, o1, vmsegptridx(os0));
}
}
void multi_update_objects_for_non_cooperative()
{
// Go through the object list and remove any objects not used in
// 'Anarchy!' games.
// This function also prints the total number of available multiplayer
// positions in this level, even though this should always be 8 or more!
const auto game_mode = Game_mode;
/* Shuffle objects before object duplication runs. Otherwise,
* duplication-eligible items would be duplicated, then scattered,
* causing the original site to be a treasure trove of swapped
* items. This way, duplicated items appear with their original.
*/
powerup_shuffle_state powerup_shuffle(Netgame.ShufflePowerupSeed);
range_for (const auto &&objp, vmobjptridx)
{
if ((objp->type==OBJ_PLAYER) || (objp->type==OBJ_GHOST))
nnp++;
else if ((objp->type==OBJ_ROBOT) && (Game_mode & GM_MULTI_ROBOTS))
;
const auto obj_type = objp->type;
if (obj_type == OBJ_PLAYER || obj_type == OBJ_GHOST)
continue;
else if (obj_type == OBJ_ROBOT && (game_mode & GM_MULTI_ROBOTS))
continue;
else if (obj_type == OBJ_POWERUP)
{
powerup_shuffle.record_powerup(objp);
continue;
}
else if (!object_allowed_in_anarchy(objp) ) {
#if defined(DXX_BUILD_DESCENT_II)
// Before deleting object, if it's a robot, drop it's special powerup, if any
if (objp->type == OBJ_ROBOT)
if (obj_type == OBJ_ROBOT)
if (objp->contains_count && (objp->contains_type == OBJ_POWERUP))
object_create_robot_egg(objp);
#endif
obj_delete(objp);
}
}
return nnp;
powerup_shuffle.shuffle();
}
}

View file

@ -13,6 +13,7 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <random>
#include "pstypes.h"
#include "window.h"
@ -2538,6 +2539,8 @@ static uint_fast32_t net_udp_prepare_heavy_game_info(const _sockaddr *addr, ubyt
buf[len] = pack_game_flags(&Netgame.game_flag).value; len++;
buf[len] = Netgame.team_vector; len++;
PUT_INTEL_INT(buf + len, Netgame.AllowedItems); len += 4;
/* In cooperative games, never shuffle. */
PUT_INTEL_INT(&buf[len], (Game_mode & GM_MULTI_COOP) ? 0 : Netgame.ShufflePowerupSeed); len += 4;
buf[len] = Netgame.SecludedSpawns; len += 1;
#if defined(DXX_BUILD_DESCENT_I)
buf[len] = Netgame.SpawnGrantedItems.mask; len += 1;
@ -2776,6 +2779,7 @@ static void net_udp_process_game_info(const uint8_t *data, uint_fast32_t, const
Netgame.game_flag = unpack_game_flags(&p); len++;
Netgame.team_vector = data[len]; len++;
Netgame.AllowedItems = GET_INTEL_INT(&(data[len])); len += 4;
Netgame.ShufflePowerupSeed = GET_INTEL_INT(&(data[len])); len += 4;
Netgame.SecludedSpawns = data[len]; len += 1;
#if defined(DXX_BUILD_DESCENT_I)
Netgame.SpawnGrantedItems = data[len]; len += 1;
@ -3286,6 +3290,7 @@ constexpr std::integral_constant<unsigned, 5 * reactor_invul_time_mini_scale> re
DXX_MENUITEM(VERB, SLIDER, SpawnInvulnerableText, opt_start_invul, Netgame.InvulAppear, 0, 8) \
DXX_MENUITEM(VERB, TEXT, "", blank_2) \
DXX_MENUITEM(VERB, TEXT, "Object Options", powerup_label) \
DXX_MENUITEM(VERB, CHECK, "Shuffle powerups in anarchy games", opt_shuffle_powerups, Netgame.ShufflePowerupSeed) \
DXX_MENUITEM(VERB, MENU, "Set Objects allowed...", opt_setpower) \
DXX_MENUITEM(VERB, MENU, "Set Objects granted at spawn...", opt_setgrant) \
DXX_MENUITEM(VERB, TEXT, "", blank_3) \
@ -3860,6 +3865,7 @@ window_event_result net_udp_setup_game()
Netgame.PacketsPerSec=DEFAULT_PPS;
snprintf(Netgame.game_name.data(), Netgame.game_name.size(), "%s%s", static_cast<const char *>(self.callsign), TXT_S_GAME);
reset_UDP_MyPort();
Netgame.ShufflePowerupSeed = 0;
Netgame.BrightPlayers = 1;
Netgame.InvulAppear = 4;
Netgame.SecludedSpawns = MAX_PLAYERS - 1;
@ -4251,6 +4257,29 @@ static int net_udp_select_players()
char title[50];
unsigned save_nplayers; //how may people would like to join
if (Netgame.ShufflePowerupSeed)
{
unsigned seed = 0;
try {
seed = std::random_device()();
if (!seed)
/* random_device can return any number, including zero.
* Rebirth treats zero specially, interpreting it as a
* request not to shuffle. Prevent a zero from
* random_device being interpreted as a request not to
* shuffle.
*/
seed = 1;
} catch (const std::exception &e) {
con_printf(CON_URGENT, "Failed to generate random number: %s", e.what());
/* Fall out without setting `seed`, so that the option is
* disabled until the user notices the message and
* resolves the problem.
*/
}
Netgame.ShufflePowerupSeed = seed;
}
net_udp_add_player( &UDP_Seq );
start_poll_menu_items spd;

View file

@ -73,6 +73,7 @@ COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
#define DuplicatePrimariesStr "DuplicatePrimaries"
#define DuplicateSecondariesStr "DuplicateSecondaries"
#define DuplicateAccessoriesStr "DuplicateAccessories"
#define ShufflePowerupsStr "ShufflePowerups"
#define AllowMarkerViewStr "Allow_marker_view"
#define AlwaysLightingStr "AlwaysLighting"
#define ShowEnemyNamesStr "ShowEnemyNames"
@ -1481,6 +1482,8 @@ void read_netgame_profile(netgame_info *ng)
ng->ThiefModifierFlags |= ThiefModifier::NoEnergyWeapons;
}
#endif
else if (cmp(lb, eq, ShufflePowerupsStr))
convert_integer(ng->ShufflePowerupSeed, value);
else if (cmp(lb, eq, ShowEnemyNamesStr))
convert_integer(ng->ShowEnemyNames, value);
else if (cmp(lb, eq, BrightPlayersStr))
@ -1531,6 +1534,7 @@ void write_netgame_profile(netgame_info *ng)
PHYSFSX_printf(file, ThiefAbsenceFlagStr "=%i\n", ng->ThiefModifierFlags & ThiefModifier::Absent);
PHYSFSX_printf(file, ThiefNoEnergyWeaponsFlagStr "=%i\n", ng->ThiefModifierFlags & ThiefModifier::NoEnergyWeapons);
#endif
PHYSFSX_printf(file, ShufflePowerupsStr "=%i\n", !!ng->ShufflePowerupSeed);
PHYSFSX_printf(file, ShowEnemyNamesStr "=%i\n", ng->ShowEnemyNames);
PHYSFSX_printf(file, BrightPlayersStr "=%i\n", ng->BrightPlayers);
PHYSFSX_printf(file, InvulAppearStr "=%i\n", ng->InvulAppear);