wowie zowie wowie zowie
This commit is contained in:
@@ -42,3 +42,4 @@ profile/*
|
||||
logs/*
|
||||
private/*
|
||||
.DS_Store
|
||||
patch/*
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
cd ~/AC/source/src/
|
||||
make -j8 CXX=clang++ -j$(nproc) install
|
||||
cd ~/AC/
|
||||
@@ -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
@@ -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 |
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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[] =
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user