wowie zowie wowie zowie

This commit is contained in:
kai
2026-10-25 08:51:39 +00:00
parent 705a6f1cff
commit a391673eb6
24 changed files with 1523 additions and 94 deletions
+1
View File
@@ -42,3 +42,4 @@ profile/*
logs/*
private/*
.DS_Store
patch/*
+14
View File
@@ -1,3 +1,17 @@
# AssaultCube+
## this is a fork of AssaultCube with very cool ass features
changes:
match settings menu
speed display `showspeed`/`showspeedpos`
gun weight (heavier weapons slow you down)
unicode support (for russian larping)
original description below
## What is AssaultCube?
AssaultCube is a **FREE**, multiplayer, first-person shooter game, based on the
[CUBE engine](http://cubeengine.com/cube.php).
BIN
View File
Binary file not shown.
Executable
+3
View File
@@ -0,0 +1,3 @@
cd ~/AC/source/src/
make -j8 CXX=clang++ -j$(nproc) install
cd ~/AC/
+1
View File
@@ -3,6 +3,7 @@
exec config/font_default.cfg // Must be executed! DO NOT MODIFY.
exec config/font_monospace.cfg // Must be executed! DO NOT MODIFY.
exec config/font_serif.cfg
exec config/font_unicode.cfg // extra glyphs (Latin-1/Ext-A, Greek, Cyrillic) for the fonts above; needs the "fontselect" and "fontcharu" commands
// Then, in that file, define the font. To do this, first define the font file.
// See: ./docs/reference.xml#identifier_font for the command to do this.
File diff suppressed because it is too large Load Diff
+10 -7
View File
@@ -170,8 +170,12 @@ menuitemslider "Recoil Multiplier: " 50 500 "$matchrecoilmul" 10 [ matchrecoilmu
menuitemslider "Spread Multiplier: " 50 500 "$matchspreadmul" 10 [ matchspreadmul $arg1 ]
menuitemslider "Speed Multiplier: " 50 500 "$matchspeedmul" 10 [ matchspeedmul $arg1 ]
menuitemslider "Gravity Multiplier: " 50 500 "$matchgravity" 10 [ matchgravity $arg1 ]
menuitemslider "Friction Multiplier: " 10 500 "$matchfriction" 10 [ matchfriction $arg1 ]
menuitem "" -1
menuitemcheckbox "Enable Assists: " "$matchassist" [ matchassist $arg1 ]
menuitemcheckbox "Explosive Deaths: " "$matchdeathexplosion" [ matchdeathexplosion $arg1 ]
menuitemslider " Blast Radius %: " 10 300 "$matchdeathradius" 10 [ matchdeathradius $arg1 ]
menuitemslider " Blast Power %: " 10 500 "$matchdeathpower" 10 [ matchdeathpower $arg1 ]
menuitem "" -1
menuitem "Friendly Fire\i\3" [ showmenu [Match Friendly Fire] ]
menuitem "" -1
@@ -179,7 +183,7 @@ menuitem "Spawn Weapons\i\3" [ showmenu [Match Spawn Weapons] ]
menuitem "" -1
menuitem "\f2Start Bot Match" [ startbotmap ]
menuitem "" -1
menuitem "Reset to defaults (100%)" [ matchrecoilmul 100; matchspreadmul 100; matchspeedmul 100; matchgravity 100; matchassist 1; matchfriendlyfire 1; matchspawnprimary 6; matchspawnsecondary 1; matchspawntertiary 0; matchspawnquaternary -1 ]
menuitem "Reset to defaults (100%)" [ matchrecoilmul 100; matchspreadmul 100; matchspeedmul 100; matchgravity 100; matchfriction 100; matchdeathradius 100; matchdeathpower 100; matchassist 1; matchfriendlyfire 1; matchdeathexplosion 0; matchspawnprimary -1; matchspawnsecondary -1; matchspawntertiary -1; matchspawnquaternary -1; showmenu [Match Settings] ]
menuitem "Back" [ showmenu Mastermode ]
//// In-Game admin > Match Settings > Friendly Fire ////
@@ -196,7 +200,7 @@ menuitem "Back" [ showmenu [Match Settings] ]
newmenu [Match Spawn Weapons]
menuinit [ ]
menutitle "Select weapons to spawn with (order = priority)"
menutitle "Spawn weapons (primary = what you hold, others = extra guns)"
menuitem "Primary Weapon\i\3" [ showmenu [Match Spawn Primary] ]
menuitem "Secondary Weapon\i\3" [ showmenu [Match Spawn Secondary] ]
menuitem "Tertiary Weapon\i\3" [ showmenu [Match Spawn Tertiary] ]
@@ -208,6 +212,7 @@ menuitem "Back" [ showmenu [Match Settings] ]
newmenu [Match Spawn Primary]
menuinit [ ]
menuitem "Default (your own choice)" [ matchspawnprimary -1; showmenu [Match Spawn Weapons] ]
menuitem "Knife" [ matchspawnprimary 0; showmenu [Match Spawn Weapons] ]
menuitem "Pistol" [ matchspawnprimary 1; showmenu [Match Spawn Weapons] ]
menuitem "Carbine" [ matchspawnprimary 2; showmenu [Match Spawn Weapons] ]
@@ -216,7 +221,6 @@ menuitem "Subgun" [ matchspawnprimary 4; showmenu [Match Spawn Weapons] ]
menuitem "Sniper" [ matchspawnprimary 5; showmenu [Match Spawn Weapons] ]
menuitem "Assault" [ matchspawnprimary 6; showmenu [Match Spawn Weapons] ]
menuitem "Grenade" [ matchspawnprimary 7; showmenu [Match Spawn Weapons] ]
menuitem "Akimbo" [ matchspawnprimary 8; showmenu [Match Spawn Weapons] ]
menuitem "" -1
menuitem "Back" [ showmenu [Match Spawn Weapons] ]
@@ -224,6 +228,7 @@ menuitem "Back" [ showmenu [Match Spawn Weapons] ]
newmenu [Match Spawn Secondary]
menuinit [ ]
menuitem "None" [ matchspawnsecondary -1; showmenu [Match Spawn Weapons] ]
menuitem "Knife" [ matchspawnsecondary 0; showmenu [Match Spawn Weapons] ]
menuitem "Pistol" [ matchspawnsecondary 1; showmenu [Match Spawn Weapons] ]
menuitem "Carbine" [ matchspawnsecondary 2; showmenu [Match Spawn Weapons] ]
@@ -232,7 +237,6 @@ menuitem "Subgun" [ matchspawnsecondary 4; showmenu [Match Spawn Weapons]
menuitem "Sniper" [ matchspawnsecondary 5; showmenu [Match Spawn Weapons] ]
menuitem "Assault" [ matchspawnsecondary 6; showmenu [Match Spawn Weapons] ]
menuitem "Grenade" [ matchspawnsecondary 7; showmenu [Match Spawn Weapons] ]
menuitem "Akimbo" [ matchspawnsecondary 8; showmenu [Match Spawn Weapons] ]
menuitem "" -1
menuitem "Back" [ showmenu [Match Spawn Weapons] ]
@@ -240,6 +244,7 @@ menuitem "Back" [ showmenu [Match Spawn Weapons] ]
newmenu [Match Spawn Tertiary]
menuinit [ ]
menuitem "None" [ matchspawntertiary -1; showmenu [Match Spawn Weapons] ]
menuitem "Knife" [ matchspawntertiary 0; showmenu [Match Spawn Weapons] ]
menuitem "Pistol" [ matchspawntertiary 1; showmenu [Match Spawn Weapons] ]
menuitem "Carbine" [ matchspawntertiary 2; showmenu [Match Spawn Weapons] ]
@@ -248,7 +253,6 @@ menuitem "Subgun" [ matchspawntertiary 4; showmenu [Match Spawn Weapons]
menuitem "Sniper" [ matchspawntertiary 5; showmenu [Match Spawn Weapons] ]
menuitem "Assault" [ matchspawntertiary 6; showmenu [Match Spawn Weapons] ]
menuitem "Grenade" [ matchspawntertiary 7; showmenu [Match Spawn Weapons] ]
menuitem "Akimbo" [ matchspawntertiary 8; showmenu [Match Spawn Weapons] ]
menuitem "" -1
menuitem "Back" [ showmenu [Match Spawn Weapons] ]
@@ -256,6 +260,7 @@ menuitem "Back" [ showmenu [Match Spawn Weapons] ]
newmenu [Match Spawn Quaternary]
menuinit [ ]
menuitem "None" [ matchspawnquaternary -1; showmenu [Match Spawn Weapons] ]
menuitem "Knife" [ matchspawnquaternary 0; showmenu [Match Spawn Weapons] ]
menuitem "Pistol" [ matchspawnquaternary 1; showmenu [Match Spawn Weapons] ]
menuitem "Carbine" [ matchspawnquaternary 2; showmenu [Match Spawn Weapons] ]
@@ -264,8 +269,6 @@ menuitem "Subgun" [ matchspawnquaternary 4; showmenu [Match Spawn Weapons
menuitem "Sniper" [ matchspawnquaternary 5; showmenu [Match Spawn Weapons] ]
menuitem "Assault" [ matchspawnquaternary 6; showmenu [Match Spawn Weapons] ]
menuitem "Grenade" [ matchspawnquaternary 7; showmenu [Match Spawn Weapons] ]
menuitem "Akimbo" [ matchspawnquaternary 8; showmenu [Match Spawn Weapons] ]
menuitem "None" [ matchspawnquaternary -1; showmenu [Match Spawn Weapons] ]
menuitem "" -1
menuitem "Back" [ showmenu [Match Spawn Weapons] ]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 199 KiB

+157
View File
@@ -0,0 +1,157 @@
#!/usr/bin/env python3
"""
Appends extra glyphs (Latin-1, Latin Extended-A, Greek, Cyrillic, some punctuation) to the three built-in
AssaultCube font atlases and writes config/font_unicode.cfg with the matching "fontcharu" definitions.
Run from the AssaultCube root folder:
python3 source/dev_tools/gen_unicode_fonts.py [--fonts-dir /usr/share/fonts/truetype/dejavu]
Requires Pillow and the DejaVu fonts (any TTF with the needed coverage works, see FONTS below).
The ASCII part of every atlas is never touched: the script cuts each png back to its original size first, so
running it repeatedly is safe. The new glyphs are appended below the old ones; the image gets wider, which is fine
because all fontchar coordinates are pixel based.
"""
import argparse, math, os, re, sys
from PIL import Image, ImageDraw, ImageFont
# glyph sets: (first, last) inclusive
RANGES = [
(0x00A1, 0x00AC), (0x00AE, 0x00FF), # Latin-1 supplement (no NBSP, no soft hyphen)
(0x0100, 0x017F), # Latin Extended-A (Polish, Czech, Hungarian, Turkish, Baltic, ...)
(0x0386, 0x0386), (0x0388, 0x038A), (0x038C, 0x038C), (0x038E, 0x03A1), (0x03A3, 0x03CE), # Greek
(0x0400, 0x045F), (0x0490, 0x0491), # Cyrillic: Russian, Ukrainian, Belarusian, Serbian, Macedonian, ...
(0x2013, 0x2014), (0x2018, 0x201A), (0x201C, 0x201E), (0x2022, 0x2022), (0x2026, 0x2026),
(0x20AC, 0x20AC), (0x2116, 0x2116), # dashes, quotes, bullet, ellipsis, euro, numero
]
# the serif atlas is the largest per glyph, so it gets a smaller set (keeps the texture at 1024x2048)
RANGES_SMALL = [(0x00A1, 0x00AC), (0x00AE, 0x00FF), (0x0400, 0x045F), (0x0490, 0x0491),
(0x2013, 0x2014), (0x2018, 0x201A), (0x201C, 0x201E), (0x2022, 0x2022), (0x2026, 0x2026), (0x20AC, 0x20AC), (0x2116, 0x2116)]
FONTS = {
# name: (png, cfg, ttf, outline px, original png size)
'default': dict(png='chars_default.png', cfg='font_default.cfg', ttf='DejaVuSans-Bold.ttf', stroke=4, orig=(512, 512)),
'mono': dict(png='chars_monospace.png', cfg='font_monospace.cfg', ttf='DejaVuSansMono-Bold.ttf', stroke=0, orig=(512, 512)),
'serif': dict(png='chars_serif.png', cfg='font_serif.cfg', ttf='DejaVuSerif-Bold.ttf', stroke=0, orig=(690, 690), ranges=RANGES_SMALL),
}
NEWW = 1024 # width of the extended atlases (power of two; old glyph pixel coordinates stay valid)
def parse_cfg(path):
hdr, chars = None, []
for line in open(path, encoding='utf-8'):
line = line.split('//')[0].strip()
if line.startswith('font '):
t = line.replace('"', ' ').split()
nums = [int(v) for v in t[3:]]
nums += [0] * (7 - len(nums))
hdr = dict(name=t[1], png=t[2], dw=nums[0], dh=nums[1], ox=nums[2], oy=nums[3], ow=nums[4], oh=nums[5])
elif line.startswith('fontchar '):
v = [int(x) for x in line.split()[1:]]
v += [0] * (4 - len(v))
chars.append(v)
return hdr, chars
def alpha_bbox(im, thr=100):
return im.getchannel('A').point(lambda v: 255 if v > thr else 0).getbbox()
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--fonts-dir', default='/usr/share/fonts/truetype/dejavu')
ap.add_argument('--root', default='.')
a = ap.parse_args()
out_cfg = ['// Extra glyphs for the built-in fonts: Latin-1, Latin Extended-A, Greek, Cyrillic, some punctuation.',
'// Generated by source/dev_tools/gen_unicode_fonts.py - do not edit by hand.',
'// "fontselect" makes an existing font the target of the following "fontcharu" commands (cp x y w h).', '']
for fname, fc in FONTS.items():
cfgp = os.path.join(a.root, 'config', fc['cfg'])
pngp = os.path.join(a.root, 'packages', 'misc', fc['png'])
hdr, chars = parse_cfg(cfgp)
cps = [c for lo, hi in fc.get('ranges', RANGES) for c in range(lo, hi + 1)]
atlas = Image.open(pngp)
mode = atlas.mode
atlas = atlas.crop((0, 0) + fc['orig']).convert('RGBA') # cut back to original -> idempotent
# reference glyph 'H' defines size and baseline
hx, hy, hw, hh = chars[ord('H') - 33]
hw = hw or hdr['dw']; hh = hh or hdr['dh']
crop = atlas.crop((hx + hdr['ox'], hy + hdr['oy'], hx + hw + hdr['ow'], hy + hh + hdr['oh']))
l, t, r, b = alpha_bbox(crop)
ref_h, ref_w = b - t, r - l
crop_base = crop.size[1] # crop height of a normal glyph
maxcrop = crop_base + 4 # glyphs with descenders may be a bit taller (like 'g' in the original)
pad_total = hw - ref_w
stroke = fc['stroke']
ttf = os.path.join(a.fonts_dir, fc['ttf'])
# fit font size so that the rendered 'H' (incl. outline) has the reference height
def render(ch, size, origin=(150, 250)):
f = ImageFont.truetype(ttf, size)
im = Image.new('RGBA', (400, 400), (0, 0, 0, 0))
d = ImageDraw.Draw(im)
d.text(origin, ch, font=f, fill=(255, 255, 255, 255), stroke_width=stroke, stroke_fill=(0, 0, 0, 255), anchor='ls')
return im, alpha_bbox(im, 8), origin
size = 20
for s in range(20, 120):
_, bb, _ = render('H', s)
if bb and bb[3] - bb[1] >= ref_h: size = s; break
base_y = b - stroke # row of the baseline inside the crop
mono = hdr['dw'] and fname == 'mono'
print('%s: ref H ink %dx%d, crop %dx%d, ttf size %d, baseline row %d' % (fname, ref_w, ref_h, crop.size[0], crop_base, size, base_y))
glyphs = [] # (cp, image (cell crop), w, crop_h)
for cp in cps:
ch = chr(cp)
im, bb, org = render(ch, size)
if not bb: continue
ext_above, ext_below = org[1] - bb[1], bb[3] - org[1]
sc = 1.0
if ext_above > base_y - 1: sc = min(sc, (base_y - 1) / ext_above)
if ext_below > maxcrop - base_y - 1: sc = min(sc, (maxcrop - base_y - 1) / ext_below)
if sc < 1.0:
im, bb, org = render(ch, max(8, int(size * sc)))
ext_above, ext_below = org[1] - bb[1], bb[3] - org[1]
ink = im.crop((bb[0], 0, bb[2], im.size[1]))
ink_w = bb[2] - bb[0]
cell_w = hdr['dw'] if mono else ink_w + pad_total
if mono and ink_w > cell_w: # keep monospace cells fixed width
k = (cell_w - 1) / ink_w
ink = ink.resize((cell_w - 1, ink.size[1])); ink_w = cell_w - 1
crop_h = max(crop_base, min(maxcrop, base_y + ext_below + 1))
cell = Image.new('RGBA', (cell_w, crop_h), (0, 0, 0, 0))
px = (cell_w - ink_w) // 2
py = base_y - org[1] # paste so that baseline lands on base_y
cell.alpha_composite(ink, (px, py)) if py >= 0 else cell.alpha_composite(ink.crop((0, -py, ink_w, ink.size[1])), (px, 0))
glyphs.append((cp, cell, cell_w, crop_h))
# pack into rows below the original atlas
pitch = maxcrop + 4
x, row, placed = 1, 0, []
for cp, cell, w, ch_ in glyphs:
if x + w + 1 > NEWW: x, row = 1, row + 1
placed.append((cp, cell, x, row, w, ch_)); x += w + 1
top0 = fc['orig'][1]
if fname == 'serif': top0 = max(top0, 700)
need = top0 + (row + 1) * pitch
newh = 1 << max(8, math.ceil(math.log2(need)))
big = Image.new('RGBA', (NEWW, newh), (0, 0, 0, 0))
big.paste(atlas, (0, 0))
out_cfg += ['fontselect %s' % hdr['name']]
for cp, cell, cx, crow, w, ch_ in placed:
ry = top0 + crow * pitch
big.paste(cell, (cx, ry))
cfg_y = ry - hdr['oy']
cfg_h = ch_ - (hdr['oh'] - hdr['oy'])
out_cfg.append('fontcharu %d %d %d %d %d\t// U+%04X %s' % (cp, cx - hdr['ox'], cfg_y, w, cfg_h, cp, chr(cp)))
out_cfg.append('')
if mode == 'LA':
big = Image.merge('LA', (big.convert('L'), big.getchannel('A')))
big.save(pngp, optimize=True)
print(' wrote %s %dx%d, %d glyphs' % (pngp, NEWW, newh, len(placed)))
open(os.path.join(a.root, 'config', 'font_unicode.cfg'), 'w', encoding='utf-8').write('\n'.join(out_cfg))
print('wrote config/font_unicode.cfg')
if __name__ == '__main__':
main()
+4
View File
@@ -23,6 +23,7 @@ struct serverstate servstate = { 0 };
extern int matchassist;
extern int matchfriendlyfire;
extern int matchdeathexplosion;
playerent *player1 = newplayerent(); // our client
vector<playerent *> players; // other clients
@@ -1028,6 +1029,9 @@ void dokill(playerent *pl, playerent *act, bool gib, int gun)
}
}
}
// explosive deaths: online the server applies the blast (and sends SV_DEATHBLAST), offline/bot games do it here
if(matchdeathexplosion && !m_mp(gamemode) && !m_demo) localdeathblast(pl);
}
void pstat_weap(int *cn)
+8
View File
@@ -749,6 +749,14 @@ void parsemessages(int cn, playerent *d, ucharbuf &p, bool demo = false)
break;
}
case SV_DEATHBLAST: // "explosive deaths": the damage comes as normal SV_DAMAGE messages, this is just the visual/sound
{
int cn = getint(p);
playerent *d = getclient(cn);
if(d) deathblasteffect(d->o);
break;
}
case SV_RESUME:
{
loopi(MAXCLIENTS)
+13 -7
View File
@@ -70,8 +70,9 @@ struct textinputbuffer
{
int len = (int)strlen(buf);
if(pos<0) break;
memmove(&buf[pos], &buf[pos+1], len - pos);
if(pos>=len-1) pos = -1;
int cl = utf8charlen(&buf[pos]); // a multibyte character is removed as a whole
memmove(&buf[pos], &buf[pos+cl], len - pos - cl + 1);
if(pos>=len-cl) pos = -1;
return true;
}
@@ -79,19 +80,24 @@ struct textinputbuffer
{
int len = (int)strlen(buf), i = pos>=0 ? pos : len;
if(i<1) break;
memmove(&buf[i-1], &buf[i], len - i + 1);
if(pos>0) pos--;
int p = utf8prev(buf, i); // start of the character before the cursor
memmove(&buf[p], &buf[i], len - i + 1);
if(pos>0) pos = p;
else if(!pos && len<=1) pos = -1;
return true;
}
case SDLK_LEFT:
if(pos > 0) pos--;
else if(pos < 0) pos = (int)strlen(buf)-1;
if(pos > 0) pos = utf8prev(buf, pos);
else if(pos < 0) { int len = (int)strlen(buf); pos = len > 0 ? utf8prev(buf, len) : -1; }
break;
case SDLK_RIGHT:
if(pos>=0 && ++pos>=(int)strlen(buf)) pos = -1;
if(pos>=0)
{
pos += utf8charlen(&buf[pos]);
if(pos>=(int)strlen(buf)) pos = -1;
}
break;
case SDLK_v:
+41 -2
View File
@@ -81,6 +81,11 @@ enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_CARBINE, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIP
#define SGMAXDMGABS 105
#define SGMAXDMGLOC 84
#define EXPDAMRAD 10
// "explosive deaths" match option. Defaults: radius of a grenade, 75% of a grenade's damage at the centre.
// matchdeathradius / matchdeathpower are percentages of those defaults (server.cpp).
extern int matchdeathradius, matchdeathpower;
#define DEATHBLASTRAD (EXPDAMRAD*matchdeathradius/100.0f)
#define DEATHBLASTDMG (guns[GUN_GRENADE].damage*3/4*matchdeathpower/100)
struct itemstat { int add, start, max, sound; };
extern itemstat ammostats[NUMGUNS];
@@ -333,11 +338,34 @@ public:
ammo[GUN_KNIFE] = mag[GUN_KNIFE] = 1;
}
// match settings (defined in server.cpp): -1 = not set, use the normal behaviour
#define matchgunok(g) (valid_weapon(g) && (g) != GUN_AKIMBO) // akimbo is a timed pickup, it can't be a spawn weapon
// gives a gun with its normal starting load (used for the match spawn loadout)
void givegun(int gun)
{
if(!matchgunok(gun)) return;
switch(gun)
{
case GUN_KNIFE: ammo[GUN_KNIFE] = mag[GUN_KNIFE] = 1; break;
case GUN_GRENADE: ammo[GUN_GRENADE] = 0; mag[GUN_GRENADE] = max(mag[GUN_GRENADE], 2); break;
default:
ammo[gun] = max(ammo[gun], ammostats[gun].start-magsize(gun));
mag[gun] = magsize(gun);
break;
}
}
virtual void spawnstate(int gamemode)
{
extern int matchspawnprimary, matchspawnsecondary, matchspawntertiary, matchspawnquaternary;
const bool fixedloadout = m_pistol || m_osok || m_lss; // these modes dictate the weapons
const bool matchprimary = !fixedloadout && matchgunok(matchspawnprimary);
if(m_pistol) primary = GUN_PISTOL;
else if(m_osok) primary = GUN_SNIPER;
else if(m_lss) primary = GUN_KNIFE;
else if(matchprimary) primary = matchspawnprimary;
else primary = nextprimary;
if(!m_nopistol)
@@ -348,8 +376,19 @@ public:
if(!m_noprimary)
{
ammo[primary] = ammostats[primary].start-magsize(primary);
mag[primary] = magsize(primary);
if(matchprimary) givegun(primary);
else
{
ammo[primary] = ammostats[primary].start-magsize(primary);
mag[primary] = magsize(primary);
}
}
if(!fixedloadout) // extra guns from the match settings: they can be switched to like the pistol/knife
{
givegun(matchspawnsecondary);
givegun(matchspawntertiary);
givegun(matchspawnquaternary);
}
gunselect = primary;
+3 -1
View File
@@ -397,6 +397,7 @@ FVARP(flyspeed, 1.0, 2.0, 5.0);
extern int matchgravity;
extern int matchspeedmul;
extern int matchfriction;
void moveplayer(physent *pl, int moveres, bool local, int curtime)
{
@@ -458,7 +459,8 @@ void moveplayer(physent *pl, int moveres, bool local, int curtime)
}
const float speed = curtime/(water ? 2000.0f : 1000.0f)*pl->maxspeed*weaponspeedmul*(matchspeedmul ? matchspeedmul / 100.0f : 1.0f)*(crouching && pl->state != CS_EDITING ? chspeed : 1.0f)*(pl==player1 && isfly ? flyspeed : 1.0f);
const float friction = water ? 20.0f : (pl->onfloor || isfly ? 6.0f : (pl->onladder ? 1.5f : 30.0f));
// the constants below are really "inertia" (higher = slides more), so more friction means dividing them
const float friction = (water ? 20.0f : (pl->onfloor || isfly ? 6.0f : (pl->onladder ? 1.5f : 30.0f))) * 100.0f / clamp(matchfriction, 10, 500);
const float fpsfric = max(friction/curtime*20.0f, 1.0f);
d.x = (float)(move*cosf(RAD*(pl->yaw-90)));
+16 -1
View File
@@ -306,6 +306,7 @@ char *filtertext(char *dst, const char *src, int flags, int len)
mapname = (flags & FTXT_MAPNAME) != 0, // only allows lowercase chars, digits, '_', '-' and '.'; probably should be used in combination with TOLOWER
cropwhitelead = (flags & FTXT_CROPWHITE_LEAD) != 0, // removes leading whitespace
cropwhitetrail = (flags & FTXT_CROPWHITE_TRAIL) != 0, // removes trailing whitespace
utf8 = (flags & FTXT_UTF8) != 0, // allows valid UTF-8 sequences (without control/format/bidi codepoints); the 7-bit rule applies to the rest
pass = false;
if(leet || mapname) nocolor = true;
#ifdef FILENAMESALLLOWERCASE
@@ -316,7 +317,20 @@ char *filtertext(char *dst, const char *src, int flags, int len)
bool insidepointybrackets = false;
for(int c = *src; c; c = *++src)
{
c &= 0x7F; // 7-bit ascii. not negotiable.
if(utf8 && (c & 0x80) && !filename && !mapname && !safecs)
{ // multibyte character: validate and copy as a whole, never cut it in half
int cl, cp = utf8decode(src, cl);
if(cp < 0 || (cp <= 0xA0) || (cp >= 0x200B && cp <= 0x200F) || (cp >= 0x2028 && cp <= 0x202E) || (cp >= 0x2060 && cp <= 0x206F) || cp == 0xFEFF || (cp >= 0xFFF0 && cp <= 0xFFFF)) continue; // invalid, C1 controls, zero-width/bidi/format chars
if(len < cl) break; // does not fit
loopi(cl) *dst++ = src[i];
src += cl - 1;
lastwhite = NULL;
cropwhitelead = false;
len -= cl;
if(len <= 0) break;
continue;
}
c &= 0x7F; // 7-bit ascii. not negotiable (unless FTXT_UTF8 and a valid multibyte sequence, see above).
pass = false;
if(trans)
{
@@ -606,6 +620,7 @@ static const int msgsizes[] = // size inclusive message token, 0 f
SV_MAPIDENT, 3, SV_DEMOCHECKSUM, 0, SV_DEMOSIGNATURE, 0,
SV_PAUSEMODE, 1,
SV_GETVITA, 2, SV_VITADATA, 0,
SV_DEATHBLAST, 2,
-1
};
+6 -4
View File
@@ -7,7 +7,7 @@
#define CUBE_SERVINFO_PORT(serverport) (serverport+1)
#define CUBE_SERVINFO_TO_SERV_PORT(servinfoport) (servinfoport-1)
#define CLIENTIPCENSOR 12 // blank out the last 12 bits of client's IPs before telling them to others (compromise between "16" from AC 0.9x and "8" from AC 1.1)
#define PROTOCOL_VERSION 1301 // bump when protocol changes (use negative numbers for mods!) - FORK VERSION
#define PROTOCOL_VERSION 1302 // bump when protocol changes (use negative numbers for mods!) - FORK VERSION
#define DEMO_VERSION 2 // bump when demo format changes
#define DEMO_MAGIC "ASSAULTCUBE_DEMO"
#define DEMO_MINTIME 10000 // don't keep demo recordings with less than 10 seconds
@@ -51,6 +51,7 @@ enum
SV_MAPIDENT, SV_DEMOCHECKSUM, SV_DEMOSIGNATURE,
SV_PAUSEMODE,
SV_GETVITA, SV_VITADATA,
SV_DEATHBLAST,
SV_NUM
};
@@ -213,6 +214,7 @@ enum
#define FTXT_MAPNAME (1<<21)
#define FTXT_CROPWHITE_LEAD (1<<22)
#define FTXT_CROPWHITE_TRAIL (1<<23)
#define FTXT_UTF8 (1<<24) // let valid UTF-8 sequences pass (otherwise everything is forced to 7-bit ascii)
// character classes
#define FTXT__ALIAS (FTXT_NOWHITE | FTXT_NOCOLOR | FTXT_SAFECS | FTXT_MAPNAME)
#define FTXT__SERVDESC (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK)
@@ -227,8 +229,8 @@ enum
#define FTXT__SERVERINFOLINE (FTXT_ALLOWTAB | FTXT_CROPWHITE_LEAD | FTXT_CROPWHITE_TRAIL)
#define FTXT__SERVERINFO (FTXT_ALLOWTAB | FTXT_ALLOWNL)
#define FTXT__MOTD (FTXT_TABTOBLANK)
#define FTXT__CHAT (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_NOCOLOR | FTXT_TABTOBLANK | FTXT_CROPWHITE_TRAIL)
#define FTXT__LOG (FTXT_NOCOLOR | FTXT_TABTOBLANK)
#define FTXT__CHAT (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_NOCOLOR | FTXT_TABTOBLANK | FTXT_CROPWHITE_TRAIL | FTXT_UTF8)
#define FTXT__LOG (FTXT_NOCOLOR | FTXT_TABTOBLANK | FTXT_UTF8)
#define FTXT__LOGIDENT (FTXT_NOCOLOR | FTXT_NOWHITE | FTXT_FILENAME)
#define FTXT__PREFERENCES (FTXT_NOCOLOR | FTXT_NOWHITE | FTXT_FILENAME)
#define FTXT__AUTH (FTXT_NOCOLOR | FTXT_NOWHITE)
@@ -239,7 +241,7 @@ enum
#define FTXT__MEDIAFILEPATH (FTXT_FILENAME | FTXT_ALLOWSLASH)
#define FTXT__ZIPDESC (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_TABTOBLANK | FTXT_NOCOLOR | FTXT_SAFECS)
#define FTXT__CERTFILENAME (FTXT_MAPNAME | FTXT_ALLOWSLASH | FTXT_TOLOWER)
#define FTXT__CONSOLEPASTE (FTXT_TABTOBLANK)
#define FTXT__CONSOLEPASTE (FTXT_TABTOBLANK | FTXT_UTF8)
#define FTXT__AUTOKEYMAPNAME (FTXT_SAFECS)
#define FTXT__VITACOMMENT (FTXT_NOWHITE | FTXT_ALLOWBLANKS | FTXT_NOCOLOR | FTXT_TABTOBLANK)
#define FTXT__VITACLAN (FTXT_NOWHITE | FTXT_NOCOLOR)
+11 -1
View File
@@ -561,6 +561,8 @@ extern void deathstate(playerent *pl);
extern void spawnplayer(playerent *d);
extern void dodamage(int damage, playerent *pl, playerent *actor, int gun = -1, bool gib = false, bool local = true);
extern void dokill(playerent *pl, playerent *act, bool gib = false, int gun = 0);
extern void deathblasteffect(const vec &o); // "explosive deaths" match option: visuals + sound
extern void localdeathblast(playerent *victim); // ... and the damage for offline/bot games (online, the server does that)
extern playerent *newplayerent();
extern botent *newbotent();
extern void freebotent(botent *d);
@@ -705,10 +707,18 @@ struct font
char *name;
Texture *tex;
vector<charinfo> chars;
vector<charinfo> chars; // ascii glyphs, index = char - skip
hashtable<int, charinfo> uchars; // non-ascii glyphs (e.g. cyrillic), key = unicode codepoint, see "fontcharu"
int defaultw, defaulth;
int offsetx, offsety, offsetw, offseth;
int skip;
charinfo *getglyph(int cp) // NULL if the font has no glyph for this codepoint
{
if(cp < 0) return NULL;
if(cp < 0x80) return chars.inrange(cp - skip) ? &chars[cp - skip] : NULL;
return uchars.access(cp);
}
};
#define VIRTH 1800
+67 -30
View File
@@ -26,6 +26,7 @@ void newfont(char *name, char *tex, int *defaultw, int *defaulth, int *offsetx,
f->tex = _tex;
f->chars.shrink(0);
f->uchars.clear();
f->defaultw = *defaultw;
f->defaulth = *defaulth;
f->offsetx = *offsetx;
@@ -52,6 +53,27 @@ void fontchar(int *x, int *y, int *w, int *h)
}
COMMAND(fontchar, "iiii");
void fontcharu(int *cp, int *x, int *y, int *w, int *h) // glyph for a unicode codepoint (non-ascii), same geometry rules as fontchar
{
if(!fontdef || *cp < 0x80 || *cp > 0x10FFFF) return;
font::charinfo &c = fontdef->uchars[*cp];
c.w = *w ? *w : fontdef->defaultw;
c.h = *h ? *h : fontdef->defaulth;
c.left = (*x + fontdef->offsetx) / float(fontdef->tex->xs);
c.top = (*y + fontdef->offsety) / float(fontdef->tex->ys);
c.right = (*x + c.w + fontdef->offsetw) / float(fontdef->tex->xs);
c.bottom = (*y + c.h + fontdef->offseth) / float(fontdef->tex->ys);
}
COMMAND(fontcharu, "iiiii");
void fontselect(char *name) // make an already defined font the target of following fontchar/fontcharu/fontskip commands (does not touch its glyphs)
{
font *f = fonts.access(name);
if(f) fontdef = f;
}
COMMAND(fontselect, "s");
void fontskip(int *n)
{
if(!fontdef) return;
@@ -102,7 +124,7 @@ void draw_textf(const char *fstr, int left, int top, ...)
draw_text(str, left, top);
}
inline int draw_char_contd(font &f, font::charinfo &info, int charcode, int x, int y)
inline int draw_char_contd(font &f, font::charinfo &info, int x, int y)
{
glTexCoord2f(info.left, info.top ); glVertex2f(x, y);
glTexCoord2f(info.right, info.top ); glVertex2f(x + info.w, y);
@@ -113,14 +135,9 @@ inline int draw_char_contd(font &f, font::charinfo &info, int charcode, int x, i
return info.w;
}
static int draw_char(int c, int x, int y)
static int draw_char(font::charinfo *info, int x, int y)
{
if(curfont->chars.inrange(c-curfont->skip))
{
font::charinfo &info = curfont->chars[c-curfont->skip];
return draw_char_contd(*curfont, info, c, x, y);
}
if(info) return draw_char_contd(*curfont, *info, x, y);
return 0;
}
@@ -261,42 +278,52 @@ void text_endcolumns()
else x = TABALIGN(x);
// decode the character at str[idx]; sets clen to the bytes used. Non-ascii bytes are handled as UTF-8 (invalid ones yield -1 and are skipped)
#define TEXTDECODE(str, idx, c, clen) \
int c = (uchar)(str)[idx], clen = 1; \
if(c >= 0x80) c = utf8decode((str) + (idx), clen);
#define TEXTSKELETON \
int y = 0, x = 0, col = 0, colx = 0;\
int i;\
font::charinfo *glyph = NULL;\
for(i = 0; str[i]; i++)\
{\
TEXTINDEX(i)\
int c = str[i];\
TEXTDECODE(str, i, c, clen)\
if(c=='\t') { TEXTTAB(i); TEXTWHITE(i) }\
else if(c==' ') { x += curfont->defaultw; TEXTWHITE(i) }\
else if(c=='\n') { TEXTLINE(i) x = 0; y += FONTH; }\
else if(c=='\f') { if(str[i+1]) { i++; TEXTCOLOR(i) }}\
else if(c=='\1') { if(str[i+1]) { i++; TEXTIGRAPH(i) }}\
else if(c=='\a') { if(str[i+1]) { i++; }}\
else if(curfont->chars.inrange(c-curfont->skip))\
else if((glyph = curfont->getglyph(c)) != NULL)\
{\
int ci = i; /* first byte of this character */\
if(maxwidth != -1)\
{\
int j = i;\
int w = curfont->chars[c-curfont->skip].w;\
int w = glyph->w;\
i += clen - 1; /* i = last byte of the current character */\
for(; str[i+1]; i++)\
{\
int c = str[i+1];\
bool isig = c == '\1';\
if(c=='\f') { if(str[i+2]) i++; continue; }\
TEXTDECODE(str, i+1, c2, clen2)\
bool isig = c2 == '\1';\
if(c2=='\f') { if(str[i+2]) i++; continue; }\
if(i-j > 16) break;\
if(!isig && !curfont->chars.inrange(c-curfont->skip)) break;\
int cw = isig ? FONTH + 1 : curfont->chars[c-curfont->skip].w + 1;\
font::charinfo *g2 = isig ? NULL : curfont->getglyph(c2);\
if(!isig && !g2) break;\
int cw = isig ? FONTH + 1 : g2->w + 1;\
if(isig && str[i + 2]) i++;\
if(w + cw >= maxwidth) break;\
w += cw;\
i += clen2 - 1;\
}\
if(x + w >= maxwidth && j!=0) { TEXTLINE(j-1) x = 0; y += FONTH; }\
TEXTWORD\
}\
else\
{ TEXTCHAR(i) }\
{ TEXTCHAR(ci) i += clen - 1; }\
}\
}
@@ -305,10 +332,11 @@ void text_endcolumns()
for(; j <= i; j++)\
{\
TEXTINDEX(j)\
int c = str[j];\
int js = j; /* first byte of this character */\
TEXTDECODE(str, j, c, clen)\
if(c=='\f') { if(str[j+1]) { j++; TEXTCOLOR(j) }}\
else if(c=='\1') { if(str[j+1]) { j++; TEXTIGRAPH(j) }}\
else { TEXTCHAR(j) }\
else if((glyph = curfont->getglyph(c)) != NULL) { j += clen - 1; TEXTCHAR(js) }\
}
int text_visible(const char *str, int hitx, int hity, int maxwidth)
@@ -319,7 +347,7 @@ int text_visible(const char *str, int hitx, int hity, int maxwidth)
#define TEXTLINE(idx) if(y+FONTH > hity) return idx;
#define TEXTCOLOR(idx)
#define TEXTIGRAPH(idx) x += FONTH; TEXTWHITE(idx)
#define TEXTCHAR(idx) x += curfont->chars[c-curfont->skip].w+1; TEXTWHITE(idx)
#define TEXTCHAR(idx) x += glyph->w+1; TEXTWHITE(idx)
#define TEXTWORD TEXTWORDSKELETON
TEXTSKELETON
#undef TEXTINDEX
@@ -342,7 +370,7 @@ void text_pos(const char *str, int cursor, int &cx, int &cy, int maxwidth)
#define TEXTLINE(idx)
#define TEXTCOLOR(idx)
#define TEXTIGRAPH(idx) x += FONTH;
#define TEXTCHAR(idx) x += curfont->chars[c-curfont->skip].w + 1;
#define TEXTCHAR(idx) x += glyph->w + 1;
#define TEXTWORD TEXTWORDSKELETON if(i >= cursor) break;
cx = INT_MIN;
cy = 0;
@@ -366,7 +394,7 @@ void text_bounds(const char *str, int &width, int &height, int maxwidth)
#define TEXTLINE(idx) if(x > width) width = x;
#define TEXTCOLOR(idx)
#define TEXTIGRAPH(idx) x += FONTH + 1;
#define TEXTCHAR(idx) x += curfont->chars[c-curfont->skip].w + 1;
#define TEXTCHAR(idx) x += glyph->w + 1;
#define TEXTWORD x += w + 1;
width = 0;
TEXTSKELETON
@@ -384,13 +412,13 @@ void text_bounds(const char *str, int &width, int &height, int maxwidth)
void draw_text(const char *str, int left, int top, int r, int g, int b, int a, int cursor, int maxwidth)
{
#define TEXTINDEX(idx) if(idx == cursor) { cx = x; cy = y; cc = str[idx]; }
#define TEXTINDEX(idx) if(idx == cursor) { int cl_; cc = utf8decode(str + idx, cl_); if(cc <= 0) cc = ' '; cx = x; cy = y; }
#define TEXTTAB(idx) TEXTGETCOLUMN
#define TEXTWHITE(idx)
#define TEXTLINE(idx)
#define TEXTCOLOR(idx) text_color(str[idx], colorstack, sizeof(colorstack), colorpos, color, a);
#define TEXTIGRAPH(idx) queue_igraph((uchar)str[idx], left+x, top+y), x += FONTH + 1;
#define TEXTCHAR(idx) x += draw_char(c, left+x, top+y) + 1;
#define TEXTCHAR(idx) x += draw_char(glyph, left+x, top+y) + 1;
#define TEXTWORD TEXTWORDSKELETON
char colorstack[10];
bvec color(r, g, b);
@@ -407,7 +435,8 @@ void draw_text(const char *str, int left, int top, int r, int g, int b, int a, i
{
if(cx == INT_MIN) { cx = x; cy = y; }
if(maxwidth != -1 && cx >= maxwidth) { cx = 0; cy += FONTH; }
int cw = curfont->chars.inrange(cc-33) ? curfont->chars[cc-33].w + 1 : curfont->defaultw;
font::charinfo *cg = curfont->getglyph(cc);
int cw = cg ? cg->w + 1 : curfont->defaultw;
rendercursor(left+cx, top+cy, cw);
}
render_igraphs();
@@ -435,12 +464,19 @@ void cutcolorstring(char *text, int maxlen)
maxlen *= curfont->defaultw;
while(*text)
{
int cl = 1; // bytes of the current character
if(*text == '\f' && text[1]) text++;
else if(*text == '\1' && text[1]) text++, len += FONTH + 1;
else if(*text == '\t') len = TABALIGN(len);
else len += curfont->chars.inrange(*text - curfont->skip) ? curfont->chars[*text - curfont->skip].w : curfont->defaultw;
else
{
TEXTDECODE(text, 0, c, clen)
font::charinfo *g = curfont->getglyph(c);
len += g ? g->w : curfont->defaultw;
cl = clen;
}
if(len > maxlen) { *text = '\0'; break; }
text++;
text += cl;
}
}
@@ -450,9 +486,10 @@ bool filterunrenderables(char *s)
char *d = s;
while(*s)
{
if(!curfont->chars.inrange(*s - curfont->skip) && *s != ' ') res = true;
else *d++ = *s;
s++;
TEXTDECODE(s, 0, c, clen)
if(c != ' ' && !curfont->getglyph(c)) res = true; // includes invalid UTF-8
else loopi(clen) *d++ = s[i];
s += clen;
}
*d = '\0';
return res;
+59 -6
View File
@@ -22,17 +22,27 @@ int matchassist = 1; // enable assist system (0=off, 1=on)
extern int matchfriendlyfire;
int matchfriendlyfire = 1; // friendly fire mode: 0=off (no damage), 1=on (damage), 2=off + no shoot teammates
// spawn weapons: -1 = default (the player's own choice of primary / no extra gun), otherwise a GUN_* value.
// primary replaces the weapon you spawn holding; secondary/tertiary/quaternary are extra guns you spawn with.
extern int matchspawnprimary;
int matchspawnprimary = GUN_ASSAULT; // default primary weapon
int matchspawnprimary = -1;
extern int matchspawnsecondary;
int matchspawnsecondary = GUN_PISTOL; // default secondary weapon
int matchspawnsecondary = -1;
extern int matchspawntertiary;
int matchspawntertiary = GUN_KNIFE; // default tertiary weapon
int matchspawntertiary = -1;
extern int matchspawnquaternary;
int matchspawnquaternary = -1; // default quaternary weapon (none)
int matchspawnquaternary = -1;
// "explosive deaths" sub-gamemode: works on top of any gamemode, every death causes an explosion where the player died
extern int matchdeathexplosion;
int matchdeathexplosion = 0;
extern int matchdeathradius;
int matchdeathradius = 100; // blast radius, % of a grenade's
extern int matchdeathpower;
int matchdeathpower = 100; // blast damage, % of the default (75% of a grenade's damage at the centre)
struct servergame
{
@@ -1901,6 +1911,13 @@ void checkitemspawns(int diff)
}
}
// explosive deaths (see matchdeathexplosion): blasts are queued while a death is processed and handled afterwards,
// so a chain reaction (a blast killing someone who then blows up too) never recurses inside serverdamage()
struct pendingblast { int cn; vec o; };
static vector<pendingblast> blastqueue;
static bool blastflushing = false;
void flushdeathblasts();
void serverdamage(client *target, client *actor, int damage, int gun, bool gib, const vec &hitpush = vec(0, 0, 0))
{
if ( m_arena && gun == GUN_GRENADE && sg->arenaroundstartmillis + 2000 > sg->gamemillis && target != actor ) return;
@@ -2016,7 +2033,7 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib,
actor->state.frags += gib && gun != GUN_GRENADE && gun != GUN_SHOTGUN ? 2 : 1;
actor->incrementvitacounter(VS_FRAGS, 1);
}
else
else if(!blastflushing) // a death blast hitting a teammate is not a real teamkill (no penalty, no autokick/autoban)
{
actor->state.frags--;
actor->state.teamkills++;
@@ -2056,6 +2073,13 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib,
// don't issue respawn yet until DEATHMILLIS has elapsed
// ts.respawn();
if(matchdeathexplosion && !sg->interm)
{
pendingblast &b = blastqueue.add();
b.cn = target->clientnum;
b.o = ts.o;
}
if(isdedicated && actor->type == ST_TCPIP && tk)
{
if( actor->state.frags < scl.banthreshold ||
@@ -2073,6 +2097,35 @@ void serverdamage(client *target, client *actor, int damage, int gun, bool gib,
actor->state.frags < 4 * actor->state.teamkills ) ) disconnect_client(actor->clientnum, DISC_AUTOKICK);
}
} else if ( target!=actor && isteam(target->team, actor->team) ) check_ffire (target, actor, damage); // friendly fire counter
if(blastqueue.length() && !blastflushing) flushdeathblasts();
}
void flushdeathblasts()
{
blastflushing = true;
for(int n = 0; n < blastqueue.length(); n++) // blasts queued by chain reactions are appended and handled by this same loop
{
pendingblast b = blastqueue[n]; // copy: the queue can grow (and move) while we apply damage
if(!clients.inrange(b.cn)) continue;
client *victim = clients[b.cn];
sendf(-1, 1, "ri2", SV_DEATHBLAST, b.cn); // clients draw the explosion
loopv(clients)
{
client *c = clients[i];
if(c->type==ST_EMPTY || c==victim || c->state.state!=CS_ALIVE) continue;
vec dir(c->state.o);
dir.sub(b.o);
float dist = dir.magnitude();
if(dist >= DEATHBLASTRAD) continue;
int damage = int(DEATHBLASTDMG*(1-dist/DEATHBLASTRAD));
if(damage <= 0) continue;
if(dist > 0) dir.div(dist);
serverdamage(c, victim, damage, GUN_GRENADE, true, dir); // the player who died is credited with the blast
}
}
blastqueue.setsize(0);
blastflushing = false;
}
#include "serverevents.h"
@@ -3111,7 +3164,7 @@ int checktype(int type, client *cl)
SV_CALLVOTESUC, SV_CALLVOTEERR, SV_VOTERESULT,
SV_SETTEAM, SV_TEAMDENY, SV_SERVERMODE, SV_IPLIST,
SV_SENDDEMOLIST, SV_SENDDEMO, SV_DEMOPLAYBACK,
SV_CLIENT, SV_DEMOCHECKSUM, SV_PAUSEMODE };
SV_CLIENT, SV_DEMOCHECKSUM, SV_PAUSEMODE, SV_DEATHBLAST };
// only allow edit messages in coop-edit mode
static int edittypes[] = { SV_EDITENT, SV_EDITXY, SV_EDITARCH, SV_EDITBLOCK, SV_EDITD, SV_EDITE, SV_NEWMAP };
if(cl)
+2 -1
View File
@@ -456,7 +456,8 @@ const char *messagenames[SV_NUM] =
"SV_CLIENT",
"SV_EXTENSION",
"SV_MAPIDENT", "SV_DEMOCHECKSUM", "SV_DEMOSIGNATURE",
"SV_PAUSEMODE"
"SV_PAUSEMODE",
"SV_GETVITA", "SV_VITADATA", "SV_DEATHBLAST"
};
const char *entnames[] =
+39
View File
@@ -542,6 +542,45 @@ static inline bool htcmp(uint x, uint y)
return x==y;
}
// UTF-8 helpers (strings in the whole engine are plain char[]; text that may contain non-ASCII is kept as UTF-8)
// decodes one UTF-8 sequence at s. Returns the codepoint, or -1 for an invalid/overlong/truncated sequence (len is 1 then).
// len receives the number of bytes consumed (0 at the terminating NUL).
static inline int utf8decode(const char *s, int &len)
{
const uchar *u = (const uchar *)s;
int c = u[0], n, cp, minv;
if(!c) { len = 0; return 0; }
len = 1;
if(c < 0x80) return c;
if((c & 0xE0) == 0xC0) { n = 1; cp = c & 0x1F; minv = 0x80; }
else if((c & 0xF0) == 0xE0) { n = 2; cp = c & 0x0F; minv = 0x800; }
else if((c & 0xF8) == 0xF0) { n = 3; cp = c & 0x07; minv = 0x10000; }
else return -1;
for(int i = 1; i <= n; i++)
{
if((u[i] & 0xC0) != 0x80) return -1;
cp = (cp << 6) | (u[i] & 0x3F);
}
if(cp < minv || cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF)) return -1;
len = n + 1;
return cp;
}
static inline int utf8charlen(const char *s) // bytes of the character starting at s (at least 1)
{
int l;
utf8decode(s, l);
return l > 0 ? l : 1;
}
static inline int utf8prev(const char *s, int i) // index of the start of the character before position i
{
if(i <= 0) return 0;
do { i--; } while(i > 0 && ((uchar)s[i] & 0xC0) == 0x80);
return i;
}
template <class K, class T> struct hashtable
{
typedef K key;
+53 -34
View File
@@ -647,15 +647,15 @@ float expdist(playerent *o, vec &dir, const vec &v)
return dist;
}
void radialeffect(playerent *o, vec &v, int qdam, playerent *at, int gun)
void radialeffect(playerent *o, vec &v, int qdam, playerent *at, int gun, float radius = EXPDAMRAD)
{
if(o->state!=CS_ALIVE) return;
vec dir;
float dist = expdist(o, dir, v);
if(dist<EXPDAMRAD)
if(dist<radius)
{
if(at == player1 && o != player1 && multiplayer(NULL)) accuracym[gun].hits += 1.0f-(float)dist/EXPDAMRAD;
int damage = (int)(qdam*(1-dist/EXPDAMRAD));
if(at == player1 && o != player1 && multiplayer(NULL)) accuracym[gun].hits += 1.0f-(float)dist/radius;
int damage = (int)(qdam*(1-dist/radius));
hit(damage, o, at, dir, gun, true, int(dist*DMF));
}
}
@@ -1046,12 +1046,52 @@ VAR(matchrecoilmul, 0, 100, 500); // 0-500% recoil multiplier
VAR(matchspreadmul, 0, 100, 500); // 0-500% spread multiplier
VAR(matchspeedmul, 0, 100, 500); // 0-500% speed multiplier
VAR(matchgravity, 0, 100, 500); // 0-500% gravity multiplier
extern int matchassist; // enable assist system (defined in server.cpp)
extern int matchfriendlyfire; // friendly fire mode: 0=off, 1=no damage, 2=no damage + no shoot teammates
extern int matchspawnprimary; // primary weapon spawn (GUN_*)
extern int matchspawnsecondary; // secondary weapon spawn (GUN_*)
extern int matchspawntertiary; // tertiary weapon spawn (GUN_*)
extern int matchspawnquaternary; // quaternary weapon spawn (GUN_*)
VAR(matchfriction, 10, 100, 500); // 10-500% friction (grip): low = slippery/ice, high = snappy, no sliding
// These settings are used by the server code (server.cpp), which the client build contains for local/bot games.
// They have to be registered as console variables, otherwise the match settings menu can neither set nor read them
// (that is why the checkboxes were always shown unticked and setting them did nothing).
extern int matchassist; // enable assist system: 0=off, 1=on
extern int matchfriendlyfire; // friendly fire mode: 0=no damage, 1=normal, 2=no damage + can't shoot teammates
extern int matchspawnprimary; // primary weapon you spawn with (GUN_*), -1 = your own choice
extern int matchspawnsecondary; // extra guns you spawn with (GUN_*), -1 = none
extern int matchspawntertiary;
extern int matchspawnquaternary;
extern int matchdeathexplosion; // "explosive deaths": every death causes an explosion
extern int matchdeathradius; // ... blast radius, % of a grenade's
extern int matchdeathpower; // ... blast damage, %
static int __matchsettings = (
variable("matchassist", 0, 1, 1, &matchassist, NULL, false),
variable("matchfriendlyfire", 0, 1, 2, &matchfriendlyfire, NULL, false),
variable("matchspawnprimary", -1, -1, NUMGUNS-1, &matchspawnprimary, NULL, false),
variable("matchspawnsecondary", -1, -1, NUMGUNS-1, &matchspawnsecondary, NULL, false),
variable("matchspawntertiary", -1, -1, NUMGUNS-1, &matchspawntertiary, NULL, false),
variable("matchspawnquaternary", -1, -1, NUMGUNS-1, &matchspawnquaternary, NULL, false),
variable("matchdeathexplosion", 0, 0, 1, &matchdeathexplosion, NULL, false),
variable("matchdeathradius", 10, 100, 500, &matchdeathradius, NULL, false),
variable("matchdeathpower", 0, 100, 500, &matchdeathpower, NULL, false),
0);
// "explosive deaths": effect shown where somebody died (also used for the blast sent by the server)
void deathblasteffect(const vec &pos)
{
vec o(pos);
particle_splash(PART_SPARK, 50, 300, o);
particle_fireball(PART_FIREBALL, o);
addscorchmark(o);
adddynlight(NULL, o, 16, 200, 100, 255, 255, 224);
adddynlight(NULL, o, 16, 600, 600, 192, 160, 128);
audiomgr.playsound(S_FEXPLODE, &o);
}
// offline/bot games have no server to do it: blow up the victim and hurt whoever is close
void localdeathblast(playerent *victim)
{
deathblasteffect(victim->o);
vec o(victim->o);
radialeffect(player1, o, DEATHBLASTDMG, victim, GUN_GRENADE, DEATHBLASTRAD);
loopv(players) if(players[i] && players[i] != victim) radialeffect(players[i], o, DEATHBLASTDMG, victim, GUN_GRENADE, DEATHBLASTRAD);
}
void weapon::renderhudmodel(int lastaction, int index)
{
@@ -1102,30 +1142,9 @@ void weapon::equipplayer(playerent *pl)
pl->weapons[GUN_SNIPER] = new sniperrifle(pl);
pl->weapons[GUN_SUBGUN] = new subgun(pl);
pl->weapons[GUN_AKIMBO] = new akimbo(pl);
// Use match settings for spawn weapons
int primary = valid_weapon(matchspawnprimary) ? matchspawnprimary : GUN_ASSAULT;
int secondary = valid_weapon(matchspawnsecondary) ? matchspawnsecondary : GUN_PISTOL;
int tertiary = valid_weapon(matchspawntertiary) ? matchspawntertiary : GUN_KNIFE;
int quaternary = valid_weapon(matchspawnquaternary) ? matchspawnquaternary : -1;
pl->selectweapon(primary);
pl->setprimary(primary);
pl->setnextprimary(secondary);
// Ensure tertiary and quaternary weapons have ammo and are in the weapon list
if(tertiary >= 0)
{
pl->ammo[tertiary] = max(pl->ammo[tertiary], guns[tertiary].magsize);
if(pl->weapons[tertiary]) pl->weapons[tertiary]->reset();
}
if(quaternary >= 0)
{
pl->ammo[quaternary] = max(pl->ammo[quaternary], guns[quaternary].magsize);
if(pl->weapons[quaternary]) pl->weapons[quaternary]->reset();
}
// Note: tertiary/quaternary are available but not auto-selected; player can switch to them
// The match spawn weapons (matchspawn*) are applied in playerstate::spawnstate(), so the server and
// local games agree. (They used to be set here, which overwrote the player's nextprimary with the
// "secondary" weapon, and the server's ammo/weapon state then replaced it anyway.)
}
// grenadeent