-
Notifications
You must be signed in to change notification settings - Fork 3
Upgrading
v1.0.0 is not backwards compatible with minqlx, or with minqlxtended before v1.0.0. The plugins repository is already ported and is the best reference if something here isn't clear.
These six come out of the game module now, so there is no stats dictionary behind them.
| Event | Was | Now |
|---|---|---|
game_start |
(data) |
() |
game_end |
(data) |
(aborted) |
round_end |
(data) |
(round_number, winning_team, time) |
kill |
(victim, killer, data) |
(victim, killer, mod) |
death |
(victim, killer, data) |
(victim, killer, mod) |
chat |
(player, msg, channel) |
(player, msg, channel, recipient) |
team_switch_attempt |
(player, old_team, new_team) |
(player, old_team, new_team, target) |
userinfo |
(player, changed) |
(player, changed, infostring) |
player_connect |
(player) |
(player, is_bot) |
round_end's winning_team is a team name or None for a draw, and time is the round's
duration in milliseconds, where data["TIME"] was elapsed match time in seconds.
mod keeps the vocabulary data["MOD"] used, so data["MOD"] == "GAUNTLET" becomes
mod == "GAUNTLET". For anything else the old dictionary carried, such as scores, rosters and per-player
totals, read Game() and Player.expanded_stats.
chat's recipient is the player who was told, None for anything said openly. Test
recipient is not None to skip tells. Messages are no longer quote-stripped.
userinfo: a dict returned by a handler is merged with what earlier handlers returned rather
than replacing it.
Use player_connect's is_bot flag. player.is_bot reports every bot as human at connect
time, and is correct everywhere else.
-
killno longer fires for suicides or world deaths.deathalways fires;killonly when another player was responsible. -
Changing team no longer raises
death. Useteam_switch. -
team_switch_attemptfires for admin puts, duel-queue promotion, follow-cycling and level exit. Cancelling it to enforce a team lock now blocks admins too.new_teamis only what was asked for; useteam_switchfor what happened. -
vote_calledfires while another vote is running, and for votes the engine rejects. Hookvote_startedfor the votes that run. -
vote_endedisn't locale-dependent, and fires for votes abandoned by intermission or a level change, reported as not passed. votefires whether or not a vote is running.- Cancel with
Return.STOP_EVENTorReturn.STOP_ALL, never a bareFalse. -
A handler whose signature doesn't fit its event is refused at registration, not at first
dispatch.
*argshandlers and ones whose extra parameters all have defaults are left alone. -
@next_frametasks all run at the start of the next frame, before any@delaycallbacks due in the same frame. -
PlayerDisonnectDispatcheris spelledPlayerDisconnectDispatcher. The event name is unchanged. -
VoteStartedDispatcher.caller()is gone.Plugin.callvote(vote, display, caller=player)is unchanged.
Every family is an enum. They are IntEnum or IntFlag, so arithmetic and | still work.
| Was | Now |
|---|---|
WP_RAILGUN |
Weapon.RAILGUN |
PRI_HIGH |
Priority.HIGH |
MOD_ROCKET |
Mod.ROCKET |
ET_ITEM |
EntityType.ITEM |
SS_GAME |
ServerState.GAME |
CVAR_ARCHIVE |
CvarFlag.ARCHIVE |
SVF_BOT |
ServerFlag.BOT |
DAMAGE_RADIUS |
DamageFlag.RADIUS |
TR_LINEAR |
TrajectoryType.LINEAR |
MOVER_POS1 |
MoverState.POS1 |
SAY_TEAM |
SayMode.TEAM |
MODELINDEX_QUADDAMAGE |
ModelIndex.QUADDAMAGE |
RET_STOP_ALL |
Return.STOP_ALL |
TEAM_RED |
Team.RED.index |
PRIV_ADMIN |
Privilege.ADMIN.level |
CS_ACTIVE |
ConnectionState.ACTIVE.index |
The configstring-index CS_* constants stay plain ints with their names: CS_* names two
different families, and CS_FREE and CS_SERVERINFO are both 0, so no one enum can hold it.
Return is not an IntEnum, so False, True, 0 and 1 match no member.
TEAMS, WEAPONS, GAMETYPES, GAMETYPES_SHORT, ITEMS, HOLDABLES, OBJECTIVES,
ROUND_STATES, CONNECTION_STATES, MEANS_OF_DEATH, TEAM_BASED_GAMETYPES and
NONTEAM_BASED_GAMETYPES each named a number the member already knows.
minqlxtended.Team.from_index(n) # was TEAMS[n]
list(minqlxtended.Team) # was TEAMS.values()
minqlxtended.Gametype.from_index(n).title # was GAMETYPES[n]
minqlxtended.Weapon(n).short # was WEAPONS[n]
minqlxtended.ModelIndex.QUADDAMAGE # was ITEMS["quad"]
game.is_team_based # was game.type_short in TEAM_BASED_GAMETYPESfrom_index() is on Team, Gametype, MeansOfDeath, Objective, RoundState and
ConnectionState. Privilege.from_level() and Weapon.from_short() are the same idea where
the key isn't an index. All raise ValueError naming the family and listing what it has.
Team("RED"), GameState("WARMUP") and Game.lock("Red") raise. The string-valued properties
compare and format exactly as the strings they replaced, so player.team == "spectator" still
works.
-
player.privilegesanswersPrivilege.NONEwhere it answeredNone. Test== Privilege.NONE. Don't writeif player.privileges:, since every member is a non-empty string and so always truthy. The setter still takesNoneto clear. -
weapon_firedhands you aWeaponmember.weapon == "rl"silently never matches. Compare againstWeapon.ROCKET_LAUNCHER, or ask the member for.short.
position, velocity, weapons, ammo, powerups, keys, flight and weapon. What they
hand back has _replace, like a namedtuple.
player.weapons = player.weapons._replace(rl=True) # was player.weapons(rl=True)
player.position = player.position._replace(z=100) # was player.position(z=100)
player.position = (0, 0, 100) # a plain triple works too
player.weapon = minqlxtended.Weapon.ROCKET_LAUNCHERreset=True becomes NO_WEAPONS, NO_AMMO, NO_POWERUPS and NO_KEYS, so "only a rocket
launcher" is NO_WEAPONS._replace(rl=True).
player.powerups is milliseconds in both directions. Where the old method took quad=30,
write ._replace(quad=30 * 1000).
send_server_command, client_command, play_sound, play_music, stop_sound,
stop_music, configstring_variables, update_configstring, send_configstring and
send_configstring_overrides. So player.play_sound(p) instead of self.play_sound(p, player).
player.configstring is the CS_PLAYERS entry describing that player; player.send_configstring
sends them any index. update_configstring writes through the server's table so everyone sees
it, where the send_* pair reaches one client and is overwritten by the next server-side write.
-
Reading or writing state on an empty slot raises
EngineStateError, notAttributeError.Player.stateitself still answersNone. -
Player.update()clears the cached userinfo, soip,countryandmodelstop reporting pre-update values. -
Player.clan's setter is dirty-checked. If you wroteplayer.clan = player.clanto force theset_configstringdispatchers, useminqlxtended.set_configstring(minqlxtended.CS_PLAYERS + player.id, player.configstring). -
Player.qportreads the netchan rather than the userinfo key. -
Player.ip,ping,is_botandqportno longer fall back to the worse source. -
player.holdablewarns instead of raising for one it can't name, and reportsNone. -
Playerand the channels are hashable, soset(players)works.
All twenty-five, with thirteen also on Player. self.lock("red") becomes
self.game.lock("red"), self.tempban(player) becomes player.tempban(), and
self.slap(player, 50) is self.game.slap(player, 50) or player.slap(50).
Every field they wrote is an accessor on Entity, GameClient or level.
player.health = 100 # was set_health(player.id, 100)
player.god = True # was god(player.id, True)
game.is_training_map = True # was allow_single_player(True)
minqlxtended.GameClient(n).ps.speed = 10000 # was set_speed(n, 10000)
minqlxtended.GameClient(n).ps.stats[minqlxtended.StatIndex.ARMOR] = 200Gone: set_stats, set_position, set_velocity, noclip, god, notarget, set_flags,
set_health, set_armor, set_speed, set_gravity, set_weapons, set_weapon, set_ammo,
set_powerups, set_holdable, set_flight, set_keys, set_invulnerability, set_score,
set_privileges, allow_single_player.
Still there, because an accessor can't express them: drop_holdable spawns an entity,
player_spawn calls ClientSpawn, slay_with_mod calls G_Damage. player_state() and
player_stats() stay as digests spanning three structs in one call, as do the cvar,
configstring, demo and client-command families.
EntityFlaghas been renamedServerFlag, and the name reused forFL_*.SVF_*on.r.sv_flagsisServerFlag;EntityFlagis.flags, where god mode and notarget live.EntityFlag.BOTis anAttributeError, butentity.flags & ServerFlag.BOTis a perfectly good expression that always answers 0.
console, destroy_kamikaze_timers, remove_dropped_items, replace_items,
force_weapon_respawn_time (call the module function of the same name), and colored_name,
which is Plugin.player(name).name.
Plugin.client_id stayed, but prefer resolve_player/resolve_identifier: client_id hands
back a raw slot number without checking who is in it, so one resolved before a thread hop can
name whoever took the slot since.
Four names left the package namespace without leaving the framework:
minqlxtended.get_configstring is configstring(index, cached=False),
minqlxtended.parse_variables is parse_infostring, which raises on a malformed infostring
where it used to return what it could, and set_cvar_once/set_cvar_limit_once are on
Plugin.
-
Plugin.center_printis broadcast-only.player.center_print(msg)goes to one client. -
slaykills throughG_Damage, so it raisesdeathandkill. If you count deaths or award onkill, you will now see admin slays, carryingMod.SUICIDE. -
Game.slapandGame.slaydon't announce anything. The broadcast is inessentials.py. -
Game.red_scoreandGame.blue_scoreare gone. They were 1st and 2nd place rather than teams. Usegame.team_scores[minqlxtended.Team.RED.index]. -
Game.statereadslevel.warmup_timerather than parsingg_gameState. Same threeGameStatemembers. -
Game.round_stateraises for a state it doesn't know, instead of returning"unknown". -
Game(cached=...)is gone. The argument had no effect. Plugin.get_cvar()takes adefault.-
Plugin.msg(msg, chat_channel=...)wants a channel object. DefaultCHAT_CHANNEL. -
Plugin.play_sound()andplay_music()raiseValueErrorfor an empty or wrong-kind path instead of returningFalse. -
player_list=[]means "search nobody". An empty list used to search everyone. -
Command.nameis a string. The rest areCommand.aliases;Command.namesis all. - Every channel's
reply()takes the same arguments. Plugin.resolve_identifier()returns a named tuple.
The engine escapes nothing, so Game.opsay("hi; quit") used to be two commands, the second
stopping the server. These raise ValueError now:
| Refuses | In |
|---|---|
" ; newline |
Game.opsay, Game.change_map (map and factory), Plugin.play_sound, Plugin.play_music
|
" |
Plugin.center_print, Player.center_print, Plugin.send_configstring_to
|
ChatChannel.reply still substitutes " for ' rather than raising.
-
minqlxtended.set_cvar()returns theCvarobject instead of a bool, so you can read back what the engine settled on after a latch or clamp.Plugin.set_cvar()is unchanged. -
set_cvar(name, value, -1)no longer means "force". Passforce=True;flagsis aCvarFlagbitfield. -
Plugin.set_cvar()andset_cvar_limit()don't go through the console, so a value containing a quote no longer truncates or runs as a further command. -
minqlxtended.require_cvar(name)is new: aRuntimeErrornaming the cvar if it isn't set by the time it's read. -
Configstrings are cached.
configstring(index)andconfigstring_variables(index)read a cache kept in step with theset_configstringhook. Passcached=Falsefor the engine. -
configstring_variables()returns a read-only mapping. Copy withdict(...), or useupdate_configstring_variables(index, changes), which re-reads the engine first. -
apply_variable_changes(variables, changes)returns(variables, dirty)and doesn't modify its argument. -
Plugin.send_configstring_to(client_id, index, value)wassend_big_configstring. It takes any length and chunks intobcs0/bcs1/bcs2when needed.
-
database.Redisno longer closes the shared connection when a plugin'sdbis collected.close()closes only a connection that instance opened;Redis.close_shared()is explicit, andwith plugin.db as db:is there for a one-off job. -
redis-py 5.1+ is required, and it wants hiredis 3.0+. With an older hiredis the
accelerator installs and is then silently never used.
requirements.txtpins both. -
get_flags(players, flag, default)reads one flag for many players in a singleMGET.get_flagis one round-trip per player, up to 64 in one handler on a full server.
-
@minqlxtended.threadon a command handler is reported now. ItsReturn.USAGEnever reaches the invoker. Validate on the calling thread, move the slow part into an inner threaded helper. There is no worker pool:@threadstarts a fresh thread andThread.start()blocks the caller until it's running. -
Work a plugin scheduled stops when the plugin is unloaded, the unload half of a
!reloadincluded. A@threadworker can't be cancelled, so a loop that shouldn't outlive its plugin must checkself.is_loaded. - A plugin that raises partway through construction no longer leaves its hooks behind.
-
starting_weapon_bit(weapon)andtoggle_starting_weapon(value, weapon)handle theg_startingWeaponsbitfield. The bit is1 << (weapon - 1).weapon_tstarts at 1 and the field reserves no bit for its 0, so shifting by the member itself lands on another weapon. -
minqlxtended.console_command()defers the seven commands that reload the game module, and calls from a@threadworker. See Console Commands. -
minqlxtended.redirect_print()nests properly, where the old one ended both blocks. -
StatsListeneris athreading.Thread, andkeep_receiving()is nowstart(). -
minqlxtended.plugins_version()replaces__plugins_version__, andmap_titles()replaces the_map_titleattributes. Both used to raiseAttributeErrorbefore the first map load and answer"NOT_SET"and empty strings now. -
The internals left the package namespace: the
handle_*functions, the concrete*Dispatcherclasses and the configstring cache's maintenance calls. Reach them asminqlxtended._handlers.handle_frameif you must, but hook throughEVENT_DISPATCHERS["name"]or@minqlxtended.hookas before. -
minqlxtended.EVENT_DISPATCHERSis iterable, withkeys(),items()andlen(). An unknown event name suggests what you probably meant. @next_frame,@delayand@threadpreserve the wrapped function's name and docstring.-
There are type stubs.
python/_minqlxtended.pyidescribes the C extension and the package carriespy.typed. Point your editor orMYPYPATHatpython/. -
A byte-pattern miss stops the server starting, where it used to warn and fall back to
command parsing.
minqlxtended.native_events()is gone with the fallback. See Internals. -
Plugin.plugin(name)returns another loaded plugin's instance orNone. Prefer it overself.plugins[name]. -
MAX_CLIENTSandMAX_CONFIGSTRINGSare exported, so stop hardcoding 64 and 1024. -
Game.is_team_basedis new, as isGametype.is_team_based. -
minqlxtended.add_event(entity_id, event, event_parm=0)is new, with theEntityEventenum. It's how to tell clients to play a sound or draw an effect.