mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-18 20:00:26 +02:00
Add a streamer mode that hides slurs, swearing and insults in text from other players, maps and mods
This commit is contained in:
1 parent
efabb0d056
commit
90dd65cab4
29 files changed
+4901
-64
No files matched your search
@@ -16,7 +16,7 @@ end.
|
||||
| --- | --- |
|
||||
| `source/` | The game: 466 C files of the decompilation, compiled as they are with few changes (each marked, below) |
|
||||
| `port/linux/src/` | The platform layer the three ports share: Direct3D 8 on OpenGL (`d3d8_gl.c`), DirectSound and XInput on SDL3, files, threads, memory, sockets, settings (`port_config.c`), the mods folder (`port_mods.c`), internet play (`p2p*.c`) |
|
||||
| `port/linux/game/` | The port's game-side code, compiled as the game's sources are: the distributed netcode (`network_*.c`), co-op, map sharing (`map_share*.c`), the menus (`menu_tags.c`, `menu_functions.c`), Custom Edition maps (`custom_edition_*.c`, `cache_file_formats.c`), the tag validator (`tag_validate.c`, `tag_schema_*.c`), mods (`mods.c`, `mod_*.c`), touch, FOV and more |
|
||||
| `port/linux/game/` | The port's game-side code, compiled as the game's sources are: the distributed netcode (`network_*.c`), co-op, map sharing (`map_share*.c`), the menus (`menu_tags.c`, `menu_functions.c`), Custom Edition maps (`custom_edition_*.c`, `cache_file_formats.c`), the tag validator (`tag_validate.c`, `tag_schema_*.c`), mods (`mods.c`, `mod_*.c`), streamer mode's word filter (`text_filter*.c`), touch, FOV and more |
|
||||
| `port/linux/include/` | The port's headers: the limits (`halo_port_limits.h`, with `HALO_PORT_NETWORK_VERSION`), capacities (`halo_port_capacity.h`), the prefix header, `halo_linux_source_fixups.h` (port functions the game's sources call) |
|
||||
| `port/include/xdk/` | Stand-ins for the Xbox SDK's headers (declarations only) |
|
||||
| `port/windows/` | The Windows build's own files (`win32_*.c`, headers) |
|
||||
@@ -218,6 +218,7 @@ as documentation of file formats, never copied.
|
||||
| [port/include/xdk/README.md](port/include/xdk/README.md) | How the SDK declarations were written and are checked |
|
||||
| [docs/custom_edition_caches.md](docs/custom_edition_caches.md) | Loading and converting Custom Edition maps, and the evidence for each layout |
|
||||
| [docs/mods.md](docs/mods.md) | Mods: folders of Halo PC tag files whose sounds, bitmaps, text and models play over a map's on one machine |
|
||||
| [port/assets/text_filter/README.md](port/assets/text_filter/README.md) | Streamer mode's words: Surge AI's list, the review, the allowed words |
|
||||
| [port/assets/menus/README.md](port/assets/menus/README.md) | The menu files' format and how to write them again |
|
||||
| [port/assets/menus/NON_HANDDRAWN.md](port/assets/menus/NON_HANDDRAWN.md) | Menu pictures that are not redraws |
|
||||
| [port/assets/menus/UNWIRED.md](port/assets/menus/UNWIRED.md) | Menu functions that do nothing yet |
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- The port's settings screen, in the PC version's style (tools/port_settings.py) -->
|
||||
<menus>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/multiplayer_hud_settings_screen" width="640" height="480" flags="pass_unhandled_to_focused_child pause_game" bitmap="bitmaps/gradient">
|
||||
<on event="b" back="true"/>
|
||||
<on event="back" back="true"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/header_profile_network_settings"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/options_menu"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/header_profile_network_settings" controller="1" left="35" top="11" width="605" height="59" bitmap="main_menu/settings_select/player_setup/player_profile_edit/network_setup/header_profile_network_settings">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/help" type="text" controller="1" left="68" top="350" width="482" height="60" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/help_strings" font="ui\large_ui" color="#FFFFFFFF">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/options_menu" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/help">
|
||||
<data input="port settings help"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_player_names" x="54" y="73"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_player_name_scale" x="54" y="103"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_scoreboard_team_layout" x="54" y="133"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_scoreboard_background" x="54" y="163"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/button_bar" y="414"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/button_defaults" y="1"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/button_ok" x="380" y="1"/>
|
||||
<child widget="common_button_cancel" x="510" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_player_names" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_names_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_names_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_names_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/labels" string_index="0" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_names_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ALL|ALLIES|ENEMIES|NONE" setting="display.player_names" values="all|allies|enemies|none" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_player_name_scale" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_name_scale_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_name_scale_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_name_scale_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/labels" string_index="1" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/player_name_scale_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="0.5x|0.75x|1x|1.25x|1.5x|2x" setting="display.player_name_scale" values="0.5|0.75|1|1.25|1.5|2" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_scoreboard_team_layout" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_team_layout_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_team_layout_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_team_layout_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/labels" string_index="2" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_team_layout_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="TEAMS|BY SCORE" setting="display.scoreboard_team_layout" values="teams|score" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/op_scoreboard_background" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_background_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_background_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_background_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/labels" string_index="3" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/scoreboard_background_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.scoreboard_background" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/button_defaults" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="3" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
|
||||
<on event="a" run="port settings defaults"/>
|
||||
<on event="start" run="port settings defaults"/>
|
||||
<on event="left_mouse" run="mouse emit accept event"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/button_ok" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="1" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
|
||||
<on event="a" run="port settings save" back="true"/>
|
||||
<on event="start" run="port settings save" back="true"/>
|
||||
<on event="left_mouse" run="mouse emit accept event"/>
|
||||
</widget>
|
||||
<strings name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/labels">
|
||||
<string text="PLAYER NAMES:"/>
|
||||
<string text="PLAYER NAME SIZE:"/>
|
||||
<string text="SCOREBOARD LAYOUT:"/>
|
||||
<string text="SCOREBOARD PANEL:"/>
|
||||
</strings>
|
||||
<strings name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/help_strings">
|
||||
<string text=""/>
|
||||
<string text="In multiplayer, whose names are drawn above their\nheads."/>
|
||||
<string text="How large the players' names are drawn."/>
|
||||
<string text="A team game's scoreboard: a column for each team,\nor every player in order of score."/>
|
||||
<string text="Draw a panel behind the scoreboard, for clearer\ntext."/>
|
||||
</strings>
|
||||
</menus>
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- The port's settings screen, in the PC version's style (tools/port_settings.py) -->
|
||||
<menus>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/streamer_mode_settings_screen" width="640" height="480" flags="pass_unhandled_to_focused_child pause_game" bitmap="bitmaps/gradient">
|
||||
<on event="b" back="true"/>
|
||||
<on event="back" back="true"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/header_profile_network_settings"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/options_menu"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/header_profile_network_settings" controller="1" left="35" top="11" width="605" height="59" bitmap="main_menu/settings_select/player_setup/player_profile_edit/network_setup/header_profile_network_settings">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/help" type="text" controller="1" left="68" top="350" width="482" height="60" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/help_strings" font="ui\large_ui" color="#FFFFFFFF">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/options_menu" type="column_list" width="640" height="480" flags="pass_unhandled_to_focused_child up_down_tabs_children" description="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/help">
|
||||
<data input="port settings help"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_enabled" x="54" y="73"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_slurs" x="54" y="103"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_swearing" x="54" y="133"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_insults" x="54" y="163"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/button_bar" y="414"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/button_defaults" y="1"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/button_ok" x="380" y="1"/>
|
||||
<child widget="common_button_cancel" x="510" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_enabled" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/enabled_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/enabled_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/enabled_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/labels" string_index="0" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/enabled_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="streamer.enabled" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_slurs" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/slurs_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/slurs_spinner" x="286" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/slurs_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/labels" string_index="1" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/slurs_spinner" type="spinner" top="2" width="206" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="OFF|SEVERE|STRONG AND WORSE|MILD AND WORSE" setting="streamer.slurs" values="off|severe|strong|mild" font="ui\large_ui" color="#FF2896FF" align="center" text_y="4" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -13 19 -7" footer_bounds="7 208 19 214">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_swearing" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/swearing_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/swearing_spinner" x="286" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/swearing_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/labels" string_index="2" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/swearing_spinner" type="spinner" top="2" width="206" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="OFF|SEVERE|STRONG AND WORSE|MILD AND WORSE" setting="streamer.swearing" values="off|severe|strong|mild" font="ui\large_ui" color="#FF2896FF" align="center" text_y="4" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -13 19 -7" footer_bounds="7 208 19 214">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/op_insults" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/insults_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/insults_spinner" x="286" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/insults_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/labels" string_index="3" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/insults_spinner" type="spinner" top="2" width="206" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="OFF|SEVERE|STRONG AND WORSE|MILD AND WORSE" setting="streamer.insults" values="off|severe|strong|mild" font="ui\large_ui" color="#FF2896FF" align="center" text_y="4" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -13 19 -7" footer_bounds="7 208 19 214">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/button_defaults" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="3" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
|
||||
<on event="a" run="port settings defaults"/>
|
||||
<on event="start" run="port settings defaults"/>
|
||||
<on event="left_mouse" run="mouse emit accept event"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/button_ok" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="1" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
|
||||
<on event="a" run="port settings save" back="true"/>
|
||||
<on event="start" run="port settings save" back="true"/>
|
||||
<on event="left_mouse" run="mouse emit accept event"/>
|
||||
</widget>
|
||||
<strings name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/labels">
|
||||
<string text="STREAMER MODE:"/>
|
||||
<string text="CENSOR SLURS:"/>
|
||||
<string text="CENSOR SWEARING:"/>
|
||||
<string text="CENSOR INSULTS:"/>
|
||||
</strings>
|
||||
<strings name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/help_strings">
|
||||
<string text=""/>
|
||||
<string text="Hide offensive words in names and text from\nother players, maps and mods, as stars."/>
|
||||
<string text="Slurs about race, sexuality, gender and\ndisability."/>
|
||||
<string text="Swearing and sexual words (STRONG AND WORSE\nleaves the mildest showing)."/>
|
||||
<string text="Name-calling and insults that are not slurs."/>
|
||||
</strings>
|
||||
</menus>
|
||||
+26
-54
@@ -17,15 +17,11 @@
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_allow_upnp" x="54" y="103" platform="desktop android"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_join_from_clipboard" x="54" y="133" platform="desktop android"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_auto" x="54" y="163" platform="desktop android"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_player_names" x="54" y="193" platform="desktop android"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_player_names" x="54" y="103" platform="web"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_player_name_scale" x="54" y="223" platform="desktop android"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_player_name_scale" x="54" y="133" platform="web"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_scoreboard_team_layout" x="54" y="253" platform="desktop android"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_scoreboard_team_layout" x="54" y="163" platform="web"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_scoreboard_background" x="54" y="283" platform="desktop android"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_scoreboard_background" x="54" y="193" platform="web"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_map_sharing" x="54" y="313" platform="desktop android"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_map_sharing" x="54" y="193" platform="desktop android"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_multiplayer_hud" x="54" y="223" platform="desktop android"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_multiplayer_hud" x="54" y="103" platform="web"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_streamer_mode" x="54" y="253" platform="desktop android"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_streamer_mode" x="54" y="133" platform="web"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/button_bar" y="414"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/button_bar" type="column_list" width="640" height="28" flags="pass_unhandled_to_focused_child left_right_tabs_items">
|
||||
@@ -69,49 +65,29 @@
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/auto_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="update.auto" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_player_names" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_names_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_names_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_names_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="4" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_names_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ALL|ALLIES|ENEMIES|NONE" setting="display.player_names" values="all|allies|enemies|none" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_player_name_scale" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_name_scale_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_name_scale_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_name_scale_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="5" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/player_name_scale_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="0.5x|0.75x|1x|1.25x|1.5x|2x" setting="display.player_name_scale" values="0.5|0.75|1|1.25|1.5|2" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_scoreboard_team_layout" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_team_layout_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_team_layout_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_team_layout_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="6" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_team_layout_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="TEAMS|BY SCORE" setting="display.scoreboard_team_layout" values="teams|score" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_scoreboard_background" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_background_label"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_background_spinner" x="320" y="1"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_background_label" type="text" controller="1" width="300" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="7" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/scoreboard_background_spinner" type="spinner" left="3" top="2" width="147" height="20" flags="pass_unhandled_to_focused_child left_right_tabs_items" strings="ON|OFF" setting="display.scoreboard_background" values="true|false" font="ui\large_ui" color="#FF2896FF" align="center" text_y="1" header_bitmap="bitmaps/arrow_sm_left" footer_bitmap="bitmaps/arrow_sm_right" header_bounds="7 -6 19 0" footer_bounds="7 150 19 156">
|
||||
<on event="created" run="port setting load"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_map_sharing" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF" platform="desktop android">
|
||||
<on event="a" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing/map_sharing_settings_screen"/>
|
||||
<on event="start" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing/map_sharing_settings_screen"/>
|
||||
<on event="left_mouse" run="mouse emit accept event"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing_label"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing_label" type="text" controller="1" width="512" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="8" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/map_sharing_label" type="text" controller="1" width="512" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="4" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_multiplayer_hud" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<on event="a" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/multiplayer_hud_settings_screen"/>
|
||||
<on event="start" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud/multiplayer_hud_settings_screen"/>
|
||||
<on event="left_mouse" run="mouse emit accept event"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud_label"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/multiplayer_hud_label" type="text" controller="1" width="512" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="5" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/op_streamer_mode" width="512" height="28" flags="pass_unhandled_to_focused_child" bitmap="bitmaps/option_bkds" color="#FF2896FF">
|
||||
<on event="a" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/streamer_mode_settings_screen"/>
|
||||
<on event="start" open="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode/streamer_mode_settings_screen"/>
|
||||
<on event="left_mouse" run="mouse emit accept event"/>
|
||||
<child widget="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode_label"/>
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/streamer_mode_label" type="text" controller="1" width="512" height="22" string_list="main_menu/settings_select/player_setup/player_profile_edit/network_setup/labels" string_index="6" font="ui\large_ui" color="#FF2896FF" text_x="13" text_y="4">
|
||||
</widget>
|
||||
<widget name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/button_defaults" type="text" width="128" height="24" bitmap="bitmaps/text_button_background" string_list="strings/common_button_captions" string_index="3" font="ui\small_ui" color="#FFFFFFFF" align="center" text_y="2">
|
||||
<on event="a" run="port settings defaults"/>
|
||||
@@ -128,11 +104,9 @@
|
||||
<string text="UPNP PORT FORWARDING:"/>
|
||||
<string text="JOIN FROM CLIPBOARD:"/>
|
||||
<string text="CHECK FOR UPDATES:"/>
|
||||
<string text="PLAYER NAMES:"/>
|
||||
<string text="PLAYER NAME SIZE:"/>
|
||||
<string text="SCOREBOARD LAYOUT:"/>
|
||||
<string text="SCOREBOARD PANEL:"/>
|
||||
<string text="MAP SHARING:"/>
|
||||
<string text="MULTIPLAYER HUD:"/>
|
||||
<string text="STREAMER MODE:"/>
|
||||
</strings>
|
||||
<strings name="main_menu/settings_select/player_setup/player_profile_edit/network_setup/help_strings">
|
||||
<string text=""/>
|
||||
@@ -140,10 +114,8 @@
|
||||
<string text="Let internet play ask the router to forward its\nport, for networks that stop connections."/>
|
||||
<string text="Join the game of an invite link copied before\nswitching to the game."/>
|
||||
<string text="Look for a new version when the game starts."/>
|
||||
<string text="In multiplayer, whose names are drawn above their\nheads."/>
|
||||
<string text="How large the players' names are drawn."/>
|
||||
<string text="A team game's scoreboard: a column for each team,\nor every player in order of score."/>
|
||||
<string text="Draw a panel behind the scoreboard, for clearer\ntext."/>
|
||||
<string text="Downloading Custom Edition maps you do not have,\nand helping send them to other players."/>
|
||||
<string text="Players' names above their heads, and the\nscoreboard."/>
|
||||
<string text="Hide offensive words in names and text from\nother players, maps and mods, as stars."/>
|
||||
</strings>
|
||||
</menus>
|
||||
@@ -48,6 +48,8 @@
|
||||
"ce/main_menu.settings_select.player_setup.player_profile_edit.mouse_settings.xml",
|
||||
"ce/main_menu.settings_select.player_setup.player_profile_edit.name_edit.xml",
|
||||
"ce/main_menu.settings_select.player_setup.player_profile_edit.network_setup.map_sharing.xml",
|
||||
"ce/main_menu.settings_select.player_setup.player_profile_edit.network_setup.multiplayer_hud.xml",
|
||||
"ce/main_menu.settings_select.player_setup.player_profile_edit.network_setup.streamer_mode.xml",
|
||||
"ce/main_menu.settings_select.player_setup.player_profile_edit.network_setup.xml",
|
||||
"ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.fov_viewmodels.xml",
|
||||
"ce/main_menu.settings_select.player_setup.player_profile_edit.video_settings.graphics.xml",
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# Streamer mode's words
|
||||
|
||||
The words streamer mode hides (Settings > Network Setup > Streamer Mode; the
|
||||
`streamer.*` settings in `port/linux/README.md`). The game builds them in
|
||||
from `port/linux/game/text_filter_terms.inc`, which
|
||||
`tools/text_filter_terms.py` writes from Surge AI's list and these files:
|
||||
|
||||
| File | Contents |
|
||||
| --- | --- |
|
||||
| `review.txt` | The review of every base word of Surge AI's list: kept (its group, severity and where it may be found) or dropped, and the spellings dropped. The words are ROT13'd. |
|
||||
| `allow.txt` | Words never hidden, however a hidden word sits inside them (Scunthorpe), in plain text |
|
||||
| `english.txt` | The words of the English word list (`port/third_party/english-words`) that the list hides, ROT13'd, which `tools/test_text_filter_terms.py` checks |
|
||||
|
||||
`python3 tools/text_filter_terms.py --list` shows the table decoded, and
|
||||
`--check "some text"` shows what the game would hide in a text.
|
||||
|
||||
## Surge AI's list
|
||||
|
||||
The words, their categories and their severities come from Surge AI's
|
||||
profanity list, "The Obscenity List": 1,598 English profanities and their
|
||||
spellings, each with up to three base words, up to three categories and a
|
||||
severity that five raters gave it (Mild, Strong or Severe).
|
||||
|
||||
Upstream: https://github.com/surge-ai/profanity (gone since; the Internet
|
||||
Archive's copy of its page of 1 March 2025 shows an MIT `LICENSE`, whose text
|
||||
it did not keep), copied at https://huggingface.co/datasets/mmathys/profanity
|
||||
(MIT), revision `c2a17bf3ec64d9b6418ead1cb5f7ef26dbdffccb`: `profanity_en.csv`,
|
||||
SHA-256 ca4ff7d713a8e6f6ec2616f489cd09f6fb96b8b3af404a207783be58a91f3ede.
|
||||
|
||||
The CSV is not in the repository: give `tools/text_filter_terms.py --surge`
|
||||
its path to write the table again (and `HALO_SURGE_CSV` its path for the
|
||||
test that checks it).
|
||||
|
||||
The groups are made from its categories (`tools/text_filter_terms.py`'s
|
||||
`GROUPS`), each base word in one, then reviewed (`review.txt`):
|
||||
|
||||
| Group | Surge AI's categories |
|
||||
| --- | --- |
|
||||
| SLURS | racial / ethnic slurs, sexual orientation / gender, mental disability, physical disability, political |
|
||||
| SWEARING | sexual anatomy / sexual acts, bodily fluids / excrement, religious offense |
|
||||
| INSULTS | other / general insult, animal references, physical attributes |
|
||||
|
||||
The table is made from their list, so it carries their notice:
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) Surge AI
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
# Streamer mode's allowed words (tools/text_filter_terms.py): words never
|
||||
# hidden, nor any part of them, however a hidden word sits inside them
|
||||
# (Scunthorpe). One a line, lowercase.
|
||||
assess
|
||||
assesses
|
||||
bereshith
|
||||
brushite
|
||||
chashitsu
|
||||
cushite
|
||||
cushitic
|
||||
elkoshite
|
||||
girgashite
|
||||
kaneshite
|
||||
koreishite
|
||||
mackintoshite
|
||||
marshite
|
||||
matsushita
|
||||
metabrushite
|
||||
mishit
|
||||
mishits
|
||||
mishitting
|
||||
niggard
|
||||
peshito
|
||||
phage
|
||||
phages
|
||||
placuntitis
|
||||
placuntoma
|
||||
scunthorpe
|
||||
shiitake
|
||||
shiite
|
||||
shiites
|
||||
shiitic
|
||||
shitake
|
||||
shittah
|
||||
shittahs
|
||||
shitterton
|
||||
shittim
|
||||
shittims
|
||||
shittimwood
|
||||
snigger
|
||||
unniggard
|
||||
washita
|
||||
wiggeries
|
||||
wiggery
|
||||
@@ -0,0 +1,252 @@
|
||||
abapr
|
||||
abaprf
|
||||
arteb
|
||||
artebf
|
||||
artebrf
|
||||
artebvq
|
||||
artebvqf
|
||||
avt
|
||||
avtten
|
||||
avttre
|
||||
avttref
|
||||
avttregbr
|
||||
avttrejrrq
|
||||
avttrel
|
||||
avttreqbz
|
||||
avttrerq
|
||||
avttresvfu
|
||||
avttresvfurf
|
||||
avttretbbfr
|
||||
avttreurnq
|
||||
avttrevfu
|
||||
avttrevfz
|
||||
avttreyvat
|
||||
betnfz
|
||||
betnfzf
|
||||
cevpx
|
||||
cevpxf
|
||||
chffl
|
||||
chffvrf
|
||||
crpxre
|
||||
crpxref
|
||||
crqbcuvyr
|
||||
cvff
|
||||
cvffrf
|
||||
ergneq
|
||||
ergneqf
|
||||
ergneqrq
|
||||
erqfxva
|
||||
erqfxvaf
|
||||
fcvp
|
||||
fcvpf
|
||||
fcvt
|
||||
fcvx
|
||||
fcvxf
|
||||
fnzob
|
||||
fnzobf
|
||||
fphz
|
||||
fphzf
|
||||
fphzzl
|
||||
fulybpx
|
||||
fulybpxf
|
||||
fung
|
||||
fuvg
|
||||
fuvgf
|
||||
fuvggl
|
||||
fuvggra
|
||||
fuvggrq
|
||||
fuvggvarff
|
||||
fuvggvat
|
||||
fuvggvre
|
||||
fuvggvrfg
|
||||
fuvggyr
|
||||
fuvgn
|
||||
fuvgrcbxr
|
||||
fuvgure
|
||||
fuvgurnq
|
||||
fuvgurry
|
||||
fvffl
|
||||
fyhg
|
||||
fyhgf
|
||||
fyhggl
|
||||
fynt
|
||||
fyntf
|
||||
gbffre
|
||||
gbffref
|
||||
gheq
|
||||
gheqf
|
||||
gjng
|
||||
gjngf
|
||||
gvg
|
||||
gvgf
|
||||
jbc
|
||||
jbcf
|
||||
jnax
|
||||
jnaxre
|
||||
jrgonpx
|
||||
jrgonpxf
|
||||
juber
|
||||
juberf
|
||||
jvttre
|
||||
nany
|
||||
navyvathf
|
||||
nefr
|
||||
nefrf
|
||||
nefrubyr
|
||||
nff
|
||||
nffrf
|
||||
nffubyr
|
||||
nffubyrf
|
||||
nob
|
||||
nobf
|
||||
obare
|
||||
obaref
|
||||
obbof
|
||||
obyybk
|
||||
obyybkrf
|
||||
obyybkrq
|
||||
obyybkvat
|
||||
obyybpx
|
||||
obyybpxf
|
||||
ohgg
|
||||
ohggf
|
||||
ohttre
|
||||
ohttref
|
||||
ohyyfuvg
|
||||
ohyyfuvgf
|
||||
ohyyfuvggrq
|
||||
ohyyfuvggvat
|
||||
onfgneq
|
||||
onfgneqf
|
||||
onfgnneq
|
||||
ornare
|
||||
ornaref
|
||||
ovgpu
|
||||
ovgpul
|
||||
ovgpurel
|
||||
ovgpurevrf
|
||||
ovgpurf
|
||||
ovgpurq
|
||||
ovgpuvat
|
||||
oybjwbo
|
||||
oybjwbof
|
||||
oynpxl
|
||||
pbba
|
||||
pbbaf
|
||||
pbbyvr
|
||||
pbbyvrf
|
||||
pbpx
|
||||
pbpxf
|
||||
pbpxurnq
|
||||
phaavyvathf
|
||||
phag
|
||||
phagf
|
||||
phz
|
||||
pnjx
|
||||
pnjxl
|
||||
pnjxre
|
||||
pnpn
|
||||
pnpnf
|
||||
pnpx
|
||||
pnpxf
|
||||
puvax
|
||||
puvaxf
|
||||
puvaxl
|
||||
puvpxrafuvg
|
||||
pyvg
|
||||
qbhpur
|
||||
qbhpurf
|
||||
qbyg
|
||||
qbygf
|
||||
qlxr
|
||||
qlxrf
|
||||
qnexl
|
||||
qnexvr
|
||||
qnexvrf
|
||||
qntb
|
||||
qntbf
|
||||
qnza
|
||||
qnzaf
|
||||
qvpx
|
||||
qvpxf
|
||||
qvqqyr
|
||||
qvqqyrf
|
||||
qvyqb
|
||||
qvyqbf
|
||||
shpx
|
||||
shpxf
|
||||
shpxjvg
|
||||
shpxre
|
||||
shpxrq
|
||||
shpxvat
|
||||
shttrq
|
||||
sneg
|
||||
snegf
|
||||
snpx
|
||||
snpxf
|
||||
snt
|
||||
sntbg
|
||||
sntbgf
|
||||
sntbgg
|
||||
sntf
|
||||
snttbg
|
||||
snttbgel
|
||||
snttbgf
|
||||
snttbgl
|
||||
snttbgrq
|
||||
snttbgvat
|
||||
snttl
|
||||
snttvat
|
||||
srpx
|
||||
srpxf
|
||||
sryyngbe
|
||||
sryyngbel
|
||||
sryyngvb
|
||||
sryyngvbf
|
||||
synzre
|
||||
synzref
|
||||
tbbx
|
||||
tbbxf
|
||||
tbjy
|
||||
tbqqnz
|
||||
tbqqnza
|
||||
tbqqnzaf
|
||||
tbqqnzarq
|
||||
tbqqnzavg
|
||||
tbqqnzf
|
||||
tbqqnzzvg
|
||||
tlc
|
||||
tlcf
|
||||
tlcfl
|
||||
ubefrfuvg
|
||||
ubr
|
||||
ubrf
|
||||
uror
|
||||
uryy
|
||||
uryyf
|
||||
vawha
|
||||
wnc
|
||||
wnpxnff
|
||||
wnpxnffrf
|
||||
wnvyonvg
|
||||
wrex
|
||||
wrexf
|
||||
wvtnobb
|
||||
wvtnobbf
|
||||
xabo
|
||||
xabof
|
||||
xaboore
|
||||
xaboorq
|
||||
xlxr
|
||||
xvxr
|
||||
xvxrf
|
||||
xyna
|
||||
xynaf
|
||||
zbat
|
||||
zbatbybvq
|
||||
zbatbybvqf
|
||||
zbgureshpxre
|
||||
zhss
|
||||
zhssf
|
||||
znfgheongr
|
||||
znfgheongrf
|
||||
@@ -0,0 +1,296 @@
|
||||
# Streamer mode's review of Surge AI's profanity list (tools/text_filter_terms.py):
|
||||
# every base word of its profanity_en.csv kept (its group, severity and rule;
|
||||
# - for Surge AI's severity and the rule worked out) or dropped. The words are
|
||||
# ROT13'd; tools/text_filter_terms.py --list shows them.
|
||||
#
|
||||
# Dropped: everyday words the list has because they can be offensive in some
|
||||
# uses (the filter has no context), and words for identities or anatomy that
|
||||
# are neither slurs nor swearing. Moved: gendered insults filed under
|
||||
# "sexual orientation / gender" go to SWEARING, homophobic phrases filed under
|
||||
# the sexual categories go to SLURS, and the mild insults filed under "mental
|
||||
# disability" or "sexual orientation / gender" go to INSULTS. Severities
|
||||
# raised or lowered where one spelling's rating set the word's (damn and butt
|
||||
# are mild, as Ofcom rates them; the Australian slur rated mild is strong).
|
||||
|
||||
drop 69
|
||||
keep - - - norrq
|
||||
keep - strong whole nob
|
||||
drop noenunz
|
||||
keep - - whole nany
|
||||
drop nahf
|
||||
drop ncr
|
||||
keep - - - nefr
|
||||
keep - - whole nff
|
||||
keep - - - onol onggre
|
||||
keep - - - onyyfnpx
|
||||
drop onat
|
||||
keep - - - onfgneq
|
||||
keep - - - orna dhrra
|
||||
keep - - whole ornare
|
||||
keep - - - orng bss
|
||||
keep - - - oryyraq
|
||||
drop oraqre
|
||||
keep swearing - - ovgpu
|
||||
keep - - whole oynpxl
|
||||
keep - - - oybj n ybnq
|
||||
keep - - - oybjwbo
|
||||
keep - - - oyhzcxva
|
||||
keep - - - obvbynf
|
||||
keep - - - obyybpxf
|
||||
drop obaqntr
|
||||
keep - - whole obare
|
||||
drop obax
|
||||
keep - - whole obbof
|
||||
drop obbgl
|
||||
drop oernfg
|
||||
keep - - whole ohttre
|
||||
drop ohz
|
||||
drop ohat
|
||||
keep - mild whole ohgg
|
||||
drop ohggrezvyx
|
||||
keep - - - pnzry wbpxrl
|
||||
keep - - - pnzrygbr
|
||||
keep slurs - - pnecrg zhapure
|
||||
keep - - - puvan ivehf
|
||||
keep - - - puvatpubat
|
||||
keep - - whole puvax
|
||||
keep - - - pubnq
|
||||
keep - - - pubpun
|
||||
keep - - - pubxr gur puvpxra
|
||||
drop pubyb
|
||||
keep - - - pyvg
|
||||
keep - - whole pbpx
|
||||
drop pbwbarf
|
||||
keep - - whole pbbyvr
|
||||
keep - - whole pbba
|
||||
drop pbbg
|
||||
drop pbbgvrf
|
||||
keep - - - pbggba cvpxre
|
||||
keep - - - pernzcvr
|
||||
drop pebgpu
|
||||
drop pebj
|
||||
keep - - whole phz
|
||||
keep - - - phaavyvathf
|
||||
keep - - anywhere phag
|
||||
keep - - whole qntb
|
||||
keep - mild - qnza
|
||||
keep - - whole qnexl
|
||||
keep - - whole qvpx
|
||||
keep - - whole qvqqyr
|
||||
keep - - - qvyqb
|
||||
drop qvcfgvpx
|
||||
keep - - - qvegl fnapurm
|
||||
keep - - - qbttl fglyr
|
||||
keep insults - whole qbyg
|
||||
drop qbat
|
||||
keep - - - qbgurnq
|
||||
drop qbhoyr cra
|
||||
keep - - whole qbhpur
|
||||
keep - - - qhzc n ybnq
|
||||
keep - - whole qlxr
|
||||
drop rwnphyngvba
|
||||
keep - - - rerpgbcynfz
|
||||
keep - - whole snt
|
||||
keep - - anywhere snttbg
|
||||
keep - - whole sneg
|
||||
keep - - - sryyngvb
|
||||
drop svatre
|
||||
keep - - whole synzre
|
||||
drop sberfxva
|
||||
keep - - anywhere shpx
|
||||
keep slurs - - shqtr cnpxre
|
||||
keep - - - shaontf
|
||||
drop tneontr ont
|
||||
keep - - - tnfu-fgnoore
|
||||
drop tnl
|
||||
keep - - - tva wbpxrl
|
||||
keep - - - tveyl ovgf
|
||||
keep insults - - tveylobl
|
||||
keep - - whole tbbx
|
||||
keep - - whole tbjy
|
||||
drop ternfre
|
||||
keep - - start tebvq
|
||||
drop tebcr
|
||||
keep - - whole tlcfl
|
||||
drop unt
|
||||
keep - - - uneel cnyzf
|
||||
keep - - whole uror
|
||||
keep - - whole uryy
|
||||
keep swearing - whole ubr
|
||||
drop ubzbfrkhny
|
||||
drop ubbgref
|
||||
drop uhfgyre
|
||||
keep - - whole vawha
|
||||
keep - - - wnpx bss
|
||||
keep - - - wnpxbss
|
||||
keep - - - wnvyonvg
|
||||
keep - - whole wnc
|
||||
keep - - whole wrex
|
||||
keep - - - wrex bss
|
||||
drop wrj
|
||||
keep - - - wvttnobb
|
||||
drop wvz pebj
|
||||
keep - - - wvmm
|
||||
keep - - - xvqgbhpure
|
||||
keep - - whole xvxr
|
||||
keep - - - xvggl chapure
|
||||
keep slurs - whole xxx
|
||||
keep - - whole xyna
|
||||
keep - strong whole xabo
|
||||
keep - - - xh xyhkre
|
||||
keep - - - ynqlobl
|
||||
drop yrfovna
|
||||
drop ybbfr
|
||||
keep - - - zna pubjqre
|
||||
keep - - - zna frrq
|
||||
keep - - - znfgheongr
|
||||
drop zrng
|
||||
keep - - - zrng phegnvaf
|
||||
keep - - - zvys
|
||||
drop zbyrfg
|
||||
keep - - whole zbat
|
||||
keep - - whole zbatbybvq
|
||||
drop zbatery
|
||||
drop zbaxrl
|
||||
keep - - - zbgureshpxre
|
||||
keep - - whole zhss
|
||||
keep - - - zhfuebbz gvc
|
||||
keep - - whole arteb
|
||||
keep - - anywhere avttn
|
||||
keep - - anywhere avttre
|
||||
keep - - - avttref
|
||||
keep - - - avtttre
|
||||
drop avccyr
|
||||
keep - - whole abapr
|
||||
drop ahg ohggre
|
||||
keep - - - ahgfnpx
|
||||
drop alzcu
|
||||
keep - - - betnfz
|
||||
keep - - - bira qbqtre
|
||||
keep - - whole cnxv
|
||||
keep - - - cnyz wbpxrl
|
||||
keep - - - cnapnxr snpr
|
||||
keep - - whole crpxre
|
||||
keep insults - - crqbcuvyr
|
||||
drop cravf
|
||||
keep - - - crgre chssre
|
||||
drop cvt
|
||||
keep slurs - - cvyybj ovgre
|
||||
keep - - - cvff
|
||||
keep - - - cbyr yvpxre
|
||||
keep - - - cbyr fzbxre
|
||||
keep - - - cbyr fhpxre
|
||||
keep - - - cbyrfzbxre
|
||||
keep - - - cbepu zbaxrl
|
||||
keep - - whole cevpx
|
||||
keep - - - chyy gur chq
|
||||
keep - - - chanav
|
||||
drop chagn
|
||||
keep - - whole chffl
|
||||
drop dhrre
|
||||
keep - - - enturnq
|
||||
drop enzebq
|
||||
keep - - - ernpunebhaq
|
||||
drop erpghz
|
||||
keep - - - erqfxva
|
||||
keep - - whole ergneq
|
||||
keep - - whole ergneqrq
|
||||
keep - - - evz wbo
|
||||
drop fnqbznfbpuvfz
|
||||
keep - - - fnynq gbffre
|
||||
keep - - whole fnzob
|
||||
keep - - - fnhfntr wbpxrl
|
||||
keep - - - fpuybat
|
||||
keep insults - whole fphz
|
||||
drop fphg
|
||||
drop frk
|
||||
keep - - - furznyr
|
||||
drop fuvrfgre
|
||||
keep - - anywhere fuvg
|
||||
keep - - - fuybat
|
||||
keep - - whole fulybpx
|
||||
keep insults - whole fvffl
|
||||
keep swearing - - fxnax
|
||||
keep swearing - whole fynt
|
||||
keep - - - fynag rlr
|
||||
drop fyvg
|
||||
keep swearing - - fyhg
|
||||
drop fangpu
|
||||
drop fcnax
|
||||
drop fcrez
|
||||
drop fcuvapgre
|
||||
keep - - whole fcvp
|
||||
drop fcbbx
|
||||
drop fchax
|
||||
keep - - - fghzc purjre
|
||||
drop fhpx
|
||||
drop fjvar
|
||||
keep - - - gnpburnq
|
||||
keep - - - gnqtre
|
||||
keep - - - gnxvat gur cvff
|
||||
keep - - - gnyyljnpxre
|
||||
keep - - - gne-onol
|
||||
drop gneg
|
||||
drop grfgvpyrf
|
||||
keep - - - guebng lbtheg
|
||||
keep - - - guebngre
|
||||
keep - - - gvpxyr gur cvpxyr
|
||||
keep - - whole gvg
|
||||
keep - - whole gvgf
|
||||
keep - - whole gbffre
|
||||
keep - - - gbffvat fnynq
|
||||
keep - - - gbjryurnq
|
||||
drop genzc
|
||||
keep - - whole genaal
|
||||
drop genafirfgvgr
|
||||
drop genfu
|
||||
keep - - - gebhfre fanxr
|
||||
keep - - - gebhfrefanxr
|
||||
keep - - whole gheq
|
||||
keep - - - gjng
|
||||
drop gjvax
|
||||
keep - - - hcfxveg
|
||||
drop intvan
|
||||
drop ihyin
|
||||
drop jnat
|
||||
keep - - - jnax
|
||||
drop jrravr
|
||||
drop jrvare
|
||||
keep - - - jrgonpx
|
||||
keep - - - junpx bss
|
||||
keep swearing - - juber
|
||||
keep - - - jvyyl-junpxre
|
||||
keep - - - jvaqbj yvpxre
|
||||
keep - - whole jbc
|
||||
drop jbhaq
|
||||
keep - - - mvccreurnq
|
||||
|
||||
# spellings of kept words that are everyday words, or another's initials
|
||||
drop onfgvanqb
|
||||
drop crgre
|
||||
drop ahttrgf
|
||||
drop ahttrg
|
||||
drop xavpxref
|
||||
drop avpxre
|
||||
drop avpxref
|
||||
drop fnpx
|
||||
drop ongre
|
||||
drop ongrf
|
||||
drop abof
|
||||
drop abo
|
||||
drop fbof
|
||||
drop fbo
|
||||
drop ubne
|
||||
drop chfflpng
|
||||
drop chffrf
|
||||
drop chff
|
||||
drop qvk
|
||||
drop srq
|
||||
drop sre
|
||||
drop srt
|
||||
drop sbexrq
|
||||
drop furrg
|
||||
drop oop
|
||||
drop ojp
|
||||
drop ow
|
||||
drop fcvpx
|
||||
@@ -403,6 +403,10 @@ the setting for one start of the game. It has priority over the file.
|
||||
| `network.stun_servers` | Google and Cloudflare | `HALO_NET_STUN` | The public STUN servers (`host:port`, with commas between them) that give the internet address of a machine. |
|
||||
| `discord.application_id` | the application of the project | `HALO_DISCORD_APPLICATION` | The Discord application for invites. Empty: no Discord. |
|
||||
| `update.auto` | `true` | `HALO_UPDATE_AUTO` | `true`: at start-up, the game looks for a new version. Refer to "Updates". `false`: the game does not look. |
|
||||
| `streamer.enabled` | `false` | `HALO_STREAMER_MODE` | `true`: streamer mode hides offensive words, as stars, in the names and text of other players, maps and mods, at the levels below. Refer to "Streamer mode". STREAMER MODE in Settings > Network Setup > Streamer Mode. |
|
||||
| `streamer.slurs` | `"mild"` | `HALO_STREAMER_SLURS` | With `streamer.enabled`, the slurs (race, ethnicity, sexuality, gender, disability) hidden: `"off"`, `"severe"`, `"strong"` (strong and severe) or `"mild"` (all). CENSOR SLURS in Settings > Network Setup > Streamer Mode. |
|
||||
| `streamer.swearing` | `"strong"` | `HALO_STREAMER_SWEARING` | With `streamer.enabled`, the swearing and sexual words hidden: `"off"`, `"severe"`, `"strong"` or `"mild"`. CENSOR SWEARING in Settings > Network Setup > Streamer Mode. |
|
||||
| `streamer.insults` | `"off"` | `HALO_STREAMER_INSULTS` | With `streamer.enabled`, the insults that are not slurs hidden: `"off"`, `"severe"`, `"strong"` or `"mild"`. CENSOR INSULTS in Settings > Network Setup > Streamer Mode. |
|
||||
| `crash_reports.upload` | `"ask"` | `HALO_CRASH_REPORTS` | Windows only. `"yes"`: the game sends a report of each crash to the developers. `"no"`: the game sends no reports. `"ask"`: the game asks at the next crash and writes the answer here. Refer to "Crash reports" in [port/windows/README.md](../windows/README.md#crash-reports). |
|
||||
| `debug.update_answer` | `""` | `HALO_UPDATE_ANSWER` | The answer to the update question, for automatic tests: `yes`, `no` or `never`. Empty: the game asks. |
|
||||
| `debug.exit_after` | `0.0` | `HALO_EXIT_AFTER` | The game stops after this number of seconds. `0`: never. |
|
||||
@@ -991,6 +995,47 @@ checks every file before it plays it, and leaves out (with a line in
|
||||
replaces. A mod changes nothing that the machines of a
|
||||
network game agree on. Refer to [docs/mods.md](../../docs/mods.md).
|
||||
|
||||
## Streamer mode
|
||||
|
||||
Streamer mode (Settings > Network Setup > Streamer Mode, `streamer.enabled`)
|
||||
hides offensive words as stars wherever the game draws text that other
|
||||
people or files wrote: players' names over their heads, in the kill feed, on
|
||||
the scoreboards and in the lobbies, the server browser's games, the host's
|
||||
notices in the console, map and mod names, and the text of a map's or mod's
|
||||
own string lists, as well as the message boxes about mods and maps. It
|
||||
changes only what this machine shows: the names others see, and what the
|
||||
machines send each other, stay as they are.
|
||||
|
||||
The words come from Surge AI's rated list of English profanities, reviewed
|
||||
for the game (`port/assets/text_filter`), in three groups, each with a level
|
||||
that hides that severity and worse:
|
||||
|
||||
| Group | Setting | Default | What it has |
|
||||
| --- | --- | --- | --- |
|
||||
| SLURS | `streamer.slurs` | `"mild"` (all) | Slurs about race, ethnicity, nationality, religion, sexuality, gender and disability |
|
||||
| SWEARING | `streamer.swearing` | `"strong"` | Swearing and sexual words; the mildest (damn, hell) show at `"strong"` |
|
||||
| INSULTS | `streamer.insults` | `"off"` | Name-calling that is not a slur |
|
||||
|
||||
A word is found however it is dressed: in capitals or with accents, in
|
||||
look-alike letters (Cyrillic, Greek, full-width, small capitals), in leet
|
||||
(`0` for o, `@` for a, `ph` for f), with its letters stretched ("fuuuck"),
|
||||
with punctuation between them (`f.u.c.k`), spaced out (`f u c k`, `fu ck`),
|
||||
and with characters the game does not draw (zero-width spaces, the menus'
|
||||
`|` codes) between them. Each word may be found inside a name, at a word's
|
||||
start, or only as a whole word, as fits it, and some words are never
|
||||
hidden though a hidden one is inside them (Scunthorpe). `python3
|
||||
tools/text_filter_terms.py --check "some text"` shows what would be hidden.
|
||||
|
||||
What it cannot catch: letters added into a word or its letters scrambled, new
|
||||
spellings, a word split between two names or two lines, voice chat (mute a
|
||||
player from the scoreboard, or set `audio.voice_volume` to 0), and pictures.
|
||||
What you type is shown as you type it.
|
||||
|
||||
Your own words: `text_filter.txt` in the data root, with lines of `add
|
||||
<word>` (hidden whenever streamer mode is on) and `allow <word>` (never
|
||||
hidden), read when the game starts and when the settings change. The web
|
||||
build's page draws its own list of mods, which is not filtered.
|
||||
|
||||
## Map checks
|
||||
|
||||
The game reads a map's tags straight into memory and uses them as its own
|
||||
@@ -1168,6 +1213,7 @@ Other changes:
|
||||
| `rasterizer/rasterizer.c` | The 128 KB of vertices of the debug display of a model's vertices are static, not on the stack: the compiler inlines the function in `render_model`, and the Windows build probed the stack a page at a time for every model drawn. |
|
||||
| `render/render.c` | The 3D view of each window is antialiased before the HUD is drawn (`display.anti_aliasing`). |
|
||||
| `interface/hud.c` | In multiplayer, players' names are drawn above their heads (`display.player_names`, `display.player_name_scale`). |
|
||||
| `text/draw_string.c`, `interface/terminal.c`, `interface/virtual_keyboard.c` | Streamer mode's words are hidden in every string drawn or measured (`game/text_filter.c`), but in the console's line and the virtual keyboard's text being typed. |
|
||||
| `rasterizer/rasterizer_text.c`, `text/draw_string.c` | Text is drawn from an atlas of the fonts' glyphs, rasterized at the resolution the game draws at (`src/text_hires.c`), when the font has every character of the string. Text can be drawn scaled about a point (`rasterizer_text_set_scale`), as the players' names are. Each glyph's advance is centred on the font tag character's, so the layout is the same, and a glyph is cut at a text box only where the font tag's character visibly was. |
|
||||
| `interface/first_person_weapons.c` | The first-person hands are drawn with the unit's shield modifier shader, as its body is (`display.viewmodel_shield`). |
|
||||
| `render/render_objects.c`, `objects/objects.c` | In first person, your own body is drawn from the waist down with the pose `first_person_legs.c` makes (`display.first_person_legs`); the waist it found and the mesh `object_mesh.c` read are forgotten on a new map. |
|
||||
|
||||
@@ -85,6 +85,7 @@ their handlers open opens.
|
||||
#include "text/draw_string.h"
|
||||
/* (internet play's server browser: the platform layer's) */
|
||||
#include "../src/p2p.h"
|
||||
#include "text_filter.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -2924,6 +2925,12 @@ static void text_field_show(struct widget_instance *value, char const *text, boo
|
||||
shown[index++] = L'_';
|
||||
shown[index] = 0;
|
||||
text_set(value, shown);
|
||||
/* (what is being typed shown as it is: streamer mode hides nothing of it,
|
||||
text_filter.c) */
|
||||
if (editing && value)
|
||||
text_filter_exempt(value->parameters.text_box.text);
|
||||
else if (!text_field_editing(NULL))
|
||||
text_filter_exempt(NULL);
|
||||
}
|
||||
|
||||
/* ---- the menu */
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
TEXT_FILTER.C
|
||||
|
||||
Streamer mode (Settings > Network Setup > Streamer Mode): the words of
|
||||
text_filter_match.c's list hidden as stars wherever text is drawn, at the
|
||||
levels set (streamer.enabled, and streamer.slurs, streamer.swearing and
|
||||
streamer.insults: "off", "severe", "strong" or "mild", each that severity
|
||||
and worse).
|
||||
|
||||
draw_string.c's draw_string and draw_unicode_string take the string's
|
||||
filtered copy before they draw or measure it (draw_string_compute_bounds
|
||||
and the rest measure through them), so every string the game shows is
|
||||
filtered: names over heads, the kill feed, the scoreboards, the lobbies, the
|
||||
server browser, map and mod names, the console's lines, and a map's or mod's
|
||||
own text. A copy is the same length or shorter (a censored character is one
|
||||
'*'), so a fixed buffer is never overrun. Copies are kept, so that a string
|
||||
drawn each frame is looked at once.
|
||||
|
||||
What the player types is drawn as it is: the menus' text field being edited
|
||||
(text_filter_exempt), the console's input line and the virtual keyboard's
|
||||
(text_filter_suspend). The player's own words: d:\text_filter.txt, lines of
|
||||
"add <word>" (hidden whenever streamer mode is on) and "allow <word>" (never
|
||||
hidden), read as the settings change.
|
||||
|
||||
Only what this machine draws changes: nothing sent between machines.
|
||||
*/
|
||||
|
||||
#include "text_filter.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* port/linux/src/port_config.c */
|
||||
int config_boolean(const char *name);
|
||||
const char *config_string(const char *name);
|
||||
unsigned long config_changes(void);
|
||||
|
||||
#define USER_FILE "d:\\text_filter.txt"
|
||||
#define CACHE_ENTRIES 128
|
||||
#define CACHE_LENGTH 256
|
||||
#define LONG_BUFFERS 4
|
||||
|
||||
struct text_filter_cache_entry
|
||||
{
|
||||
unsigned long hash;
|
||||
short length;
|
||||
unsigned char narrow;
|
||||
unsigned char clean;
|
||||
unsigned short original[CACHE_LENGTH];
|
||||
union
|
||||
{
|
||||
unsigned short wide[CACHE_LENGTH];
|
||||
char narrow[CACHE_LENGTH];
|
||||
} filtered;
|
||||
};
|
||||
|
||||
static struct
|
||||
{
|
||||
unsigned long changes;
|
||||
int enabled;
|
||||
unsigned char levels[NUMBER_OF_TEXT_FILTER_GROUPS];
|
||||
long suspended;
|
||||
void const *exempt;
|
||||
struct text_filter_cache_entry cache[CACHE_ENTRIES];
|
||||
union
|
||||
{
|
||||
unsigned short wide[TEXT_FILTER_MAXIMUM_LENGTH + 1];
|
||||
char narrow[TEXT_FILTER_MAXIMUM_LENGTH + 1];
|
||||
} long_buffers[LONG_BUFFERS];
|
||||
long next_long_buffer;
|
||||
unsigned short units[TEXT_FILTER_MAXIMUM_LENGTH];
|
||||
unsigned char censored[TEXT_FILTER_MAXIMUM_LENGTH];
|
||||
} text_filter_globals = { (unsigned long)-1 };
|
||||
|
||||
static unsigned char text_filter_level(
|
||||
char const *name)
|
||||
{
|
||||
char const *value = config_string(name);
|
||||
|
||||
if (!value || !strcmp(value, "off"))
|
||||
return _text_filter_off;
|
||||
if (!strcmp(value, "severe"))
|
||||
return _text_filter_severe;
|
||||
if (!strcmp(value, "strong"))
|
||||
return _text_filter_strong;
|
||||
return _text_filter_mild;
|
||||
}
|
||||
|
||||
/* the list again with the player's own words (USER_FILE) */
|
||||
static void text_filter_user_words(
|
||||
void)
|
||||
{
|
||||
FILE *file = fopen(USER_FILE, "r");
|
||||
char line[128];
|
||||
|
||||
text_filter_words_reset();
|
||||
if (!file)
|
||||
return;
|
||||
while (fgets(line, sizeof(line), file))
|
||||
{
|
||||
char *word = line;
|
||||
|
||||
line[strcspn(line, "\r\n")] = 0;
|
||||
while (*word == ' ' || *word == '\t')
|
||||
word++;
|
||||
if (!strncmp(word, "add ", 4))
|
||||
text_filter_word_add(word + 4, _text_filter_own, _text_filter_severe, _text_filter_anywhere);
|
||||
else if (!strncmp(word, "allow ", 6))
|
||||
text_filter_allow_add(word + 6);
|
||||
}
|
||||
fclose(file);
|
||||
return;
|
||||
}
|
||||
|
||||
/* whether anything is hidden: the settings, read again when they change */
|
||||
static int text_filter_enabled(
|
||||
void)
|
||||
{
|
||||
if (text_filter_globals.changes != config_changes())
|
||||
{
|
||||
long index;
|
||||
|
||||
text_filter_globals.changes = config_changes();
|
||||
text_filter_globals.enabled = config_boolean("streamer.enabled") != 0;
|
||||
text_filter_globals.levels[_text_filter_slurs] = text_filter_level("streamer.slurs");
|
||||
text_filter_globals.levels[_text_filter_swearing] = text_filter_level("streamer.swearing");
|
||||
text_filter_globals.levels[_text_filter_insults] = text_filter_level("streamer.insults");
|
||||
text_filter_globals.levels[_text_filter_own] = _text_filter_mild;
|
||||
for (index = 0; index < CACHE_ENTRIES; index++)
|
||||
text_filter_globals.cache[index].length = -1;
|
||||
if (text_filter_globals.enabled)
|
||||
text_filter_user_words();
|
||||
}
|
||||
return text_filter_globals.enabled && !text_filter_globals.suspended;
|
||||
}
|
||||
|
||||
/* whether the string is a copy of ours (a string measured is drawn again
|
||||
through draw_string or draw_unicode_string) */
|
||||
static int text_filter_ours(
|
||||
void const *string)
|
||||
{
|
||||
char const *pointer = (char const *)string;
|
||||
char const *start = (char const *)&text_filter_globals;
|
||||
|
||||
return pointer >= start && pointer < start + sizeof(text_filter_globals);
|
||||
}
|
||||
|
||||
static unsigned long text_filter_hash(
|
||||
unsigned short const *units,
|
||||
long length)
|
||||
{
|
||||
unsigned long hash = 2166136261UL;
|
||||
long index;
|
||||
|
||||
for (index = 0; index < length; index++)
|
||||
hash = (hash ^ units[index]) * 16777619UL;
|
||||
return hash;
|
||||
}
|
||||
|
||||
/* the units with what is hidden as '*' (a wide string's "|n" and a narrow
|
||||
string's style codes kept, and a wide "|"'s hidden character dropped with
|
||||
it), into wide or narrow; whether anything was */
|
||||
static int text_filter_apply(
|
||||
unsigned short const *units,
|
||||
long length,
|
||||
int narrow,
|
||||
unsigned short *wide,
|
||||
char *narrow_text)
|
||||
{
|
||||
unsigned char *censored = text_filter_globals.censored;
|
||||
long index;
|
||||
long written = 0;
|
||||
|
||||
if (!text_filter_censor(units, length, narrow, text_filter_globals.levels, censored))
|
||||
return 0;
|
||||
for (index = 0; index < length; index++)
|
||||
{
|
||||
unsigned short unit = units[index];
|
||||
|
||||
if (censored[index])
|
||||
{
|
||||
if (unit == '|' && index + 1 < length)
|
||||
{
|
||||
unsigned short next = units[index + 1];
|
||||
|
||||
if (next == 'n' || (narrow && next && next < 0x80 && strchr("ibukprlctn", (char)next)))
|
||||
{
|
||||
/* a line break or a style code: kept */
|
||||
if (narrow)
|
||||
{
|
||||
narrow_text[written++] = '|';
|
||||
narrow_text[written++] = (char)next;
|
||||
}
|
||||
else
|
||||
{
|
||||
wide[written++] = '|';
|
||||
wide[written++] = next;
|
||||
}
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
if (!narrow)
|
||||
{
|
||||
/* a "|" that hides the character after it: both one '*' */
|
||||
index++;
|
||||
}
|
||||
unit = '*';
|
||||
}
|
||||
else if (unit != '\r' && unit != '\n' && unit != '\t')
|
||||
{
|
||||
unit = '*';
|
||||
}
|
||||
}
|
||||
if (narrow)
|
||||
narrow_text[written++] = (char)unit;
|
||||
else
|
||||
wide[written++] = unit;
|
||||
}
|
||||
if (narrow)
|
||||
narrow_text[written] = 0;
|
||||
else
|
||||
wide[written] = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* the string's filtered copy, or the string itself (nothing hidden, or a
|
||||
string longer than TEXT_FILTER_MAXIMUM_LENGTH, which is drawn as it is) */
|
||||
static void const *text_filter(
|
||||
void const *string,
|
||||
int narrow)
|
||||
{
|
||||
unsigned short *units = text_filter_globals.units;
|
||||
struct text_filter_cache_entry *entry;
|
||||
unsigned long hash;
|
||||
long length = 0;
|
||||
|
||||
if (!string || string == text_filter_globals.exempt || text_filter_ours(string) || !text_filter_enabled())
|
||||
return string;
|
||||
if (narrow)
|
||||
{
|
||||
unsigned char const *text = (unsigned char const *)string;
|
||||
|
||||
while (text[length] && length <= TEXT_FILTER_MAXIMUM_LENGTH)
|
||||
{
|
||||
if (length < TEXT_FILTER_MAXIMUM_LENGTH)
|
||||
units[length] = text[length];
|
||||
length++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned short const *text = (unsigned short const *)string;
|
||||
|
||||
while (text[length] && length <= TEXT_FILTER_MAXIMUM_LENGTH)
|
||||
{
|
||||
if (length < TEXT_FILTER_MAXIMUM_LENGTH)
|
||||
units[length] = text[length];
|
||||
length++;
|
||||
}
|
||||
}
|
||||
if (!length || length > TEXT_FILTER_MAXIMUM_LENGTH)
|
||||
return string;
|
||||
if (length >= CACHE_LENGTH)
|
||||
{
|
||||
/* a long string: filtered each time it is drawn, into one of a few
|
||||
buffers in turn */
|
||||
long buffer = text_filter_globals.next_long_buffer;
|
||||
|
||||
text_filter_globals.next_long_buffer = (buffer + 1) % LONG_BUFFERS;
|
||||
if (!text_filter_apply(units, length, narrow, text_filter_globals.long_buffers[buffer].wide,
|
||||
text_filter_globals.long_buffers[buffer].narrow))
|
||||
{
|
||||
return string;
|
||||
}
|
||||
return narrow ?
|
||||
(void const *)text_filter_globals.long_buffers[buffer].narrow :
|
||||
(void const *)text_filter_globals.long_buffers[buffer].wide;
|
||||
}
|
||||
hash = text_filter_hash(units, length) ^ (unsigned long)narrow;
|
||||
entry = &text_filter_globals.cache[hash % CACHE_ENTRIES];
|
||||
if (entry->length != length || entry->hash != hash || entry->narrow != narrow ||
|
||||
memcmp(entry->original, units, length * sizeof(*units)))
|
||||
{
|
||||
entry->hash = hash;
|
||||
entry->length = (short)length;
|
||||
entry->narrow = (unsigned char)narrow;
|
||||
memcpy(entry->original, units, length * sizeof(*units));
|
||||
entry->clean = !text_filter_apply(units, length, narrow, entry->filtered.wide, entry->filtered.narrow);
|
||||
}
|
||||
if (entry->clean)
|
||||
return string;
|
||||
return narrow ? (void const *)entry->filtered.narrow : (void const *)entry->filtered.wide;
|
||||
}
|
||||
|
||||
/* draw_unicode_string's string (draw_string.c) */
|
||||
wchar_t const *text_filter_unicode(
|
||||
wchar_t const *string)
|
||||
{
|
||||
return (wchar_t const *)text_filter(string, 0);
|
||||
}
|
||||
|
||||
/* draw_string's string (draw_string.c) */
|
||||
char const *text_filter_string(
|
||||
char const *string)
|
||||
{
|
||||
return (char const *)text_filter(string, 1);
|
||||
}
|
||||
|
||||
/* what is drawn until text_filter_resume drawn as it is (what the player
|
||||
types) */
|
||||
void text_filter_suspend(
|
||||
void)
|
||||
{
|
||||
text_filter_globals.suspended++;
|
||||
return;
|
||||
}
|
||||
|
||||
void text_filter_resume(
|
||||
void)
|
||||
{
|
||||
if (text_filter_globals.suspended > 0)
|
||||
text_filter_globals.suspended--;
|
||||
return;
|
||||
}
|
||||
|
||||
/* the text (a widget's buffer) drawn as it is while it is being edited;
|
||||
NULL: none */
|
||||
void text_filter_exempt(
|
||||
void const *text)
|
||||
{
|
||||
text_filter_globals.exempt = text;
|
||||
return;
|
||||
}
|
||||
|
||||
/* text shown outside the game's drawing (the platform's message boxes),
|
||||
UTF-8, filtered in place (a hidden character, of however many bytes, one
|
||||
'*') */
|
||||
void text_filter_utf8(
|
||||
char *text)
|
||||
{
|
||||
unsigned short *units = text_filter_globals.units;
|
||||
unsigned char *censored = text_filter_globals.censored;
|
||||
long starts[TEXT_FILTER_MAXIMUM_LENGTH];
|
||||
unsigned char const *byte = (unsigned char const *)text;
|
||||
long length = 0;
|
||||
long index;
|
||||
long written = 0;
|
||||
long read = 0;
|
||||
long total;
|
||||
|
||||
if (!text || !text_filter_enabled())
|
||||
return;
|
||||
while (*byte && length < TEXT_FILTER_MAXIMUM_LENGTH)
|
||||
{
|
||||
unsigned long code = *byte;
|
||||
long size = 1;
|
||||
|
||||
if (code >= 0xE0 && byte[1] && byte[2])
|
||||
{
|
||||
code = ((code & 0x0F) << 12) | ((byte[1] & 0x3F) << 6) | (byte[2] & 0x3F);
|
||||
size = 3;
|
||||
}
|
||||
else if (code >= 0xC0 && byte[1])
|
||||
{
|
||||
code = ((code & 0x1F) << 6) | (byte[1] & 0x3F);
|
||||
size = 2;
|
||||
}
|
||||
starts[length] = (long)(byte - (unsigned char const *)text);
|
||||
units[length++] = (unsigned short)code;
|
||||
byte += size;
|
||||
}
|
||||
if (*byte || !text_filter_censor(units, length, 0, text_filter_globals.levels, censored))
|
||||
return;
|
||||
total = (long)(byte - (unsigned char const *)text);
|
||||
for (index = 0; index < length; index++)
|
||||
{
|
||||
long next = index + 1 < length ? starts[index + 1] : total;
|
||||
|
||||
read = starts[index];
|
||||
if (censored[index])
|
||||
{
|
||||
text[written++] = '*';
|
||||
}
|
||||
else
|
||||
{
|
||||
while (read < next)
|
||||
text[written++] = text[read++];
|
||||
}
|
||||
}
|
||||
text[written] = 0;
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
TEXT_FILTER.H
|
||||
|
||||
Streamer mode (Settings > Network Setup > Streamer Mode, streamer.*): the
|
||||
words hidden as stars in what is drawn. text_filter_match.c finds them, with
|
||||
no game types, so that the harness builds it alone
|
||||
(tools/harness/tests/test_text_filter.py); text_filter.c applies them to
|
||||
what the game draws.
|
||||
*/
|
||||
|
||||
#ifndef __TEXT_FILTER_H__
|
||||
#define __TEXT_FILTER_H__
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/* the groups a player sets a level for, and the player's own words
|
||||
(text_filter.txt), hidden whenever streamer mode is on */
|
||||
enum text_filter_group
|
||||
{
|
||||
_text_filter_slurs,
|
||||
_text_filter_swearing,
|
||||
_text_filter_insults,
|
||||
_text_filter_own,
|
||||
NUMBER_OF_TEXT_FILTER_GROUPS
|
||||
};
|
||||
|
||||
/* a term's severity (Surge AI's: mild, strong, severe), and a group's
|
||||
level: the least severity hidden, or none */
|
||||
enum text_filter_severity
|
||||
{
|
||||
_text_filter_off,
|
||||
_text_filter_mild,
|
||||
_text_filter_strong,
|
||||
_text_filter_severe
|
||||
};
|
||||
|
||||
/* where a term may be found: inside any word, at a word's start, or as the
|
||||
whole word (or it and "s" or "es") */
|
||||
enum text_filter_rule
|
||||
{
|
||||
_text_filter_anywhere,
|
||||
_text_filter_start,
|
||||
_text_filter_whole,
|
||||
NUMBER_OF_TEXT_FILTER_RULES
|
||||
};
|
||||
|
||||
/* the most code units of a string looked at (a longer one is drawn as it is) */
|
||||
#define TEXT_FILTER_MAXIMUM_LENGTH 1024
|
||||
|
||||
/* text_filter_match.c */
|
||||
void text_filter_words_reset(void);
|
||||
int text_filter_word_add(char const *term, int group, int severity, int rule);
|
||||
int text_filter_allow_add(char const *word);
|
||||
long text_filter_censor(unsigned short const *text, long length, int narrow, unsigned char const *levels,
|
||||
unsigned char *censored);
|
||||
|
||||
/* text_filter.c */
|
||||
wchar_t const *text_filter_unicode(wchar_t const *string);
|
||||
char const *text_filter_string(char const *string);
|
||||
void text_filter_suspend(void);
|
||||
void text_filter_resume(void);
|
||||
void text_filter_exempt(void const *text);
|
||||
void text_filter_utf8(char *text);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,758 @@
|
||||
/*
|
||||
TEXT_FILTER_MATCH.C
|
||||
|
||||
Streamer mode's matcher: which code units of a string spell a hidden word,
|
||||
however it is dressed (README.md, "Streamer mode"). A string is read as the
|
||||
game draws it (draw_string.c's parsers: a wide string's "|n" breaks a line
|
||||
and any other "|" hides the character after it; a narrow string's style
|
||||
codes are not drawn), and each character it shows becomes an element:
|
||||
|
||||
- a letter, of a-z: the case folded, accents and look-alikes (Cyrillic,
|
||||
Greek, full-width, small capitals: text_filter_fold.inc) made plain;
|
||||
- a digit or symbol read as letters (leet: 0 o, 1 i or l, 3 e, @ a, $ s...),
|
||||
or as any letter (* # % ?, the last of which is what the server browser's
|
||||
listings show for a character they cannot), which may also be
|
||||
punctuation;
|
||||
- a space or punctuation, which a word may be spelled across;
|
||||
- anything else, which no word crosses.
|
||||
|
||||
Two elements together may read as one letter (ph f, vv w, \/ v, |< k).
|
||||
Then each term (text_filter_terms.inc, made by tools/text_filter_terms.py
|
||||
from Surge AI's list; and the player's own) is looked for from each element:
|
||||
a run of one letter reads as one ("fuuuck"); punctuation may come between
|
||||
letters ("f.u.c.k"); spaces too where the pieces are a letter or two
|
||||
("f u c k", "fu ck") or the match is whole words ("f uck"). A term's rule
|
||||
says where it may be (inside a word, at a word's start, or as the whole
|
||||
word), and the allowed words (Scunthorpe) are never hidden. Nothing here
|
||||
knows the game: the harness builds it alone (tools/harness).
|
||||
*/
|
||||
|
||||
#include "text_filter.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define MAXIMUM_TERMS 2048
|
||||
#define MAXIMUM_TERM_CHARACTERS 32768
|
||||
#define MAXIMUM_TERM_LENGTH 40
|
||||
#define MAXIMUM_ALLOWED 1024
|
||||
#define MAXIMUM_ALLOWED_CHARACTERS 16384
|
||||
#define MAXIMUM_GAP 4
|
||||
|
||||
#define ALL_LETTERS 0x03FFFFFFUL
|
||||
#define LETTER(c) (1UL << ((c) - 'a'))
|
||||
|
||||
/* text_filter_terms.inc's terms: their letters with each byte xor'd by
|
||||
TEXT_FILTER_TERM_KEY, so that the program holds no readable list */
|
||||
struct text_filter_term_definition
|
||||
{
|
||||
unsigned char group;
|
||||
unsigned char severity;
|
||||
unsigned char rule;
|
||||
char const *letters;
|
||||
};
|
||||
|
||||
/* text_filter_fold.inc's characters read as letters */
|
||||
struct text_filter_fold
|
||||
{
|
||||
unsigned short code;
|
||||
unsigned long letters;
|
||||
};
|
||||
|
||||
#include "text_filter_terms.inc"
|
||||
#include "text_filter_fold.inc"
|
||||
|
||||
enum
|
||||
{
|
||||
_element_letter,
|
||||
_element_digit,
|
||||
_element_symbol,
|
||||
_element_wildcard,
|
||||
_element_space,
|
||||
_element_punctuation,
|
||||
_element_break
|
||||
};
|
||||
|
||||
struct element
|
||||
{
|
||||
unsigned long letters;
|
||||
unsigned long pair;
|
||||
short first;
|
||||
short last;
|
||||
unsigned short code;
|
||||
unsigned char kind;
|
||||
};
|
||||
|
||||
struct term
|
||||
{
|
||||
short offset;
|
||||
unsigned char length;
|
||||
unsigned char group;
|
||||
unsigned char severity;
|
||||
unsigned char rule;
|
||||
};
|
||||
|
||||
static struct
|
||||
{
|
||||
int ready;
|
||||
long term_count;
|
||||
long character_count;
|
||||
struct term terms[MAXIMUM_TERMS];
|
||||
char characters[MAXIMUM_TERM_CHARACTERS];
|
||||
/* the terms by their first letter: first[letter] .. first[letter + 1] of
|
||||
order */
|
||||
short order[MAXIMUM_TERMS];
|
||||
short first[27];
|
||||
int sorted;
|
||||
long allowed_count;
|
||||
long allowed_character_count;
|
||||
short allowed[MAXIMUM_ALLOWED];
|
||||
char allowed_characters[MAXIMUM_ALLOWED_CHARACTERS];
|
||||
} text_filter_words;
|
||||
|
||||
/* a term's or allowed word's letters, kept a-z only (others dropped);
|
||||
their count */
|
||||
static long text_filter_letters(
|
||||
char *letters,
|
||||
long size,
|
||||
char const *text)
|
||||
{
|
||||
long length = 0;
|
||||
|
||||
for (; *text; text++)
|
||||
{
|
||||
char character = *text;
|
||||
|
||||
if (character >= 'A' && character <= 'Z')
|
||||
character = (char)(character - 'A' + 'a');
|
||||
if (character >= 'a' && character <= 'z' && length < size - 1)
|
||||
letters[length++] = character;
|
||||
}
|
||||
letters[length] = 0;
|
||||
return length;
|
||||
}
|
||||
|
||||
static int text_filter_word_store(
|
||||
char const *letters,
|
||||
long length,
|
||||
int group,
|
||||
int severity,
|
||||
int rule)
|
||||
{
|
||||
struct term *term;
|
||||
|
||||
if (length < 2 || length > MAXIMUM_TERM_LENGTH ||
|
||||
text_filter_words.term_count >= MAXIMUM_TERMS ||
|
||||
text_filter_words.character_count + length + 1 > MAXIMUM_TERM_CHARACTERS ||
|
||||
group < 0 || group >= NUMBER_OF_TEXT_FILTER_GROUPS ||
|
||||
severity < _text_filter_mild || severity > _text_filter_severe ||
|
||||
rule < 0 || rule >= NUMBER_OF_TEXT_FILTER_RULES)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
term = &text_filter_words.terms[text_filter_words.term_count++];
|
||||
term->offset = (short)text_filter_words.character_count;
|
||||
term->length = (unsigned char)length;
|
||||
term->group = (unsigned char)group;
|
||||
term->severity = (unsigned char)severity;
|
||||
term->rule = (unsigned char)rule;
|
||||
memcpy(text_filter_words.characters + text_filter_words.character_count, letters, length + 1);
|
||||
text_filter_words.character_count += length + 1;
|
||||
text_filter_words.sorted = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* the built-in terms and allowed words again, without the player's */
|
||||
void text_filter_words_reset(
|
||||
void)
|
||||
{
|
||||
long index;
|
||||
|
||||
text_filter_words.term_count = 0;
|
||||
text_filter_words.character_count = 0;
|
||||
text_filter_words.allowed_count = 0;
|
||||
text_filter_words.allowed_character_count = 0;
|
||||
for (index = 0; index < (long)(sizeof(text_filter_term_definitions) / sizeof(*text_filter_term_definitions)); index++)
|
||||
{
|
||||
struct text_filter_term_definition const *definition = &text_filter_term_definitions[index];
|
||||
char letters[MAXIMUM_TERM_LENGTH + 1];
|
||||
long length = 0;
|
||||
char const *encoded;
|
||||
|
||||
for (encoded = definition->letters; *encoded && length < MAXIMUM_TERM_LENGTH; encoded++)
|
||||
letters[length++] = (char)(*encoded ^ TEXT_FILTER_TERM_KEY);
|
||||
letters[length] = 0;
|
||||
text_filter_word_store(letters, length, definition->group, definition->severity, definition->rule);
|
||||
}
|
||||
for (index = 0; index < (long)(sizeof(text_filter_allowed_words) / sizeof(*text_filter_allowed_words)); index++)
|
||||
text_filter_allow_add(text_filter_allowed_words[index]);
|
||||
text_filter_words.ready = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* a term of the player's, hidden at its group's level (a-z: others are
|
||||
dropped); whether it was kept */
|
||||
int text_filter_word_add(
|
||||
char const *term,
|
||||
int group,
|
||||
int severity,
|
||||
int rule)
|
||||
{
|
||||
char letters[MAXIMUM_TERM_LENGTH + 2];
|
||||
long length;
|
||||
|
||||
if (!text_filter_words.ready)
|
||||
text_filter_words_reset();
|
||||
length = text_filter_letters(letters, sizeof(letters), term);
|
||||
return text_filter_word_store(letters, length, group, severity, rule);
|
||||
}
|
||||
|
||||
/* a word never hidden, nor any part of it (a-z); whether it was kept */
|
||||
int text_filter_allow_add(
|
||||
char const *word)
|
||||
{
|
||||
char letters[MAXIMUM_TERM_LENGTH + 2];
|
||||
long length = text_filter_letters(letters, sizeof(letters), word);
|
||||
|
||||
if (length < 2 || length > MAXIMUM_TERM_LENGTH ||
|
||||
text_filter_words.allowed_count >= MAXIMUM_ALLOWED ||
|
||||
text_filter_words.allowed_character_count + length + 1 > MAXIMUM_ALLOWED_CHARACTERS)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
text_filter_words.allowed[text_filter_words.allowed_count++] = (short)text_filter_words.allowed_character_count;
|
||||
memcpy(text_filter_words.allowed_characters + text_filter_words.allowed_character_count, letters, length + 1);
|
||||
text_filter_words.allowed_character_count += length + 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* the terms by their first letter */
|
||||
static void text_filter_words_sort(
|
||||
void)
|
||||
{
|
||||
long letter;
|
||||
long count = 0;
|
||||
|
||||
for (letter = 0; letter < 26; letter++)
|
||||
{
|
||||
long index;
|
||||
|
||||
text_filter_words.first[letter] = (short)count;
|
||||
for (index = 0; index < text_filter_words.term_count; index++)
|
||||
{
|
||||
if (text_filter_words.characters[text_filter_words.terms[index].offset] == 'a' + letter)
|
||||
text_filter_words.order[count++] = (short)index;
|
||||
}
|
||||
}
|
||||
text_filter_words.first[26] = (short)count;
|
||||
text_filter_words.sorted = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
/* ---------- elements */
|
||||
|
||||
/* a character above ASCII read as letters (text_filter_fold.inc), or 0 */
|
||||
static unsigned long text_filter_fold_letters(
|
||||
unsigned short code)
|
||||
{
|
||||
long low = 0;
|
||||
long high = (long)(sizeof(text_filter_folds) / sizeof(*text_filter_folds)) - 1;
|
||||
|
||||
while (low <= high)
|
||||
{
|
||||
long middle = (low + high) / 2;
|
||||
|
||||
if (text_filter_folds[middle].code == code)
|
||||
return text_filter_folds[middle].letters;
|
||||
if (text_filter_folds[middle].code < code)
|
||||
low = middle + 1;
|
||||
else
|
||||
high = middle - 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* what a character is (an _element_ kind; NONE (-1) for one not drawn), and
|
||||
the letters it may read as */
|
||||
static int text_filter_classify(
|
||||
unsigned short code,
|
||||
unsigned long *letters)
|
||||
{
|
||||
*letters = 0;
|
||||
if (code >= 0xFF01 && code <= 0xFF5E)
|
||||
code = (unsigned short)(code - 0xFEE0);
|
||||
if (code >= 'A' && code <= 'Z')
|
||||
code = (unsigned short)(code - 'A' + 'a');
|
||||
if (code >= 'a' && code <= 'z')
|
||||
{
|
||||
*letters = LETTER(code);
|
||||
return _element_letter;
|
||||
}
|
||||
switch (code)
|
||||
{
|
||||
case '0': *letters = LETTER('o'); return _element_digit;
|
||||
case '1': *letters = LETTER('i') | LETTER('l'); return _element_digit;
|
||||
case '2': *letters = LETTER('z'); return _element_digit;
|
||||
case '3': *letters = LETTER('e'); return _element_digit;
|
||||
case '4': *letters = LETTER('a'); return _element_digit;
|
||||
case '5': *letters = LETTER('s'); return _element_digit;
|
||||
case '6': *letters = LETTER('g') | LETTER('b'); return _element_digit;
|
||||
case '7': *letters = LETTER('t'); return _element_digit;
|
||||
case '8': *letters = LETTER('b'); return _element_digit;
|
||||
case '9': *letters = LETTER('g') | LETTER('q'); return _element_digit;
|
||||
case '@': *letters = LETTER('a'); return _element_symbol;
|
||||
case '$': *letters = LETTER('s'); return _element_symbol;
|
||||
case '!': *letters = LETTER('i'); return _element_symbol;
|
||||
case '+': *letters = LETTER('t'); return _element_symbol;
|
||||
case '(': *letters = LETTER('c'); return _element_symbol;
|
||||
case '<': *letters = LETTER('c'); return _element_symbol;
|
||||
case '|': *letters = LETTER('i') | LETTER('l'); return _element_symbol;
|
||||
case 0x00A1: *letters = LETTER('i'); return _element_symbol;
|
||||
case 0x00A2: *letters = LETTER('c'); return _element_symbol;
|
||||
case 0x00A3: *letters = LETTER('l') | LETTER('e'); return _element_symbol;
|
||||
case 0x00A5: *letters = LETTER('y'); return _element_symbol;
|
||||
case 0x00A7: *letters = LETTER('s'); return _element_symbol;
|
||||
case 0x00A9: *letters = LETTER('c'); return _element_symbol;
|
||||
case 0x00AE: *letters = LETTER('r'); return _element_symbol;
|
||||
case 0x00D7: *letters = LETTER('x'); return _element_symbol;
|
||||
case 0x20AC: *letters = LETTER('e'); return _element_symbol;
|
||||
case '*': case '#': case '%': case '?':
|
||||
*letters = ALL_LETTERS;
|
||||
return _element_wildcard;
|
||||
case ' ': case '\t': case '\r': case '\n': case 0x00A0: case 0x1680: case 0x202F: case 0x205F: case 0x3000:
|
||||
return _element_space;
|
||||
}
|
||||
if (code >= 0x2000 && code <= 0x200A)
|
||||
return _element_space;
|
||||
/* invisible: the soft hyphen, joiners, marks over letters, direction
|
||||
and variation selectors */
|
||||
if (code == 0x00AD || code == 0x034F || code == 0x180E || code == 0xFEFF ||
|
||||
(code >= 0x0300 && code <= 0x036F) || (code >= 0x1AB0 && code <= 0x1AFF) ||
|
||||
(code >= 0x1DC0 && code <= 0x1DFF) || (code >= 0x20D0 && code <= 0x20FF) ||
|
||||
(code >= 0x200B && code <= 0x200F) || (code >= 0x202A && code <= 0x202E) ||
|
||||
(code >= 0x2060 && code <= 0x2069) || (code >= 0xFE00 && code <= 0xFE0F) ||
|
||||
(code >= 0xFE20 && code <= 0xFE2F))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (code >= 0x80)
|
||||
{
|
||||
*letters = text_filter_fold_letters(code);
|
||||
if (*letters)
|
||||
return _element_letter;
|
||||
}
|
||||
if ((code > ' ' && code < 0x7F) || (code >= 0x00A1 && code <= 0x00BF) ||
|
||||
(code >= 0x2010 && code <= 0x2027) || (code >= 0x2030 && code <= 0x205E))
|
||||
{
|
||||
return _element_punctuation;
|
||||
}
|
||||
return _element_break;
|
||||
}
|
||||
|
||||
static int text_filter_style_code(
|
||||
unsigned short code)
|
||||
{
|
||||
return code && code < 0x80 && strchr("ibukprlctn", (char)code) != NULL;
|
||||
}
|
||||
|
||||
/* the string's elements as it is drawn; their count */
|
||||
static long text_filter_elements(
|
||||
unsigned short const *text,
|
||||
long length,
|
||||
int narrow,
|
||||
struct element *elements,
|
||||
long maximum)
|
||||
{
|
||||
long count = 0;
|
||||
long index = 0;
|
||||
long element_index;
|
||||
|
||||
while (index < length && count < maximum)
|
||||
{
|
||||
unsigned short code = text[index];
|
||||
struct element *element = &elements[count];
|
||||
long last = index;
|
||||
int kind;
|
||||
|
||||
if (code == '|' && index + 1 < length)
|
||||
{
|
||||
unsigned short next = text[index + 1];
|
||||
|
||||
if (narrow ? text_filter_style_code(next) : 1)
|
||||
{
|
||||
last = index + 1;
|
||||
if (next == 'n' || (narrow && next == 't'))
|
||||
{
|
||||
element->letters = 0;
|
||||
element->kind = _element_space;
|
||||
element->code = ' ';
|
||||
element->first = (short)index;
|
||||
element->last = (short)last;
|
||||
count++;
|
||||
index = last + 1;
|
||||
continue;
|
||||
}
|
||||
if (narrow)
|
||||
{
|
||||
/* a style code: not drawn */
|
||||
index = last + 1;
|
||||
continue;
|
||||
}
|
||||
/* a wide string's "|": drawn, the character after it not */
|
||||
}
|
||||
}
|
||||
kind = text_filter_classify(code, &element->letters);
|
||||
if (kind < 0)
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
element->kind = (unsigned char)kind;
|
||||
element->code = code >= 'A' && code <= 'Z' ? (unsigned short)(code - 'A' + 'a') : code;
|
||||
element->first = (short)index;
|
||||
element->last = (short)last;
|
||||
count++;
|
||||
index = last + 1;
|
||||
}
|
||||
|
||||
/* pairs read as one letter */
|
||||
for (element_index = 0; element_index < count; element_index++)
|
||||
{
|
||||
unsigned short first = elements[element_index].code;
|
||||
unsigned short second = element_index + 1 < count ? elements[element_index + 1].code : 0;
|
||||
unsigned long pair = 0;
|
||||
|
||||
if (first == 'p' && second == 'h') pair = LETTER('f');
|
||||
else if (first == 'v' && second == 'v') pair = LETTER('w');
|
||||
else if (first == '\\' && second == '/') pair = LETTER('v');
|
||||
else if (first == '/' && second == '\\') pair = LETTER('a');
|
||||
else if (first == '|' && second == '<') pair = LETTER('k');
|
||||
else if (first == '>' && second == '<') pair = LETTER('x');
|
||||
else if (first == '(' && second == ')') pair = LETTER('o');
|
||||
else if (first == '[' && second == ']') pair = LETTER('o');
|
||||
else if (first == '|' && second == ')') pair = LETTER('d');
|
||||
elements[element_index].pair = pair;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/* ---------- matching */
|
||||
|
||||
struct attempt
|
||||
{
|
||||
struct element const *elements;
|
||||
long count;
|
||||
char const *letters;
|
||||
long length;
|
||||
int rule;
|
||||
long start;
|
||||
long end;
|
||||
};
|
||||
|
||||
/* what a match has crossed so far */
|
||||
struct path
|
||||
{
|
||||
long wildcards;
|
||||
long real_letters;
|
||||
long piece;
|
||||
long longest_piece;
|
||||
int spaced;
|
||||
int crossed;
|
||||
};
|
||||
|
||||
static int text_filter_try_letter(struct attempt *attempt, long term_index, long element_index, struct path path);
|
||||
|
||||
static int text_filter_word_character(
|
||||
struct element const *element)
|
||||
{
|
||||
return element->kind == _element_letter;
|
||||
}
|
||||
|
||||
/* whether an allowed word covers the match, inside the word it is in */
|
||||
static int text_filter_allowed(
|
||||
struct attempt const *attempt)
|
||||
{
|
||||
long word_start = attempt->start;
|
||||
long word_end = attempt->end;
|
||||
long index;
|
||||
|
||||
while (word_start > 0 &&
|
||||
(attempt->elements[word_start - 1].kind == _element_letter || attempt->elements[word_start - 1].kind == _element_digit))
|
||||
{
|
||||
word_start--;
|
||||
}
|
||||
while (word_end < attempt->count &&
|
||||
(attempt->elements[word_end].kind == _element_letter || attempt->elements[word_end].kind == _element_digit))
|
||||
{
|
||||
word_end++;
|
||||
}
|
||||
for (index = 0; index < text_filter_words.allowed_count; index++)
|
||||
{
|
||||
char const *allowed = text_filter_words.allowed_characters + text_filter_words.allowed[index];
|
||||
long length = (long)strlen(allowed);
|
||||
long at;
|
||||
|
||||
for (at = word_start; at + length <= word_end; at++)
|
||||
{
|
||||
long offset;
|
||||
|
||||
if (at > attempt->start || at + length < attempt->end)
|
||||
continue;
|
||||
for (offset = 0; offset < length; offset++)
|
||||
{
|
||||
if (!(attempt->elements[at + offset].letters & LETTER(allowed[offset])))
|
||||
break;
|
||||
}
|
||||
if (offset == length)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* the term read to the element before end: whether the match stands */
|
||||
static int text_filter_finish(
|
||||
struct attempt *attempt,
|
||||
long end,
|
||||
struct path path)
|
||||
{
|
||||
struct element const *elements = attempt->elements;
|
||||
int start_boundary = attempt->start == 0 || !text_filter_word_character(&elements[attempt->start - 1]);
|
||||
int end_boundary = end >= attempt->count || !text_filter_word_character(&elements[end]);
|
||||
|
||||
if (path.piece > path.longest_piece)
|
||||
path.longest_piece = path.piece;
|
||||
/* any letter (* ? #) once in a short word, twice in a longer one, and
|
||||
never in a word of three letters */
|
||||
if (path.wildcards && (attempt->length < 4 || path.wildcards > (attempt->length <= 5 ? 1 : 2)))
|
||||
return 0;
|
||||
/* a third of the letters letters, not digits or symbols (not "8008") */
|
||||
if (!path.real_letters || path.real_letters * 3 < attempt->length)
|
||||
return 0;
|
||||
/* across spaces: pieces of a letter or two, or whole words */
|
||||
if (path.spaced && path.longest_piece > 2 && !(start_boundary && end_boundary))
|
||||
return 0;
|
||||
switch (attempt->rule)
|
||||
{
|
||||
case _text_filter_start:
|
||||
if (!start_boundary)
|
||||
return 0;
|
||||
break;
|
||||
case _text_filter_whole:
|
||||
if (!start_boundary)
|
||||
return 0;
|
||||
if (!end_boundary)
|
||||
{
|
||||
/* or with "s", or "es" after s, x, z, ch or sh */
|
||||
long after = end;
|
||||
char last = attempt->letters[attempt->length - 1];
|
||||
char before_last = attempt->length > 1 ? attempt->letters[attempt->length - 2] : 0;
|
||||
|
||||
if ((last == 's' || last == 'x' || last == 'z' || (last == 'h' && (before_last == 'c' || before_last == 's'))) &&
|
||||
after < attempt->count && (elements[after].letters & LETTER('e')) && elements[after].kind == _element_letter &&
|
||||
after + 1 < attempt->count && (elements[after + 1].letters & LETTER('s')))
|
||||
{
|
||||
after++;
|
||||
}
|
||||
if (after < attempt->count && (elements[after].letters & LETTER('s')) &&
|
||||
(elements[after].kind == _element_letter || elements[after].kind == _element_digit) &&
|
||||
(after + 1 >= attempt->count || !text_filter_word_character(&elements[after + 1])))
|
||||
{
|
||||
end = after + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!path.crossed)
|
||||
{
|
||||
attempt->end = end;
|
||||
if (text_filter_allowed(attempt))
|
||||
return 0;
|
||||
}
|
||||
attempt->end = end;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* the term's letter at term_index from element_index on, after what came
|
||||
before (spaces and punctuation between letters skipped, at most
|
||||
MAXIMUM_GAP) */
|
||||
static int text_filter_next(
|
||||
struct attempt *attempt,
|
||||
long term_index,
|
||||
long element_index,
|
||||
long gap,
|
||||
int spaced,
|
||||
struct path path)
|
||||
{
|
||||
struct element const *element;
|
||||
|
||||
if (term_index == attempt->length)
|
||||
return text_filter_finish(attempt, element_index, path);
|
||||
if (element_index >= attempt->count)
|
||||
return 0;
|
||||
element = &attempt->elements[element_index];
|
||||
if (gap > 0)
|
||||
{
|
||||
path.crossed = 1;
|
||||
if (spaced)
|
||||
{
|
||||
path.spaced = 1;
|
||||
if (path.piece > path.longest_piece)
|
||||
path.longest_piece = path.piece;
|
||||
path.piece = 0;
|
||||
}
|
||||
}
|
||||
if ((element->letters || element->pair) && text_filter_try_letter(attempt, term_index, element_index, path))
|
||||
return 1;
|
||||
if (term_index > 0 && gap < MAXIMUM_GAP &&
|
||||
(element->kind == _element_space || element->kind == _element_punctuation ||
|
||||
element->kind == _element_symbol || element->kind == _element_wildcard))
|
||||
{
|
||||
return text_filter_next(attempt, term_index, element_index + 1, gap + 1,
|
||||
spaced || element->kind == _element_space, path);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* after the term's letter at term_index, read to element end: the same
|
||||
letter again ("fuuuck") is part of it, unless the term has it twice; for a
|
||||
short whole word, only three or more of it ("woops" is not a short word's
|
||||
"o" twice) */
|
||||
static int text_filter_after(
|
||||
struct attempt *attempt,
|
||||
long term_index,
|
||||
long end,
|
||||
struct path path)
|
||||
{
|
||||
char letter = attempt->letters[term_index];
|
||||
|
||||
if (term_index + 1 >= attempt->length || attempt->letters[term_index + 1] != letter)
|
||||
{
|
||||
long repeated = end;
|
||||
|
||||
while (repeated < attempt->count &&
|
||||
(attempt->elements[repeated].kind == _element_letter || attempt->elements[repeated].kind == _element_digit) &&
|
||||
(attempt->elements[repeated].letters & LETTER(letter)))
|
||||
{
|
||||
repeated++;
|
||||
}
|
||||
if (repeated != end &&
|
||||
(attempt->rule != _text_filter_whole || attempt->length > 4 || repeated - end >= 2))
|
||||
{
|
||||
struct path longer = path;
|
||||
|
||||
longer.piece += repeated - end;
|
||||
if (text_filter_next(attempt, term_index + 1, repeated, 0, 0, longer))
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return text_filter_next(attempt, term_index + 1, end, 0, 0, path);
|
||||
}
|
||||
|
||||
static int text_filter_try_letter(
|
||||
struct attempt *attempt,
|
||||
long term_index,
|
||||
long element_index,
|
||||
struct path path)
|
||||
{
|
||||
struct element const *element = &attempt->elements[element_index];
|
||||
unsigned long letter = LETTER(attempt->letters[term_index]);
|
||||
|
||||
if (element->letters & letter)
|
||||
{
|
||||
struct path one = path;
|
||||
|
||||
one.piece++;
|
||||
if (element->kind == _element_wildcard)
|
||||
one.wildcards++;
|
||||
else if (element->kind == _element_letter)
|
||||
one.real_letters++;
|
||||
if ((element->kind != _element_wildcard || term_index > 0) &&
|
||||
text_filter_after(attempt, term_index, element_index + 1, one))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (element->pair & letter)
|
||||
{
|
||||
struct path two = path;
|
||||
|
||||
two.piece++;
|
||||
if (element->kind == _element_letter)
|
||||
two.real_letters++;
|
||||
if (text_filter_after(attempt, term_index, element_index + 2, two))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* which code units of the text (of a wide string, or narrow: char's widened)
|
||||
spell a term of a group whose level its severity reaches (levels: one for
|
||||
each group, _text_filter_off or the least severity hidden), marked in
|
||||
censored (as long as the text); how many */
|
||||
long text_filter_censor(
|
||||
unsigned short const *text,
|
||||
long length,
|
||||
int narrow,
|
||||
unsigned char const *levels,
|
||||
unsigned char *censored)
|
||||
{
|
||||
static struct element elements[TEXT_FILTER_MAXIMUM_LENGTH];
|
||||
long count;
|
||||
long start;
|
||||
long marked = 0;
|
||||
|
||||
memset(censored, 0, length);
|
||||
if (!text_filter_words.ready)
|
||||
text_filter_words_reset();
|
||||
if (!text_filter_words.sorted)
|
||||
text_filter_words_sort();
|
||||
count = text_filter_elements(text, length, narrow, elements, TEXT_FILTER_MAXIMUM_LENGTH);
|
||||
for (start = 0; start < count; start++)
|
||||
{
|
||||
unsigned long letters = elements[start].kind == _element_wildcard ? 0 : (elements[start].letters | elements[start].pair);
|
||||
long letter;
|
||||
|
||||
for (letter = 0; letters && letter < 26; letter++)
|
||||
{
|
||||
long order;
|
||||
|
||||
if (!(letters & (1UL << letter)))
|
||||
continue;
|
||||
for (order = text_filter_words.first[letter]; order < text_filter_words.first[letter + 1]; order++)
|
||||
{
|
||||
struct term const *term = &text_filter_words.terms[text_filter_words.order[order]];
|
||||
struct attempt attempt;
|
||||
struct path path;
|
||||
|
||||
if (levels[term->group] == _text_filter_off || term->severity < levels[term->group])
|
||||
continue;
|
||||
attempt.elements = elements;
|
||||
attempt.count = count;
|
||||
attempt.letters = text_filter_words.characters + term->offset;
|
||||
attempt.length = term->length;
|
||||
attempt.rule = term->rule;
|
||||
attempt.start = start;
|
||||
attempt.end = start;
|
||||
memset(&path, 0, sizeof(path));
|
||||
if (text_filter_try_letter(&attempt, 0, start, path))
|
||||
{
|
||||
long unit;
|
||||
|
||||
for (unit = elements[start].first; unit <= elements[attempt.end - 1].last; unit++)
|
||||
{
|
||||
if (!censored[unit])
|
||||
{
|
||||
censored[unit] = 1;
|
||||
marked++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return marked;
|
||||
}
|
||||
@@ -0,0 +1,653 @@
|
||||
/* port/linux/game/text_filter_terms.inc: streamer mode's words, written by
|
||||
tools/text_filter_terms.py from Surge AI's profanity_en.csv
|
||||
(sha256 ca4ff7d713a8e6f6ec2616f489cd09f6fb96b8b3af404a207783be58a91f3ede) and port/assets/text_filter/review.txt and allow.txt.
|
||||
Each term's letters are xor'd with TEXT_FILTER_TERM_KEY. Do not edit. */
|
||||
|
||||
#define TEXT_FILTER_TERM_KEY 0x5A
|
||||
|
||||
static struct text_filter_term_definition const text_filter_term_definitions[] =
|
||||
{
|
||||
{ _text_filter_insults, 1, _text_filter_anywhere, "\x3D\x33\x28\x36\x33\x3F\x38\x35\x23" },
|
||||
{ _text_filter_insults, 1, _text_filter_anywhere, "\x3D\x33\x28\x36\x23\x38\x35\x23" },
|
||||
{ _text_filter_insults, 1, _text_filter_anywhere, "\x29\x39\x2F\x37\x37\x23" },
|
||||
{ _text_filter_insults, 1, _text_filter_whole, "\x3E\x35\x36\x2E" },
|
||||
{ _text_filter_insults, 1, _text_filter_whole, "\x30\x3F\x28\x31" },
|
||||
{ _text_filter_insults, 1, _text_filter_whole, "\x34\x35\x34\x39\x3F" },
|
||||
{ _text_filter_insults, 1, _text_filter_whole, "\x29\x39\x2F\x37" },
|
||||
{ _text_filter_insults, 1, _text_filter_whole, "\x29\x33\x29\x29\x23" },
|
||||
{ _text_filter_insults, 1, _text_filter_whole, "\x2E\x35\x29\x29\x3F\x28" },
|
||||
{ _text_filter_insults, 2, _text_filter_anywhere, "\x38\x3B\x29\x29\x2E\x2F\x28\x3E" },
|
||||
{ _text_filter_insults, 2, _text_filter_anywhere, "\x38\x3B\x29\x2E\x3B\x28\x3E\x33\x29\x32" },
|
||||
{ _text_filter_insults, 2, _text_filter_anywhere, "\x38\x3B\x29\x2E\x3B\x28\x3E\x29" },
|
||||
{ _text_filter_insults, 2, _text_filter_anywhere, "\x38\x3B\x29\x2E\x3F\x28\x3E" },
|
||||
{ _text_filter_insults, 2, _text_filter_anywhere, "\x38\x3B\x29\x2E\x2F\x28\x3E" },
|
||||
{ _text_filter_insults, 2, _text_filter_anywhere, "\x3E\x2F\x37\x38\x38\x3B\x29\x2E\x3B\x28\x3E" },
|
||||
{ _text_filter_insults, 2, _text_filter_anywhere, "\x2A\x3F\x3E\x35\x38\x3F\x3B\x28" },
|
||||
{ _text_filter_insults, 2, _text_filter_anywhere, "\x2A\x3F\x3E\x35\x2A\x32\x33\x36\x3F" },
|
||||
{ _text_filter_insults, 2, _text_filter_anywhere, "\x2A\x3F\x3E\x35\x2A\x32\x36" },
|
||||
{ _text_filter_insults, 2, _text_filter_anywhere, "\x2A\x3F\x3E\x35\x20" },
|
||||
{ _text_filter_insults, 2, _text_filter_anywhere, "\x28\x3B\x2E\x38\x3B\x29\x2E\x3B\x28\x3E" },
|
||||
{ _text_filter_insults, 2, _text_filter_anywhere, "\x28\x3B\x2E\x38\x3B\x20\x2E\x3B\x3E" },
|
||||
{ _text_filter_insults, 2, _text_filter_anywhere, "\x2E\x2F\x28\x3E\x39\x2F\x2E\x2E\x3F\x28" },
|
||||
{ _text_filter_insults, 2, _text_filter_anywhere, "\x2E\x2F\x28\x3E\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_insults, 2, _text_filter_whole, "\x38\x3B\x29\x2E\x3B\x28\x3E" },
|
||||
{ _text_filter_insults, 2, _text_filter_whole, "\x38\x3B\x29\x2E\x33\x3E" },
|
||||
{ _text_filter_insults, 2, _text_filter_whole, "\x3D\x35\x2D\x36" },
|
||||
{ _text_filter_insults, 2, _text_filter_whole, "\x2A\x3F\x3E\x35" },
|
||||
{ _text_filter_insults, 2, _text_filter_whole, "\x2A\x3F\x3E\x35\x29" },
|
||||
{ _text_filter_insults, 2, _text_filter_whole, "\x2E\x2F\x28\x3E" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x38\x3F\x3B\x34\x2B\x2F\x3F\x3F\x34" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x3C\x2F\x3E\x3D\x3F\x2A\x3B\x39\x31\x3F\x28" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x35\x2C\x3F\x34\x3E\x35\x3E\x3D\x3F\x28" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x2A\x3B\x39\x31\x3F\x28\x3C\x2F\x3E\x3D\x3F\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x2A\x3B\x39\x31\x33\x34\x3D\x3C\x2F\x3E\x3D\x3F" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x2A\x3B\x39\x31\x37\x23\x3C\x2F\x3E\x3D\x3F" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x2A\x3B\x34\x39\x3B\x31\x3F\x3C\x3B\x39\x3F" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x2A\x3F\x2E\x3F\x28\x2A\x2F\x3C\x3C\x3F\x28" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x2A\x33\x36\x36\x35\x2D\x38\x33\x2E\x3F\x28" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x28\x3F\x3E\x29\x31\x33\x34" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x2E\x3B\x39\x35\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x2D\x33\x34\x3E\x35\x2D\x36\x33\x39\x31\x3F\x28" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x2D\x35\x2A\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x20\x33\x2A\x33\x34\x2E\x32\x3F\x2D\x33\x28\x3F" },
|
||||
{ _text_filter_slurs, 2, _text_filter_anywhere, "\x20\x33\x2A\x2A\x3F\x28\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_slurs, 2, _text_filter_start, "\x3B\x38\x35\x3F" },
|
||||
{ _text_filter_slurs, 2, _text_filter_start, "\x3D\x28\x35\x33\x3E" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x3B\x38\x3F\x3F\x3E" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x3B\x38\x35" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x38\x36\x3B\x39\x31\x23" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x39\x35\x35\x36\x33\x3F" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x3E\x3B\x3D\x35" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x3D\x23\x2A" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x3D\x23\x2A\x29\x23" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x32\x3F\x38\x3F" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x32\x3F\x3F\x38" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x33\x34\x30\x2F\x34" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x31\x31\x31" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x31\x36\x3B\x34" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x37\x35\x34\x3D" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x37\x35\x34\x3D\x35\x36\x35\x33\x3E" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x29\x3B\x37\x38\x35" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x29\x32\x23\x36\x35\x39\x31" },
|
||||
{ _text_filter_slurs, 2, _text_filter_whole, "\x2D\x35\x2A" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x38\x3F\x3B\x34\x3F\x28\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x39\x3B\x37\x3F\x36\x30\x35\x39\x31\x3F\x23" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x39\x3B\x28\x2A\x3F\x2E\x37\x2F\x34\x39\x32\x3F\x28" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x39\x3B\x28\x2A\x3F\x2E\x37\x2F\x34\x39\x32\x33\x34\x3D" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x39\x32\x33\x34\x3B\x2C\x33\x28\x2F\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x39\x32\x33\x34\x3F\x29\x3F\x2C\x33\x28\x2F\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x39\x32\x33\x34\x3D\x39\x32\x35\x34\x3D" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x39\x32\x33\x34\x31\x23" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x39\x35\x2E\x2E\x35\x34\x2A\x33\x39\x31\x3F\x28" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x3E\x3B\x28\x31\x33\x3F" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x3E\x35\x2E\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x3E\x2F\x34\x3F\x39\x35\x35\x34" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x3C\x3B\x3D\x38\x3B\x3D" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x3C\x3B\x3D\x3F\x2E" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x3C\x3B\x3D\x3D\x35\x2E" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x3C\x3B\x3D\x3D\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x3C\x3B\x3D\x2B\x2F\x3F\x3F\x34" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x3C\x3B\x3D\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x30\x33\x3D\x3B\x38\x35\x35" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x30\x33\x3D\x3D\x3B\x38\x35\x35" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x30\x33\x3D\x3D\x3B\x38\x2F" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x30\x33\x3D\x3D\x3B\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x30\x33\x3D\x3D\x3F\x28\x38\x35\x35" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x30\x33\x3D\x3D\x23\x38\x35\x35" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x31\x33\x31\x3F\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x31\x34\x3F\x3F\x3D\x28\x35\x2D\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x31\x2F\x31\x36\x2F\x22\x3F\x28" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x31\x23\x31\x3F" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x36\x3B\x3E\x38\x35\x23\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x36\x3B\x3E\x38\x35\x23\x20" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x36\x3B\x3E\x33\x38\x35\x23" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x36\x3B\x3E\x23\x38\x35\x23" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x37\x3F\x39\x32\x3B\x3C\x3B\x3D" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x37\x3F\x3D\x3B\x3C\x3B\x3D" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x34\x3F\x3D\x28\x35\x3F\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x34\x33\x39\x31\x3B\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x34\x33\x3D\x3B\x32" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x34\x33\x3D\x3D\x3B" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x34\x33\x3D\x3D\x3F\x28" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x34\x33\x3D\x3D\x33\x28" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x34\x33\x3D\x3D\x2F\x32" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x34\x33\x3D\x36\x3F\x2E" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x34\x33\x3D\x34\x33\x3D\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x34\x33\x3D\x34\x35\x3D" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x34\x33\x3D\x2F\x32" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x34\x33\x31\x31\x3B\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x34\x2F\x39\x31\x3B\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x2A\x32\x3B\x3D\x3D\x35\x2E\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x2A\x32\x3B\x3D\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x2A\x35\x28\x39\x32\x37\x35\x34\x31\x3F\x23" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x28\x3B\x3D\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x28\x3F\x2E\x3B\x28\x3E\x35" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x29\x32\x3F\x37\x3B\x36\x3F" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x29\x32\x37\x3B\x36\x3F" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x29\x36\x3B\x34\x2E\x3F\x23\x3F" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x29\x36\x3B\x34\x2E\x3C\x28\x3F\x3B\x31" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x29\x36\x3B\x34\x2E\x23" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x29\x2A\x33\x22" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x2E\x3B\x28\x38\x3B\x38\x23" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x2E\x35\x2D\x3F\x36\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_slurs, 3, _text_filter_anywhere, "\x2D\x3F\x2E\x38\x3B\x39\x31" },
|
||||
{ _text_filter_slurs, 3, _text_filter_start, "\x3C\x3B\x3D\x3D\x23" },
|
||||
{ _text_filter_slurs, 3, _text_filter_start, "\x3C\x36\x3B\x37\x3F\x28\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_start, "\x32\x33\x3D\x3D\x3F\x28" },
|
||||
{ _text_filter_slurs, 3, _text_filter_start, "\x32\x33\x3D\x3D\x3F\x28\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_start, "\x34\x33\x39\x39\x3F\x28" },
|
||||
{ _text_filter_slurs, 3, _text_filter_start, "\x34\x33\x3D\x3F\x28\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_start, "\x34\x33\x3D\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_start, "\x34\x33\x31\x31\x3F\x28" },
|
||||
{ _text_filter_slurs, 3, _text_filter_start, "\x2A\x32\x3B\x3D\x3D\x35\x2E" },
|
||||
{ _text_filter_slurs, 3, _text_filter_start, "\x2A\x32\x3B\x3D\x35\x2E" },
|
||||
{ _text_filter_slurs, 3, _text_filter_start, "\x29\x2A\x33\x39\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_start, "\x2D\x33\x3D\x3D\x3F\x28" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x38\x3F\x3B\x34\x3F\x28" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x39\x32\x33\x34\x31" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x39\x35\x35\x34" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x39\x35\x35\x34\x33\x3F" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x39\x35\x35\x34\x33\x3F\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x39\x35\x35\x34\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x3E\x3B\x28\x31\x23" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x3E\x23\x31\x3F" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x3C\x3B\x3D" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x3C\x3B\x3D\x3D\x33\x34\x3D" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x3C\x3B\x3D\x3D\x35" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x3C\x3B\x3D\x35\x2E" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x3C\x36\x3B\x37\x3F\x28" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x3D\x35\x35\x31" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x32\x33\x3D\x3D\x3B" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x30\x3B\x2A" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x30\x33\x3D\x3D\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x31\x33\x31\x3F" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x34\x3F\x3D\x3D\x3B" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x34\x3F\x3D\x3D\x3B\x28" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x34\x3F\x3D\x28\x35" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x34\x3F\x3D\x28\x35\x33\x3E" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x34\x33\x39\x31\x3B" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x34\x33\x39\x31\x31\x3F\x28" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x34\x33\x3D" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x34\x33\x3D\x3B" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x34\x33\x3D\x3D\x3D\x3F\x28" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x34\x33\x3D\x3D\x28\x3F\x29\x29" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x34\x33\x3D\x28\x3B" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x2A\x3B\x31\x33" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x28\x3F\x2E\x3B\x28\x3E" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x28\x3F\x2E\x3B\x28\x3E\x3F\x3E" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x29\x2A\x33\x39" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x29\x2A\x33\x3D" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x29\x2A\x33\x31" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x2E\x3B\x28\x3E" },
|
||||
{ _text_filter_slurs, 3, _text_filter_whole, "\x2E\x28\x3B\x34\x34\x23" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x38\x3B\x38\x23\x38\x3B\x2E\x2E\x3F\x28" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x38\x3B\x36\x36\x29\x3B\x39\x31" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x38\x3F\x3B\x2E\x35\x3C\x3C" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x38\x3F\x36\x36\x3F\x34\x3E" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x38\x3F\x2D\x38\x29" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x38\x2F\x3D\x3D\x3F\x28\x35\x3C\x3C" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x38\x2F\x2E\x2E\x32\x35\x36\x3F" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x38\x2F\x2E\x2E\x37\x2F\x34\x39\x32\x3F\x28\x29" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x38\x2F\x2E\x2E\x2A\x33\x28\x3B\x2E\x3F" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x39\x3B\x37\x3F\x36\x2E\x35\x3F" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x39\x32\x35\x31\x3F\x2E\x32\x3F\x39\x32\x33\x39\x31\x3F\x34" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x3E\x35\x2F\x39\x32\x3F\x38\x3B\x3D" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x3E\x35\x2F\x39\x32\x3F\x39\x3B\x34\x35\x3F" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x3C\x2F\x34\x38\x3B\x3D\x29" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x3D\x33\x28\x36\x23\x38\x33\x2E\x29" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x3D\x35\x3E\x3B\x37\x34\x33\x2E" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x3D\x35\x3E\x3E\x3B\x37\x37\x33\x2E" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x3D\x35\x3E\x3E\x3B\x37\x34\x33\x2E" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x3D\x35\x2E\x35\x32\x3F\x36\x36" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x30\x3B\x39\x31\x35\x3C\x3C" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x37\x3B\x34\x39\x32\x35\x2D\x3E\x3F\x28" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x37\x3B\x34\x29\x3F\x3F\x3E" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x37\x2F\x29\x32\x28\x35\x35\x37\x2E\x33\x2A" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x2A\x3B\x36\x37\x30\x35\x39\x31\x3F\x23" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x2A\x33\x29\x29\x3C\x3B\x39\x3F" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x2A\x33\x29\x29\x35\x3C\x3C" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x2A\x2F\x36\x36\x2E\x32\x3F\x2A\x2F\x3E" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x28\x3F\x3B\x39\x32\x3B\x28\x35\x2F\x34\x3E" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x29\x39\x32\x36\x35\x34\x3D" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x29\x32\x36\x35\x34\x3D" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x29\x2E\x2F\x37\x2A\x39\x32\x3F\x2D\x3F\x28" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x2E\x3B\x3E\x3D\x3F\x28" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x2E\x3B\x31\x33\x34\x3D\x2E\x32\x3F\x2A\x33\x29\x29" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x2E\x3B\x36\x36\x23\x2D\x3B\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x2E\x33\x39\x31\x36\x3F\x2E\x32\x3F\x2A\x33\x39\x31\x36\x3F" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x2E\x28\x35\x2F\x29\x3F\x28\x29\x34\x3B\x31\x3F" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x2F\x2A\x29\x31\x33\x28\x2E" },
|
||||
{ _text_filter_swearing, 1, _text_filter_anywhere, "\x2D\x32\x3B\x39\x31\x35\x3C\x3C" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x38\x35\x34\x3F\x28" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x38\x35\x35\x38\x29" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x38\x2F\x3D\x3D\x3F\x28" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x38\x2F\x2E\x2E" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x3E\x3B\x37\x34" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x3E\x33\x36\x3E\x35" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x3E\x35\x35\x29\x32" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x3E\x35\x2F\x39\x32\x3F" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x3D\x35\x3E\x3E\x3B\x37" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x3D\x35\x3E\x3E\x3B\x37\x34" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x3D\x35\x3E\x3E\x3B\x37\x34\x3F\x3E" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x32\x3F\x36\x36" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x35\x28\x3D\x3B\x29\x37" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x2A\x33\x29\x29" },
|
||||
{ _text_filter_swearing, 1, _text_filter_whole, "\x2E\x33\x2E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3B\x34\x3B\x36\x32\x35\x36\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3B\x34\x3B\x36\x32\x35\x28\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3B\x34\x3B\x36\x33\x34\x3D\x2F\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3B\x34\x3B\x36\x2A\x33\x28\x3B\x2E\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3B\x34\x3B\x36\x2A\x36\x2F\x3D" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3B\x34\x33\x36\x33\x34\x3D\x2F\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3B\x28\x29\x3F\x38\x3B\x34\x3E\x33\x2E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3B\x28\x29\x3F\x3D\x35\x38\x38\x36\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3B\x28\x29\x3F\x32\x35\x36\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3B\x28\x29\x3F\x29\x32\x3B\x3D\x3D\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3B\x28\x29\x3F\x2D\x33\x2A\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x3B\x36\x36\x38\x3B\x3D" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x3F\x3F\x35\x2E\x39\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x3F\x23\x35\x39\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x3F\x23\x35\x2E\x39\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x33\x3B\x2E\x39\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x33\x35\x2E\x39\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x33\x2E\x39\x32\x3F\x3E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x33\x2E\x39\x32\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x33\x2E\x39\x32\x3F\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x33\x2E\x39\x32\x3F\x20" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x33\x2E\x39\x32\x32\x35\x36\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x33\x2E\x39\x32\x33\x34\x3D" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x33\x2E\x39\x32\x36\x33\x31\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x33\x2E\x39\x32\x34\x3F\x29\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x33\x2E\x39\x32\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x33\x2E\x39\x32\x2E\x33\x2E\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x33\x2E\x39\x32\x23" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x33\x20\x20\x3B\x39\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x36\x35\x2D\x3B\x36\x35\x3B\x3E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x36\x35\x2D\x30\x35\x38" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x36\x2F\x37\x2A\x31\x33\x34" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x35\x36\x36\x35\x39\x31" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x35\x36\x36\x35\x22" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x38\x2E\x35\x2E\x32\x3F\x33\x34\x39\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x39\x32\x35\x3B\x3E\x34\x3F\x39\x2E\x3B\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x39\x36\x33\x2E\x36\x33\x39\x31\x3F\x28\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x39\x35\x38\x38\x31\x34\x35\x38\x38\x36\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x39\x28\x3B\x20\x23\x29\x35\x38" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x39\x28\x3F\x3B\x37\x2A\x33\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x39\x2F\x34\x34\x33\x36\x33\x34\x3D\x2F\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3E\x33\x28\x2E\x23\x29\x3B\x34\x39\x32\x3F\x20" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3E\x35\x3D\x3D\x35\x29\x2E\x23\x36\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3E\x35\x3D\x3D\x23\x29\x2E\x23\x36\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3E\x35\x3D\x33\x3F\x29\x2E\x23\x36\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3E\x35\x3D\x29\x2E\x23\x36\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3E\x2F\x37\x2A\x3B\x36\x35\x3B\x3E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3F\x28\x3F\x39\x2E\x35\x2A\x36\x3B\x29\x37" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3C\x3F\x39\x31\x3B\x28\x29\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3C\x3F\x36\x36\x3B\x2E\x33\x35\x3B\x3C\x33\x39\x33\x35\x34\x3B\x3E\x35" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3C\x3F\x36\x36\x3B\x2E\x35\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3D\x3B\x29\x32\x29\x2E\x3B\x38\x38\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x3D\x33\x34\x30\x35\x39\x31\x3F\x23" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x32\x3B\x28\x28\x23\x2A\x3B\x36\x37\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x32\x35\x3F\x38\x3B\x3D" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x30\x3B\x39\x31\x3B\x28\x29\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x30\x3B\x39\x31\x35\x3C\x3C\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x30\x3B\x39\x31\x35\x3C\x3C\x20" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x30\x3B\x3D\x35\x3C\x3C" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x30\x3B\x33\x36\x38\x3B\x33\x2E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x30\x3F\x28\x31\x33\x34\x3D\x35\x3C\x3C" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x30\x3F\x28\x31\x35\x3C\x3C" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x30\x3F\x28\x31\x29\x35\x3C\x3C" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x30\x35\x3C\x3C\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x31\x33\x3E\x3E\x23\x2E\x35\x2F\x39\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x31\x33\x3E\x2E\x35\x2F\x39\x32\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x31\x33\x2E\x2E\x23\x2A\x2F\x34\x39\x32\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x31\x36\x33\x2E\x35\x28\x33\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x31\x34\x35\x38\x3E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x31\x34\x35\x38\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x31\x34\x35\x38\x3C\x3B\x39\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x31\x34\x35\x38\x3D\x35\x38\x38\x36\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x37\x3F\x3B\x2E\x39\x2F\x28\x2E\x3B\x33\x34\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x34\x35\x38\x38\x23\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x34\x35\x38\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x34\x35\x38\x30\x35\x39\x31\x3F\x23" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x34\x35\x38\x30\x35\x39\x31\x33\x3F\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x34\x35\x38\x30\x35\x39\x31\x23" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x34\x35\x38\x30\x35\x31\x3F\x23" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x34\x2F\x2E\x29\x3B\x39\x31" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2A\x3F\x39\x31\x3F\x28\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2A\x35\x36\x3F\x36\x33\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2A\x35\x36\x3F\x29\x37\x35\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2A\x35\x36\x3F\x29\x2F\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2A\x28\x33\x39\x31\x3C\x3B\x39\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2A\x28\x33\x39\x31\x3D\x35\x38\x38\x36\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2A\x28\x33\x39\x31\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2A\x2F\x34\x3B\x34\x33" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x28\x33\x37\x30\x35\x38" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x29\x3B\x36\x3B\x3E\x2E\x35\x29\x29\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x29\x31\x3B\x34\x31" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x29\x36\x2F\x2E\x32\x35\x36\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x29\x36\x2F\x2E\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x29\x36\x2F\x2E\x2E\x23" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x29\x35\x34\x34\x3B\x38\x33\x2E\x39\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x29\x35\x34\x35\x38\x33\x2E\x39\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x29\x35\x34\x35\x3C\x3B\x38\x33\x2E\x39\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x29\x35\x2A\x2A\x23\x38\x35\x36\x36\x2F\x39\x31\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x29\x2E\x2F\x2A\x33\x3E\x32\x35\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x29\x2F\x37\x38\x33\x2E\x39\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x29\x2F\x37\x35\x3C\x3B\x38\x33\x2E\x39\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2E\x32\x28\x35\x3B\x2E\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2E\x32\x28\x35\x3B\x2E\x23\x35\x3D\x2F\x28\x2E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2E\x35\x29\x29\x33\x34\x3D\x29\x3B\x36\x3B\x3E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2E\x2D\x3B\x2E\x3C\x3B\x39\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2E\x2D\x3B\x2E\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2E\x2D\x3B\x2E\x2E\x33\x29\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2E\x2D\x3B\x2E\x2D\x3B\x3C\x3C\x36\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2E\x2D\x3B\x2E\x20\x33\x36\x36\x3B" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2D\x3B\x34\x31\x3F\x28\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2D\x3B\x34\x31\x35\x3C\x3C" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2D\x32\x35\x3B\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2D\x33\x36\x36\x23\x2D\x32\x3B\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_anywhere, "\x2D\x34\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_start, "\x38\x33\x39\x32\x3F\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_start, "\x38\x33\x29\x32\x3F\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_start, "\x39\x32\x35\x3B\x3E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_start, "\x39\x32\x35\x39\x32\x3B" },
|
||||
{ _text_filter_swearing, 2, _text_filter_start, "\x29\x35\x3B\x38" },
|
||||
{ _text_filter_swearing, 2, _text_filter_start, "\x2D\x3B\x34\x31\x3F\x3E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_start, "\x2D\x3B\x34\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_start, "\x2D\x3B\x34\x31\x33\x3F\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_start, "\x2D\x3B\x34\x31\x33\x34\x3D" },
|
||||
{ _text_filter_swearing, 2, _text_filter_start, "\x2D\x3B\x34\x31\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x3B\x34\x3B\x36" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x3B\x28\x29\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x38\x33\x2E\x39\x32" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x38\x33\x2E\x39\x32\x33\x34" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x39\x36\x33\x2E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x3C\x3F\x36\x36\x3B\x2E\x33\x35" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x32\x35\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x32\x35\x3F\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x31\x34\x35\x38" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x37\x3B\x29\x2E\x2F\x28\x38\x3B\x2E\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x37\x33\x36\x3C" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x2A\x3F\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x2A\x28\x33\x39\x31" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x2A\x28\x33\x39\x31\x29" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x29\x36\x3B\x3D" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x29\x36\x2F\x2E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x2E\x2D\x3B\x2E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x2E\x2D\x3B\x2E\x2E" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x2D\x3B\x34\x31" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x2D\x32\x35\x28\x3F" },
|
||||
{ _text_filter_swearing, 2, _text_filter_whole, "\x2D\x32\x35\x28\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3B\x29\x32\x35\x36\x3F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3B\x29\x29\x38\x3B\x34\x3E\x33\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3B\x29\x29\x3F\x3B\x2E\x33\x34\x3D\x34\x35\x38\x30\x35\x31\x3F\x23\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3B\x29\x29\x3C\x3B\x39\x3F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3B\x29\x29\x32\x3B\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3B\x29\x29\x32\x35\x36\x3F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3B\x29\x29\x31\x33\x29\x29\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3B\x29\x29\x36\x33\x39\x31\x33\x34\x3D" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3B\x29\x29\x37\x2F\x34\x39\x32" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3B\x29\x29\x2D\x32\x35\x36\x3F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3B\x29\x29\x2D\x33\x2A\x3F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3B\x20\x20\x32\x35\x36\x3F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x38\x35\x33\x35\x36\x3B\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x3B\x2D\x31\x37\x2F\x34\x39\x32\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x3B\x2D\x31\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x38\x35\x23" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x3E\x28\x35\x2A\x36\x3F\x2E\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x3F\x3B\x2E\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x3C\x3B\x39\x3F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x3D\x35\x38\x38\x36\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x32\x3F\x3E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x30\x35\x39\x31\x3F\x23" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x31\x34\x35\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x36\x33\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x37\x35\x34\x3D\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x37\x2F\x34\x39\x32" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x28\x33\x3E\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x29\x31\x33\x34" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x29\x37\x35\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x29\x34\x35\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x29\x2F\x39\x39" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x29\x2F\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x29\x2F\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x31\x2E\x3F\x3B\x29\x3F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x39\x29\x2F\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x31\x37\x2F\x34\x39\x32\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x35\x31\x29\x2F\x39\x31\x3B" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x28\x3B\x20\x23\x37\x35\x3C\x35\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x2F\x31\x29\x2F\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x2F\x31\x29\x2F\x31\x31\x3B" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x2F\x37\x3E\x2F\x37\x2A\x29\x2E\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x2F\x37\x3D\x35\x35\x34" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x2F\x37\x29\x36\x2F\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x2F\x34\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x39\x23\x38\x3F\x28\x3C\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x3B\x3C\x2F\x2B" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x38\x28\x3F\x3B\x2E\x32" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x3C\x3B\x39\x3F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x3D\x35\x38\x38\x36\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x30\x35\x39\x31\x33\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x36\x3F\x29\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x36\x33\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x37\x33\x36\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x37\x2F\x34\x39\x32\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x34\x33\x38\x38\x36\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x29\x31\x33\x34" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x29\x36\x3B\x2A\x2A\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x29\x37\x35\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x29\x2F\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x2C\x3B\x3D" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x39\x31\x20" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x31\x32\x3F\x3B\x3E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x2A\x29\x32\x3B\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x33\x2A\x29\x32\x33\x3E\x33\x35\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x36\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x35\x2F\x38\x36\x3F\x3E\x33\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x2F\x37\x3B\x29\x29\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x2F\x37\x38\x3B\x28\x28\x3B\x29\x29\x3F\x3E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3E\x2F\x37\x38\x3B\x29\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x3B\x39\x31\x2F\x32\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x3B\x2E\x3B\x29\x29\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x39\x39\x2F\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x39\x31\x3B" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x39\x31\x3F\x3E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x39\x31\x33\x34" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x39\x31\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x39\x31\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x39\x31\x23\x3F\x3B\x32" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x39\x31\x23\x35\x2F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x39\x2F\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x39\x2C\x31\x33\x34\x3D" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x3F\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x31\x38\x34\x23" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x31\x38\x2F\x34\x34\x23" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x31\x3F\x3E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x31\x33\x34\x3D" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x31\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x35\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x35\x35\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x35\x35\x31\x2F\x32" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x35\x2E\x32\x3F\x28\x37\x2F\x39\x31\x3F\x28\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x39\x39\x3F\x28\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x39\x39\x33\x34\x3D" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x39\x39\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x39\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x39\x22" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x3D\x3D\x3F\x28\x20" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x3D\x3D\x33\x34\x3D\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x3D\x36\x23" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x31\x39\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x31\x32\x3F\x3B\x3E\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x31\x33\x34" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x31\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x31\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x31\x2F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x31\x2D\x33\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2F\x2E\x32\x3B\x37\x2F\x39\x31\x3B" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x2C\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x3C\x22\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x32\x35\x28\x29\x3F\x29\x3B\x29\x29\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x30\x3B\x39\x31\x3B\x29\x29\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x30\x33\x20\x20\x38\x3B\x3D\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x30\x33\x20\x20\x3F\x3B\x2E\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x30\x33\x20\x20\x3F\x3E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x30\x33\x20\x20\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x30\x33\x20\x20\x33\x34\x3D" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x30\x33\x20\x20\x30\x35\x39\x31\x3F\x23" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x30\x33\x20\x20\x36\x33\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x30\x33\x20\x20\x29\x3B\x39\x31\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x30\x33\x20\x20\x29\x2E\x3B\x33\x34" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x31\x33\x3E\x3E\x33\x3E\x3E\x36\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x31\x33\x29\x29\x3B\x29\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x31\x33\x29\x29\x37\x23\x3B\x29\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x3B\x28\x31\x3B\x29\x29\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x35\x3C\x35\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x35\x3C\x2F\x39\x39\x31\x3F\x28\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x35\x3C\x2F\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x35\x2E\x32\x3B\x3C\x2F\x39\x39" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x35\x2E\x32\x3F\x28\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x35\x2E\x32\x3F\x28\x3F\x3C\x3C\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x35\x2E\x32\x3F\x28\x3C\x3B\x39\x31\x33\x34\x3D" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x35\x2E\x32\x3F\x28\x3C\x3F\x39\x31\x3B" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x35\x2E\x32\x3F\x28\x3C\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x35\x2E\x32\x3F\x28\x3C\x2F\x39\x39\x3B" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x35\x2E\x32\x3C\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x2F\x3E\x3E\x3F\x28\x3C\x2F\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x2F\x3C\x3E\x33\x2C\x3F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x2F\x3C\x3E\x33\x2C\x33\x34" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x2F\x3C\x3C\x3E\x33\x2C\x33\x34\x3D" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x37\x2F\x3C\x3C\x33\x34\x3E\x33\x2C\x33\x34" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x35\x39\x2E\x35\x2A\x2F\x29\x29\x23" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x2A\x32\x2F\x39" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x2A\x32\x2F\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x2A\x32\x2F\x2B" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x2A\x32\x2C\x39\x31\x33\x34\x3D\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x2A\x2F\x34\x31\x3B\x29\x29\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x2A\x2F\x29\x29\x36\x3F\x29\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x2A\x2F\x29\x29\x36\x33\x39\x31\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x2A\x2F\x29\x29\x23\x36\x33\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x2A\x2F\x29\x29\x23\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x2A\x2F\x29\x29\x23\x2D\x32\x33\x2A\x2A\x3F\x3E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x2A\x2F\x20\x20\x33\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x2A\x2F\x20\x20\x23" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x29\x3B\x2F\x29\x3B\x3D\x3F\x30\x35\x39\x31\x3F\x23" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x29\x32\x3F\x33\x29\x2E\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x29\x32\x33\x2A\x3E\x33\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x29\x32\x33\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x29\x2E\x2F\x2A\x33\x3E\x3B\x29\x29\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x29\x2F\x39\x31\x37\x23\x39\x35\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x29\x2F\x39\x31\x37\x23\x3E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x2E\x3B\x28\x3E\x3B\x29\x29\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_anywhere, "\x2D\x33\x29\x3F\x3B\x29\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x39\x3B\x2D\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x39\x2F\x37\x38\x3B\x36\x36" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x39\x2F\x37\x20" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x39\x2F\x2F\x34\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x3E\x3B\x28\x31\x3B\x29\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x3C\x3B\x2E\x3B\x29\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x3C\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x3C\x2F\x3D\x3D\x3F\x3E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x3C\x2F\x3D\x3D\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x3C\x2F\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x3C\x2F\x2B" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x3C\x2F\x22" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x37\x3C\x3F\x28" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x37\x3C\x3F\x28\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x37\x3C\x33\x34\x3D" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x37\x3C\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x37\x3C\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x37\x35\x3C\x35" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x37\x35\x3C\x35\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_start, "\x29\x2E\x3C\x2F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3B\x29\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3B\x29\x29\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3B\x20\x20" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x38\x2F\x36\x36\x29\x32\x33" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x39\x3B\x3B\x3B\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x39\x3B\x39\x3B" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x39\x3B\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x39\x35\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x39\x35\x39\x31\x36\x3F\x29\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x39\x35\x39\x31\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x39\x2F\x37" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x39\x2F\x37\x37\x33\x34\x3D" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x39\x2F\x37\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3E\x33\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3E\x33\x39\x31\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3E\x33\x3E\x3E\x36\x3F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3E\x33\x31\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3C\x3B\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3C\x3B\x28\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3C\x3F\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3C\x33\x39\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3C\x35\x35\x31" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3C\x2F\x39" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3C\x2F\x39\x39" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x3C\x2F\x39\x39\x3B" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x30\x3B\x39\x31\x3B\x29\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x30\x3B\x39\x31\x3B\x29\x29\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x30\x33\x20\x20" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x30\x33\x20\x20\x23" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x37\x2F\x3C\x3C" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x2A\x2F\x29\x29\x33\x3F" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x2A\x2F\x29\x29\x33\x3F\x29" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x2A\x2F\x29\x29\x23" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x2A\x2F\x2F\x29\x29\x23" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x29\x32\x3B\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x29\x32\x3F\x3F\x3F\x3F\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x29\x32\x3F\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x29\x32\x33\x3B\x2E" },
|
||||
{ _text_filter_swearing, 3, _text_filter_whole, "\x2E\x33\x2E\x29" },
|
||||
};
|
||||
|
||||
static char const *const text_filter_allowed_words[] =
|
||||
{
|
||||
"assess",
|
||||
"assesses",
|
||||
"bereshith",
|
||||
"brushite",
|
||||
"chashitsu",
|
||||
"cushite",
|
||||
"cushitic",
|
||||
"elkoshite",
|
||||
"girgashite",
|
||||
"kaneshite",
|
||||
"koreishite",
|
||||
"mackintoshite",
|
||||
"marshite",
|
||||
"matsushita",
|
||||
"metabrushite",
|
||||
"mishit",
|
||||
"mishits",
|
||||
"mishitting",
|
||||
"niggard",
|
||||
"peshito",
|
||||
"phage",
|
||||
"phages",
|
||||
"placuntitis",
|
||||
"placuntoma",
|
||||
"scunthorpe",
|
||||
"shiitake",
|
||||
"shiite",
|
||||
"shiites",
|
||||
"shiitic",
|
||||
"shitake",
|
||||
"shittah",
|
||||
"shittahs",
|
||||
"shitterton",
|
||||
"shittim",
|
||||
"shittims",
|
||||
"shittimwood",
|
||||
"snigger",
|
||||
"unniggard",
|
||||
"washita",
|
||||
"wiggeries",
|
||||
"wiggery",
|
||||
};
|
||||
@@ -472,6 +472,18 @@ static const struct config_setting config_settings[] =
|
||||
{ "update.auto", _config_boolean, "true", "HALO_UPDATE_AUTO", _environment_value, _platform_all,
|
||||
"Look for a new version when the game starts, and offer to update to it;\n"
|
||||
"false never looks (the game's \"Do not ask again\" writes false here)." },
|
||||
{ "streamer.enabled", _config_boolean, "false", "HALO_STREAMER_MODE", _environment_value, _platform_all,
|
||||
"Hide offensive words in names and text from other players, maps and mods\n"
|
||||
"as stars, at the levels below (Settings > Network Setup > Streamer Mode)." },
|
||||
{ "streamer.slurs", _config_string, "\"mild\"", "HALO_STREAMER_SLURS", _environment_value, _platform_all,
|
||||
"With streamer.enabled, the slurs (race, sexuality, gender, disability)\n"
|
||||
"hidden: \"off\", \"severe\", \"strong\" (and severe) or \"mild\" (all)." },
|
||||
{ "streamer.swearing", _config_string, "\"strong\"", "HALO_STREAMER_SWEARING", _environment_value, _platform_all,
|
||||
"With streamer.enabled, the swearing and sexual words hidden: \"off\",\n"
|
||||
"\"severe\", \"strong\" (and severe) or \"mild\" (all)." },
|
||||
{ "streamer.insults", _config_string, "\"off\"", "HALO_STREAMER_INSULTS", _environment_value, _platform_all,
|
||||
"With streamer.enabled, the insults that are not slurs hidden: \"off\",\n"
|
||||
"\"severe\", \"strong\" (and severe) or \"mild\" (all)." },
|
||||
{ "crash_reports.upload", _config_string, "\"ask\"", "HALO_CRASH_REPORTS", _environment_value, _platform_windows,
|
||||
"Send a report of each crash (a minidump and halo.log) to the developers'\n"
|
||||
"Sentry project (port/windows/src/win32_crash.c): \"yes\" sends them, \"no\"\n"
|
||||
|
||||
@@ -17,6 +17,7 @@ and the debug keyboard that the game's console reads.
|
||||
#include "port_mods.h"
|
||||
#include "xiso.h"
|
||||
#include "touch_input.h"
|
||||
#include "../game/text_filter.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdio.h>
|
||||
@@ -1237,6 +1238,9 @@ void platform_show_message(const char *title, const char *message)
|
||||
pthread_mutex_lock(&platform_message_lock);
|
||||
snprintf(platform_message_title, sizeof(platform_message_title), "%s", title);
|
||||
snprintf(platform_message_text, sizeof(platform_message_text), "%s", message);
|
||||
/* (streamer mode's words hidden here too: a map's or a mod's name) */
|
||||
text_filter_utf8(platform_message_title);
|
||||
text_filter_utf8(platform_message_text);
|
||||
platform_message_pending = TRUE;
|
||||
pthread_mutex_unlock(&platform_message_lock);
|
||||
}
|
||||
@@ -1249,10 +1253,36 @@ void web_js_ask(const char *title, const char *text, const char *labels);
|
||||
|
||||
int platform_ask(const char *title, const char *text, const char *const *labels, int count, int default_index)
|
||||
{
|
||||
/* (streamer mode's words hidden here too: mods' names) */
|
||||
static char shown_title[128];
|
||||
static char shown_text[4096];
|
||||
static char shown_labels[3][128];
|
||||
const char *shown[3];
|
||||
int index;
|
||||
#ifdef HALO_WEB
|
||||
char joined[512] = "";
|
||||
int index;
|
||||
#else
|
||||
SDL_MessageBoxButtonData buttons[3];
|
||||
SDL_MessageBoxData question;
|
||||
int answer = -1;
|
||||
#endif
|
||||
|
||||
if (count > 3)
|
||||
count = 3;
|
||||
snprintf(shown_title, sizeof(shown_title), "%s", title);
|
||||
snprintf(shown_text, sizeof(shown_text), "%s", text);
|
||||
text_filter_utf8(shown_title);
|
||||
text_filter_utf8(shown_text);
|
||||
for (index = 0; index < count; index++)
|
||||
{
|
||||
snprintf(shown_labels[index], sizeof(shown_labels[index]), "%s", labels[index]);
|
||||
text_filter_utf8(shown_labels[index]);
|
||||
shown[index] = shown_labels[index];
|
||||
}
|
||||
title = shown_title;
|
||||
text = shown_text;
|
||||
labels = shown;
|
||||
#ifdef HALO_WEB
|
||||
/* (the page's dialog: the game's thread waits for the player there, as
|
||||
SDL's box would, which a page's worker cannot show) */
|
||||
for (index = 0; index < count; index++)
|
||||
@@ -1268,13 +1298,6 @@ int platform_ask(const char *title, const char *text, const char *const *labels,
|
||||
(void)default_index;
|
||||
return __atomic_load_n(&web_shared[_web_answer], __ATOMIC_SEQ_CST);
|
||||
#else
|
||||
SDL_MessageBoxButtonData buttons[3];
|
||||
SDL_MessageBoxData question;
|
||||
int answer = -1;
|
||||
int index;
|
||||
|
||||
if (count > 3)
|
||||
count = 3;
|
||||
for (index = 0; index < count; index++)
|
||||
{
|
||||
buttons[index].flags = index == default_index ?
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org/>
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# english-words
|
||||
|
||||
A list of 370,105 English words, by dwyl and its contributors, released into
|
||||
the public domain (the Unlicense, see `LICENSE.md`).
|
||||
|
||||
Upstream: https://github.com/dwyl/english-words, commit
|
||||
`20f5cc9b3f0ccc8ce45d814c532b7c2031bba31c`. `words_alpha.txt.gz` is its
|
||||
`words_alpha.txt` (SHA-256
|
||||
3ed0c94610d8bcf7c11bbb49c56aa49c7234d32b66824df91f554169e572da48), unchanged
|
||||
but compressed (`gzip -9 -n`).
|
||||
|
||||
Nothing the game builds uses it. Streamer mode's tools do:
|
||||
`tools/text_filter_terms.py` works out where each word it hides may be found
|
||||
(inside a word, at a word's start, or only as a whole word) from which
|
||||
English words contain it, and `tools/test_text_filter_terms.py` checks that
|
||||
the words hidden in English are those expected
|
||||
(`port/assets/text_filter/english.txt`).
|
||||
Binary file not shown.
@@ -65,6 +65,7 @@ symbols in this file:
|
||||
#include "tag_groups.h"
|
||||
#include "text_group.h"
|
||||
#include "font_group.h"
|
||||
#include "text_filter.h" /* port: port/linux/game/text_filter.c */
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
@@ -247,7 +248,11 @@ void terminal_draw(
|
||||
}
|
||||
buffer[cursor_index] = '_';
|
||||
}
|
||||
/* port: the line being typed drawn as it is, not hidden by streamer
|
||||
mode (port/linux/game/text_filter.c) */
|
||||
text_filter_suspend();
|
||||
rasterizer_draw_string(&terminal_gets_bounds, NULL, NULL, 0, buffer);
|
||||
text_filter_resume();
|
||||
}
|
||||
|
||||
if (terminal_render_enable)
|
||||
|
||||
@@ -96,6 +96,7 @@ symbols in this file:
|
||||
#include "text/font_group.h"
|
||||
#include "text/text_group.h"
|
||||
#include "text/unicode.h"
|
||||
#include "text_filter.h" /* port: port/linux/game/text_filter.c */
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
@@ -691,6 +692,9 @@ static void virtual_keyboard_render_internal(
|
||||
rasterizer_draw_unicode_string(&bounds, &bounds, NULL, 0, caption);
|
||||
}
|
||||
|
||||
/* port: the text being typed drawn as it is, not hidden by streamer mode
|
||||
(port/linux/game/text_filter.c) */
|
||||
text_filter_suspend();
|
||||
draw_string_set_draw_mode(virtual_keyboard_globals.keyboard->font_tag.index, NONE, 2, 0, &text_color);
|
||||
{
|
||||
rectangle2d bounds;
|
||||
@@ -716,6 +720,7 @@ static void virtual_keyboard_render_internal(
|
||||
}
|
||||
rasterizer_draw_unicode_string(&bounds, &bounds, NULL, 0, virtual_keyboard_globals.text_buffer);
|
||||
}
|
||||
text_filter_resume(); /* port */
|
||||
|
||||
if (!virtual_keyboard_globals.first_key_replaces_buffer &&
|
||||
virtual_keyboard_globals.caret_bitmap_index != NONE &&
|
||||
|
||||
@@ -116,6 +116,7 @@ symbols in this file:
|
||||
#include "text/font_group.h"
|
||||
#include "text/international_strings.h"
|
||||
#include "text/text_group.h"
|
||||
#include "text_filter.h" /* port: port/linux/game/text_filter.c and text_filter_match.c */
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
@@ -1012,6 +1013,10 @@ void draw_string(
|
||||
struct parse_string_state state;
|
||||
point2d cursor;
|
||||
|
||||
/* port: streamer mode's words hidden, in what is drawn and what is measured
|
||||
(port/linux/game/text_filter.c) */
|
||||
string = text_filter_string(string);
|
||||
|
||||
match_assert("c:\\halo\\SOURCE\\text\\draw_string.c", 648, bounds);
|
||||
match_assert("c:\\halo\\SOURCE\\text\\draw_string.c", 649, string);
|
||||
|
||||
@@ -1210,6 +1215,10 @@ void draw_unicode_string(
|
||||
struct parse_string_state state;
|
||||
point2d cursor;
|
||||
|
||||
/* port: streamer mode's words hidden, in what is drawn and what is measured
|
||||
(port/linux/game/text_filter.c) */
|
||||
string = text_filter_unicode(string);
|
||||
|
||||
match_assert("c:\\halo\\SOURCE\\text\\draw_string.c", 848, bounds);
|
||||
match_assert("c:\\halo\\SOURCE\\text\\draw_string.c", 849, string);
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
"""Streamer mode's matcher (port/linux/game/text_filter_match.c), taken whole, with a list of made-up words so that the
|
||||
cases spell nothing offensive: how a word is found however it is dressed (case, accents, look-alikes, leet, repeats,
|
||||
spaces and punctuation, the game's "|" codes), the rules of where it may be, the allowed words and the levels."""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
from harness import build, mutated, read # noqa: E402
|
||||
|
||||
# the made-up list: (group, severity, rule, word)
|
||||
WORDS = [
|
||||
("_text_filter_slurs", 3, "_text_filter_anywhere", "zorbul"),
|
||||
("_text_filter_slurs", 2, "_text_filter_whole", "blik"),
|
||||
("_text_filter_slurs", 1, "_text_filter_start", "quan"),
|
||||
("_text_filter_swearing", 3, "_text_filter_anywhere", "frak"),
|
||||
("_text_filter_swearing", 1, "_text_filter_whole", "darn"),
|
||||
("_text_filter_swearing", 2, "_text_filter_anywhere", "bebo"),
|
||||
("_text_filter_swearing", 2, "_text_filter_anywhere", "wug"),
|
||||
("_text_filter_insults", 2, "_text_filter_whole", "gorp"),
|
||||
("_text_filter_insults", 1, "_text_filter_anywhere", "trillo"),
|
||||
("_text_filter_insults", 1, "_text_filter_whole", "zarch"),
|
||||
]
|
||||
ALLOWED = ["frakton"]
|
||||
|
||||
ALL = ("1", "1", "1")
|
||||
# (text, levels, what is shown); narrow cases are the game's char strings
|
||||
CASES = {
|
||||
"plain": [("you frak", ALL, "you ****"), ("FRAK off", ALL, "**** off"), ("zorbul", ALL, "******")],
|
||||
"inside-a-name": [("xXzorbulXx", ALL, "xX******Xx"), ("xxfrakxx", ALL, "xx****xx")],
|
||||
"accents": [("fr\u00e4k", ALL, "****"), ("FR\u00c2K", ALL, "****"), ("z\u00f8rbul", ALL, "******")],
|
||||
"look-alikes": [("fr\u0430k", ALL, "****"), ("\u0444rak", ALL, "\u0444rak"),
|
||||
("\uff46\uff52\uff41\uff4b", ALL, "****"),
|
||||
("\u1d00", ALL, "\u1d00"), ("fr\u1d00k", ALL, "****")],
|
||||
"leet": [("fr4k", ALL, "****"), ("fr@k", ALL, "****"), ("phrak", ALL, "*****"), ("z0rbu1", ALL, "******"),
|
||||
("vvug", ALL, "****"), ("|-|", ALL, "|-|")],
|
||||
"repeats": [("frrraaak", ALL, "********"), ("ffrak", ALL, "*****"), ("zorrbuul", ALL, "********")],
|
||||
"doubled-in-the-word": [("trilo", ALL, "trilo"), ("trillo", ALL, "******"), ("trilllllo", ALL, "*********")],
|
||||
"punctuation": [("f.r.a.k", ALL, "*******"), ("f-r-a-k", ALL, "*******"), ("f_r_a_k!", ALL, "*******!")],
|
||||
"spaces": [("f r a k", ALL, "*******"), ("fr ak", ALL, "*****"), ("f r a k", ALL, "**********"),
|
||||
("zor bul", ALL, "*******"), ("mazor bulb", ALL, "mazor bulb"), ("f r\ra k", ALL, "*******")],
|
||||
"too-far-apart": [("f rak", ALL, "f rak")],
|
||||
"wildcards": [("fr*k", ALL, "****"), ("fr?k", ALL, "****"), ("f**k", ALL, "f**k"), ("*rak", ALL, "*rak"),
|
||||
("zo?bu?", ALL, "******"), ("****", ALL, "****")],
|
||||
"digits-alone": [("8380", ALL, "8380"), ("b3b0", ALL, "****")],
|
||||
"hidden-characters": [("fr\u200bak", ALL, "*****"), ("fr\u00adak", ALL, "*****"), ("fra\u0301k", ALL, "*****")],
|
||||
"breaks": [("fr\u4e2dak", ALL, "fr\u4e2dak")],
|
||||
"wide-bar": [("fr|xak", ALL, "******"), ("|nfrak", ALL, "|n****")],
|
||||
"whole": [("blik", ALL, "****"), ("bliks", ALL, "*****"), ("blikes", ALL, "blikes"), ("zarches", ALL, "*******"),
|
||||
("bliker", ALL, "bliker"),
|
||||
("ablik", ALL, "ablik"), ("a blik.", ALL, "a ****."), ("gorp!", ALL, "****!")],
|
||||
"short-words": [("blik", ALL, "****"), ("bliik", ALL, "bliik"), ("bliiik", ALL, "******"),
|
||||
("zarch", ALL, "*****")],
|
||||
"start": [("quanx", ALL, "****x"), ("aquan", ALL, "aquan"), ("quan", ALL, "****")],
|
||||
"allowed": [("frakton", ALL, "frakton"), ("FR4KTON", ALL, "FR4KTON"), ("frak frakton", ALL, "**** frakton")],
|
||||
"levels": [("darn", ("1", "2", "1"), "darn"), ("darn", ("1", "1", "1"), "****"), ("frak", ("1", "0", "1"), "frak"),
|
||||
("frak", ("0", "3", "0"), "****"), ("blik", ("3", "1", "1"), "blik"), ("gorp", ("1", "1", "0"), "gorp")],
|
||||
}
|
||||
NARROW_CASES = {
|
||||
"narrow-style-codes": [("|bfrak", ALL, "|b****"), ("fr|bak", ALL, "******"), ("f|nr|na|nk", ALL, "**********")],
|
||||
"narrow-bar": [("|<", ALL, "|<")],
|
||||
}
|
||||
|
||||
# a fault in text_filter_match.c, and the case that must catch it
|
||||
NEGATIVE_CONTROLS = {
|
||||
"no-punctuation-between": (("if (term_index > 0 && gap < MAXIMUM_GAP &&", "if (0 &&"), "punctuation"),
|
||||
"no-repeats": (("if (repeated != end &&", "if (0 &&"), "repeats"),
|
||||
"short-words-stretched": (("attempt->length > 4 || repeated - end >= 2", "1"), "short-words"),
|
||||
"no-look-alikes": (("*letters = text_filter_fold_letters(code);", "*letters = 0;"), "look-alikes"),
|
||||
"nothing-allowed": (("if (text_filter_allowed(attempt))", "if (0)"), "allowed"),
|
||||
"wildcards-unlimited": (("if (path.wildcards && (", "if (0 && ("), "wildcards"),
|
||||
"digits-alone-enough": (("if (!path.real_letters || path.real_letters * 3 < attempt->length)", "if (0)"),
|
||||
"digits-alone"),
|
||||
"spaces-anywhere": (("if (path.spaced && path.longest_piece > 2 && !(start_boundary && end_boundary))", "if (0)"),
|
||||
"spaces"),
|
||||
"whole-anywhere": (("\tcase _text_filter_whole:\n\t\tif (!start_boundary)",
|
||||
"\tcase _text_filter_whole:\n\t\tif (0)"),
|
||||
"whole"),
|
||||
"levels-ignored": (("term->severity < levels[term->group]", "0"), "levels"),
|
||||
}
|
||||
|
||||
|
||||
def encoded(word):
|
||||
return "".join(f"\\x{ord(character) ^ 0x5A:02X}" for character in word)
|
||||
|
||||
|
||||
def terms_include():
|
||||
lines = ["#define TEXT_FILTER_TERM_KEY 0x5A", "static struct text_filter_term_definition const "
|
||||
"text_filter_term_definitions[] = {"]
|
||||
lines += [f'\t{{ {group}, {severity}, {rule}, "{encoded(word)}" }},' for group, severity, rule, word in WORDS]
|
||||
lines += ["};", "static char const *const text_filter_allowed_words[] = {"]
|
||||
lines += [f'\t"{word}",' for word in ALLOWED]
|
||||
lines += ["};", ""]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generated(real=False, fault=None):
|
||||
source = read("port/linux/game/text_filter_match.c")
|
||||
if fault:
|
||||
source = mutated(source, *fault)
|
||||
terms = read("port/linux/game/text_filter_terms.inc") if real else terms_include()
|
||||
return (("text_filter.h", read("port/linux/game/text_filter.h")),
|
||||
("text_filter_terms.inc", terms),
|
||||
("text_filter_fold.inc", read("port/linux/game/text_filter_fold.inc")),
|
||||
("under_test.inc", source))
|
||||
|
||||
|
||||
def censor(executable, texts, levels, narrow=False, extra=()):
|
||||
command = [str(executable), "--censor", *(["--narrow"] if narrow else []), *extra, *levels]
|
||||
data = ("\n".join(texts) + "\n").encode("latin-1" if narrow else "utf-8")
|
||||
result = subprocess.run(command, input=data, capture_output=True, check=True, timeout=60)
|
||||
return result.stdout.decode("latin-1" if narrow else "utf-8").split("\n")[:len(texts)]
|
||||
|
||||
|
||||
def failures(executable, cases, narrow=False):
|
||||
found = []
|
||||
for text, levels, expected in cases:
|
||||
shown = censor(executable, [text], levels, narrow)[0]
|
||||
if shown != expected:
|
||||
found.append(f"{text!r} at {levels}: {shown!r}, not {expected!r}")
|
||||
return found
|
||||
|
||||
|
||||
@pytest.mark.parametrize("case", sorted(CASES))
|
||||
def test_case(case):
|
||||
assert not failures(build("text_filter", generated()), CASES[case])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("case", sorted(NARROW_CASES))
|
||||
def test_narrow_case(case):
|
||||
assert not failures(build("text_filter", generated()), NARROW_CASES[case], narrow=True)
|
||||
|
||||
|
||||
def test_players_words():
|
||||
executable = build("text_filter", generated())
|
||||
assert censor(executable, ["a plonk here", "zorbul"], ("0", "0", "0"), extra=("--add", "plonk")) == \
|
||||
["a ***** here", "zorbul"]
|
||||
assert censor(executable, ["xzorbulite"], ALL, extra=("--allow", "zorbulite")) == ["xzorbulite"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("control", sorted(NEGATIVE_CONTROLS))
|
||||
def test_negative_control(control):
|
||||
fault, case = NEGATIVE_CONTROLS[control]
|
||||
assert failures(build("text_filter", generated(fault=fault)), CASES[case]), \
|
||||
f"{control}: the case {case} does not catch it"
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
Streamer mode's matcher (port/linux/game/text_filter_match.c), taken whole: each line read (UTF-8; or, narrow, bytes as
|
||||
the game's char strings) is written back with the code units it hides as '*'. The levels of SLURS, SWEARING and
|
||||
INSULTS are the arguments (0 off, 1 mild and worse, 2 strong and worse, 3 severe); the player's own words are always
|
||||
hidden. "--add word" and "--allow word" before them add a player's word and an allowed one.
|
||||
*/
|
||||
|
||||
#include "harness.h"
|
||||
#include "under_test.inc"
|
||||
|
||||
static long utf8_to_units(char const *text, unsigned short *units, long maximum)
|
||||
{
|
||||
long count = 0;
|
||||
unsigned char const *byte = (unsigned char const *)text;
|
||||
|
||||
while (*byte && count < maximum)
|
||||
{
|
||||
unsigned long code = *byte++;
|
||||
|
||||
if (code >= 0xE0 && byte[0] && byte[1])
|
||||
{
|
||||
code = ((code & 0x0F) << 12) | ((byte[0] & 0x3F) << 6) | (byte[1] & 0x3F);
|
||||
byte += 2;
|
||||
}
|
||||
else if (code >= 0xC0 && byte[0])
|
||||
{
|
||||
code = ((code & 0x1F) << 6) | (byte[0] & 0x3F);
|
||||
byte += 1;
|
||||
}
|
||||
units[count++] = (unsigned short)code;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void put_unit(unsigned short code)
|
||||
{
|
||||
if (code < 0x80)
|
||||
{
|
||||
putchar(code);
|
||||
}
|
||||
else if (code < 0x800)
|
||||
{
|
||||
putchar(0xC0 | (code >> 6));
|
||||
putchar(0x80 | (code & 0x3F));
|
||||
}
|
||||
else
|
||||
{
|
||||
putchar(0xE0 | (code >> 12));
|
||||
putchar(0x80 | ((code >> 6) & 0x3F));
|
||||
putchar(0x80 | (code & 0x3F));
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
static char line[8192];
|
||||
static unsigned short units[TEXT_FILTER_MAXIMUM_LENGTH];
|
||||
static unsigned char censored[TEXT_FILTER_MAXIMUM_LENGTH];
|
||||
unsigned char levels[NUMBER_OF_TEXT_FILTER_GROUPS] = { 1, 1, 1, 1 };
|
||||
int narrow = 0;
|
||||
int argument = 1;
|
||||
int level = 0;
|
||||
|
||||
if (argc < 2 || strcmp(argv[1], "--censor"))
|
||||
{
|
||||
fprintf(stderr, "usage: text_filter --censor [--narrow] [--add word] [--allow word] slurs swearing insults\n");
|
||||
return 2;
|
||||
}
|
||||
text_filter_words_reset();
|
||||
for (argument = 2; argument < argc; argument++)
|
||||
{
|
||||
if (!strcmp(argv[argument], "--narrow"))
|
||||
narrow = 1;
|
||||
else if (!strcmp(argv[argument], "--add") && argument + 1 < argc)
|
||||
CHECK(text_filter_word_add(argv[++argument], _text_filter_own, _text_filter_severe, _text_filter_anywhere), "add");
|
||||
else if (!strcmp(argv[argument], "--allow") && argument + 1 < argc)
|
||||
CHECK(text_filter_allow_add(argv[++argument]), "allow");
|
||||
else if (level < 3)
|
||||
levels[level++] = (unsigned char)atoi(argv[argument]);
|
||||
}
|
||||
while (fgets(line, sizeof(line), stdin))
|
||||
{
|
||||
long length;
|
||||
long index;
|
||||
|
||||
line[strcspn(line, "\n")] = 0;
|
||||
if (narrow)
|
||||
{
|
||||
for (length = 0; line[length] && length < TEXT_FILTER_MAXIMUM_LENGTH; length++)
|
||||
units[length] = (unsigned char)line[length];
|
||||
}
|
||||
else
|
||||
{
|
||||
length = utf8_to_units(line, units, TEXT_FILTER_MAXIMUM_LENGTH);
|
||||
}
|
||||
text_filter_censor(units, length, narrow, levels, censored);
|
||||
for (index = 0; index < length; index++)
|
||||
put_unit(censored[index] ? '*' : units[index]);
|
||||
putchar('\n');
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
+36
-1
@@ -262,10 +262,20 @@ _audio["categories"] = [
|
||||
# part in another's game, a screen of its own (a category, as Video Setup's)
|
||||
SEED_UPLOADS = (64, 128, 256, 512, 1024, 2048)
|
||||
_network = SCREENS["network_setup"]
|
||||
# (and the multiplayer HUD's rows, and streamer mode, screens of their own:
|
||||
# Network Setup has room for nine places)
|
||||
_hud_settings = ("display.player_names", "display.player_name_scale", "display.scoreboard_team_layout",
|
||||
"display.scoreboard_background")
|
||||
_hud_rows = [row for row in _network["rows"] if row[1] in _hud_settings]
|
||||
_network["rows"] = [row for row in _network["rows"] if row[1] not in _hud_settings]
|
||||
_network["categories"] = [
|
||||
("MAP SHARING:", "network_setup/map_sharing",
|
||||
"Downloading Custom Edition maps you do not have,\nand helping send them to other players.",
|
||||
"desktop android"),
|
||||
("MULTIPLAYER HUD:", "network_setup/multiplayer_hud",
|
||||
"Players' names above their heads, and the\nscoreboard."),
|
||||
("STREAMER MODE:", "network_setup/streamer_mode",
|
||||
"Hide offensive words in names and text from\nother players, maps and mods, as stars."),
|
||||
]
|
||||
SCREENS["network_setup/map_sharing"] = {
|
||||
"screen": "map_sharing_settings_screen", "header": _network["header"], "spacing": 30,
|
||||
@@ -281,13 +291,38 @@ SCREENS["network_setup/map_sharing"] = {
|
||||
],
|
||||
}
|
||||
|
||||
# Network Setup's multiplayer HUD: its rows, moved from Network Setup
|
||||
SCREENS["network_setup/multiplayer_hud"] = {
|
||||
"screen": "multiplayer_hud_settings_screen", "header": _network["header"], "spacing": 30,
|
||||
"rows": _hud_rows,
|
||||
}
|
||||
|
||||
# Network Setup's streamer mode (port/linux/game/text_filter.c): words hidden
|
||||
# in what other players, maps and mods show, each group from a severity of
|
||||
# Surge AI's on (port/assets/text_filter)
|
||||
STREAMER_LEVELS = [("OFF", "off"), ("SEVERE", "severe"), ("STRONG AND WORSE", "strong"), ("MILD AND WORSE", "mild")]
|
||||
SCREENS["network_setup/streamer_mode"] = {
|
||||
"screen": "streamer_mode_settings_screen", "header": _network["header"], "spacing": 30,
|
||||
"rows": [
|
||||
("STREAMER MODE:", "streamer.enabled", ON_OFF,
|
||||
"Hide offensive words in names and text from\nother players, maps and mods, as stars.", None),
|
||||
("CENSOR SLURS:", "streamer.slurs", STREAMER_LEVELS,
|
||||
"Slurs about race, sexuality, gender and\ndisability.", None),
|
||||
("CENSOR SWEARING:", "streamer.swearing", STREAMER_LEVELS,
|
||||
"Swearing and sexual words (STRONG AND WORSE\nleaves the mildest showing).", None),
|
||||
("CENSOR INSULTS:", "streamer.insults", STREAMER_LEVELS,
|
||||
"Name-calling and insults that are not slurs.", None),
|
||||
],
|
||||
}
|
||||
|
||||
# Controls Setup: the keyboard and mouse's actions, in groups (the order of
|
||||
# port/linux/game/menu_functions.c's table of them)
|
||||
CONTROL_GROUPS = ["MOVEMENT", "WEAPONS", "ACTIONS"]
|
||||
CONTROL_ROWS = 7
|
||||
|
||||
# the settings whose rows have the wider spinner (a device's name)
|
||||
WIDE_SETTINGS = {"audio.output_device", "audio.input_device"}
|
||||
WIDE_SETTINGS = {"audio.output_device", "audio.input_device", "streamer.slurs", "streamer.swearing",
|
||||
"streamer.insults"}
|
||||
|
||||
# the host's voice chat and vote kicks, on Teamplay Options
|
||||
# (_teamplay_options_extras): each row's key, its label, its choices' words
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
"""Streamer mode's words (port/linux/game/text_filter_terms.inc, tools/text_filter_terms.py): the table agrees with the
|
||||
review (port/assets/text_filter/review.txt) and the allowed words, hides in the English word list just the words
|
||||
expected (english.txt), and hides nothing of the game's own text but its two lines of swearing: the menus' (asset-free,
|
||||
at the widest levels) and the maps' string lists and HUD messages (assets/maps, else skipped; at the default levels).
|
||||
With HALO_SURGE_CSV naming Surge AI's profanity_en.csv, the table is made again from it and must be the one committed.
|
||||
|
||||
The words are ROT13'd here and in the files, as in review.txt; a failure prints them so."""
|
||||
|
||||
import codecs
|
||||
import gzip
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
sys.path.insert(0, str(ROOT / "tools" / "harness" / "tests"))
|
||||
import text_filter_terms # noqa: E402
|
||||
from harness import build # noqa: E402
|
||||
from test_text_filter import censor, generated # noqa: E402
|
||||
|
||||
ASSETS = ROOT / "port" / "assets" / "text_filter"
|
||||
MAPS = ROOT / "assets" / "maps"
|
||||
ALL = ("1", "1", "1")
|
||||
# the settings' defaults (port_config.c): slurs mild and worse, swearing strong and worse, insults off
|
||||
DEFAULTS = ("1", "2", "0")
|
||||
|
||||
|
||||
def rot13(text):
|
||||
return codecs.encode(text, "rot13")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def matcher():
|
||||
return build("text_filter", generated(real=True))
|
||||
|
||||
|
||||
def hidden(matcher, texts, levels):
|
||||
"""the texts something is hidden in"""
|
||||
texts = [text.replace("\n", " ").replace("\r", " ") for text in texts]
|
||||
return {text for text, shown in zip(texts, censor(matcher, texts, levels)) if shown != text}
|
||||
|
||||
|
||||
def test_table_agrees_with_the_review():
|
||||
review = text_filter_terms.read_review(ASSETS / "review.txt")
|
||||
terms = text_filter_terms.read_terms()
|
||||
keys = {key for _, _, _, key in terms}
|
||||
allowed = re.findall(r'^\t"([a-z]+)",$', text_filter_terms.TERMS_INC.read_text(), re.M)
|
||||
assert allowed == sorted(set(text_filter_terms.read_allowed(ASSETS / "allow.txt")))
|
||||
dropped = {text_filter_terms.letters(word) for word in review["drop"]} - {None}
|
||||
assert not keys & dropped, f"dropped words in the table: {sorted(rot13(key) for key in keys & dropped)}"
|
||||
anywhere = [(group, severity, key) for group, severity, rule, key in terms if rule == "anywhere"]
|
||||
for word, (group, severity, rule) in review["keep"].items():
|
||||
key = text_filter_terms.letters(word)
|
||||
assert key in keys or any(other in key for _, _, other in anywhere), \
|
||||
f"a kept word is not in the table: {rot13(word)}"
|
||||
for word in review["term"]:
|
||||
assert text_filter_terms.letters(word) in keys, f"a review term is not in the table: {rot13(word)}"
|
||||
recorded = re.search(r"sha256 ([0-9a-f]{64})", text_filter_terms.TERMS_INC.read_text()).group(1)
|
||||
assert recorded in (ASSETS / "README.md").read_text(), "the README names another version of the list"
|
||||
|
||||
|
||||
@pytest.mark.skipif(not os.environ.get("HALO_SURGE_CSV"), reason="HALO_SURGE_CSV (Surge AI's profanity_en.csv) unset")
|
||||
def test_table_made_again():
|
||||
csv_path = os.environ["HALO_SURGE_CSV"]
|
||||
assert hashlib.sha256(Path(csv_path).read_bytes()).hexdigest() in text_filter_terms.TERMS_INC.read_text()
|
||||
english = text_filter_terms.English(text_filter_terms.ENGLISH)
|
||||
terms, unreviewed, _ = text_filter_terms.build(csv_path, text_filter_terms.read_review(ASSETS / "review.txt"),
|
||||
english)
|
||||
assert not unreviewed
|
||||
assert terms == text_filter_terms.read_terms()
|
||||
|
||||
|
||||
def test_english_words_hidden(matcher):
|
||||
with gzip.open(text_filter_terms.ENGLISH, "rt") as file:
|
||||
words = file.read().split()
|
||||
found = hidden(matcher, words, ALL)
|
||||
expected = {rot13(line) for line in (ASSETS / "english.txt").read_text().split()}
|
||||
assert found == expected, (f"hidden, not expected: {sorted(rot13(word) for word in found - expected)}; "
|
||||
f"expected, not hidden: {sorted(rot13(word) for word in expected - found)} "
|
||||
"(ROT13'd: if right, port/assets/text_filter/english.txt)")
|
||||
|
||||
|
||||
def menu_texts():
|
||||
texts = []
|
||||
for path in sorted((ROOT / "port" / "assets" / "menus").rglob("*.xml")):
|
||||
for value in re.findall(r'\b(?:text|strings)="([^"]*)"', path.read_text(encoding="utf-8")):
|
||||
texts += [part.replace("\\n", " ") for part in value.split("|")]
|
||||
return [text for text in texts if text.strip()]
|
||||
|
||||
|
||||
# the game's own text that has swearing in it (ROT13'd: a multiplayer profile name, and a tip of the loading
|
||||
# screens the PC version's menus have too), hidden as other players' would be
|
||||
PROFILE_NAME = rot13("Cevfba Ovgpu")
|
||||
TIP = rot13("Lbh unir bar yvsr gb yvir. Ohg ng yrnfg lbh'er hfvat vg gb xvpx nff npebff gur tnynkl vafgrnq bs whfg "
|
||||
"fvggvat va n znafvba pelvat jvgu lbhe evpu fbpvnyvgr ybiref.")
|
||||
|
||||
|
||||
def test_menus_hide_nothing(matcher):
|
||||
texts = menu_texts()
|
||||
assert len(texts) > 500
|
||||
found = hidden(matcher, texts, ALL)
|
||||
assert found == {TIP}, f"the menus' own text hidden: {sorted(rot13(text) for text in found)}"
|
||||
|
||||
|
||||
def map_texts(path):
|
||||
from test_mods import TAG_BASE, MapTags
|
||||
tags = MapTags(path)
|
||||
texts = []
|
||||
for group, _, definition in tags.instances.values():
|
||||
if group == b"ustr":
|
||||
references = [data for _, data in tags.block(definition, 0, 20)]
|
||||
elif group == b"hmt ":
|
||||
references = [tags.tags[definition - TAG_BASE:][:20]]
|
||||
else:
|
||||
continue
|
||||
for reference in references:
|
||||
size, _, _, address, _ = struct.unpack("<iiiII", reference)
|
||||
if size > 0:
|
||||
text = tags.tags[address - TAG_BASE:address - TAG_BASE + size].decode("utf-16-le", "replace")
|
||||
texts += [part for part in text.split("\0") if part.strip()]
|
||||
return texts
|
||||
|
||||
|
||||
@pytest.mark.skipif(not MAPS.is_dir() or not any(MAPS.glob("*.map")), reason="no maps (assets/maps)")
|
||||
def test_maps_hide_nothing_by_default(matcher):
|
||||
texts = []
|
||||
for path in sorted(MAPS.glob("*.map")):
|
||||
try:
|
||||
texts += map_texts(path)
|
||||
except Exception: # (a map this reader cannot read: a Custom Edition one)
|
||||
continue
|
||||
assert len(texts) > 1000
|
||||
found = {" ".join(text.split()) for text in hidden(matcher, sorted(set(texts)), DEFAULTS)}
|
||||
assert found == {PROFILE_NAME, TIP}, f"the maps' own text hidden: {sorted(rot13(text) for text in found)}"
|
||||
|
||||
|
||||
def test_check_command():
|
||||
result = subprocess.run([sys.executable, str(ROOT / "tools" / "text_filter_terms.py"), "--check",
|
||||
rot13("shpx guvf"), "Scunthorpe"], capture_output=True, text=True, check=True)
|
||||
assert "-> '**** this'" in result.stdout and "-> 'Scunthorpe'" in result.stdout
|
||||
@@ -0,0 +1,367 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Streamer mode's words (port/linux/game/text_filter_match.c): writes
|
||||
port/linux/game/text_filter_terms.inc from Surge AI's rated list of
|
||||
profanities and this project's review of it, and lists or tries them.
|
||||
|
||||
python3 tools/text_filter_terms.py --surge .../profanity_en.csv
|
||||
python3 tools/text_filter_terms.py --list [slurs|swearing|insults]
|
||||
python3 tools/text_filter_terms.py --check "some text"
|
||||
python3 tools/text_filter_terms.py --fold
|
||||
|
||||
The list (port/assets/text_filter/README.md): Surge AI's profanity_en.csv
|
||||
(MIT), each row a spelling with up to three base words, three categories
|
||||
and a severity (Mild, Strong, Severe). A base word's group is its own row's
|
||||
first category's (failing that, the first category most of its spellings
|
||||
give): SLURS, SWEARING or INSULTS (GROUPS). Its severity is the worst of the
|
||||
rows that name it alone (failing that, of all its rows). Its spellings are
|
||||
the rows that name it alone, kept to their letters; those with digits or
|
||||
symbols are left to the matcher's leet, and those another term already
|
||||
finds are left out.
|
||||
|
||||
port/assets/text_filter/review.txt is the review of every base word, the
|
||||
words ROT13'd (nothing in the repository spells them out):
|
||||
|
||||
keep <group|-> <severity|-> <rule|-> <word> a base word kept: its group
|
||||
(- Surge's first category's),
|
||||
its severity (- Surge's),
|
||||
its rule (- worked out)
|
||||
drop <word> a base word or spelling left out
|
||||
term <group> <severity> <rule|-> <word> a word of the project's, or
|
||||
a spelling's own settings
|
||||
|
||||
A rule (where a term may be found: text_filter.h) worked out is "anywhere"
|
||||
when no English word (tools/../port/third_party/english-words) has it but
|
||||
its own forms (s, es, ed, er, ing, y), "start" when none starts with it but
|
||||
those, and "whole" otherwise. port/assets/text_filter/allow.txt is the words
|
||||
never hidden (Scunthorpe), in plain text.
|
||||
|
||||
A base word of the CSV that the review does not name stops the generator,
|
||||
which then prints a line to review for each (--template prints them all).
|
||||
The CSV is not in the repository: the .inc records its checksum, and
|
||||
tools/test_text_filter_terms.py checks the .inc against the review without
|
||||
it. --fold writes text_filter_fold.inc, the characters above ASCII that read
|
||||
as letters, from Python's Unicode names (its version is recorded).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import codecs
|
||||
import csv
|
||||
import gzip
|
||||
import hashlib
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import unicodedata
|
||||
from collections import Counter, defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
ASSETS = ROOT / "port/assets/text_filter"
|
||||
REVIEW = ASSETS / "review.txt"
|
||||
ALLOW = ASSETS / "allow.txt"
|
||||
TERMS_INC = ROOT / "port/linux/game/text_filter_terms.inc"
|
||||
FOLD_INC = ROOT / "port/linux/game/text_filter_fold.inc"
|
||||
ENGLISH = ROOT / "port/third_party/english-words/words_alpha.txt.gz"
|
||||
KEY = 0x5A
|
||||
|
||||
GROUPS = {
|
||||
"racial / ethnic slurs": "slurs", "sexual orientation / gender": "slurs", "mental disability": "slurs",
|
||||
"physical disability": "slurs", "political": "slurs",
|
||||
"sexual anatomy / sexual acts": "swearing", "bodily fluids / excrement": "swearing",
|
||||
"religious offense": "swearing",
|
||||
"other / general insult": "insults", "animal references": "insults", "physical attributes": "insults",
|
||||
}
|
||||
GROUP_NAMES = {"slurs": "_text_filter_slurs", "swearing": "_text_filter_swearing", "insults": "_text_filter_insults"}
|
||||
SEVERITIES = {"Mild": 1, "Strong": 2, "Severe": 3}
|
||||
SEVERITY_NAMES = {1: "mild", 2: "strong", 3: "severe"}
|
||||
RULES = {"anywhere": "_text_filter_anywhere", "start": "_text_filter_start", "whole": "_text_filter_whole"}
|
||||
FORMS = ("", "s", "es", "ed", "er", "ers", "ing", "y", "ies")
|
||||
|
||||
|
||||
def rot13(text):
|
||||
return codecs.encode(text, "rot13")
|
||||
|
||||
|
||||
def letters(text):
|
||||
"""a term's key: its letters, lowercase (spaces, hyphens, apostrophes
|
||||
and stops dropped); None for one with digits or other symbols"""
|
||||
text = text.lower()
|
||||
if re.search(r"[^a-z \-'.]", text):
|
||||
return None
|
||||
return re.sub(r"[^a-z]", "", text)
|
||||
|
||||
|
||||
def collapse(word):
|
||||
"""a word with each run of one letter made one ("batter" "bater")"""
|
||||
return re.sub(r"(.)\1+", r"\1", word)
|
||||
|
||||
|
||||
def read_review(path):
|
||||
review = {"keep": {}, "drop": set(), "term": {}}
|
||||
for number, line in enumerate(path.read_text().splitlines(), 1):
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
parts = line.split(None, 4 if line.startswith(("keep", "term")) else 1)
|
||||
kind = parts[0]
|
||||
if kind == "drop" and len(parts) == 2:
|
||||
review["drop"].add(rot13(parts[1]))
|
||||
elif kind in ("keep", "term") and len(parts) == 5:
|
||||
_, group, severity, rule, word = parts
|
||||
known_group = group in GROUP_NAMES or (kind == "keep" and group == "-")
|
||||
known_severity = severity == "-" or severity in SEVERITY_NAMES.values()
|
||||
if not known_group or not known_severity or (rule != "-" and rule not in RULES):
|
||||
sys.exit(f"{path}:{number}: a group, severity or rule this does not know")
|
||||
entry = (None if group == "-" else group, None if severity == "-" else
|
||||
{name: value for value, name in SEVERITY_NAMES.items()}[severity], None if rule == "-" else rule)
|
||||
review[kind][rot13(word)] = entry
|
||||
else:
|
||||
sys.exit(f"{path}:{number}: not a review line")
|
||||
return review
|
||||
|
||||
|
||||
def read_allowed(path):
|
||||
return [line.strip().lower() for line in path.read_text().splitlines()
|
||||
if line.strip() and not line.startswith("#")]
|
||||
|
||||
|
||||
class English:
|
||||
"""the English word list, for working out rules"""
|
||||
|
||||
def __init__(self, path):
|
||||
with gzip.open(path, "rt") as file:
|
||||
self.words = file.read().split()
|
||||
self.text = "\n" + "\n".join(self.words) + "\n"
|
||||
self.collapsed = "\n" + "\n".join(collapse(word) for word in self.words) + "\n"
|
||||
|
||||
@staticmethod
|
||||
def _find(text, key, own):
|
||||
found, at = [], 0
|
||||
while True:
|
||||
at = text.find(key, at)
|
||||
if at < 0:
|
||||
return found
|
||||
start = text.rindex("\n", 0, at) + 1
|
||||
end = text.index("\n", at)
|
||||
word = text[start:end]
|
||||
if word not in own:
|
||||
found.append(word)
|
||||
at = end
|
||||
|
||||
def containing(self, key):
|
||||
"""the words that the matcher would find the key in (the key, it with
|
||||
"ph" for "f" and "vv" for "w", or with any letter doubled), but its
|
||||
own forms: (word, whether it starts with the key)"""
|
||||
found = []
|
||||
for variant in {key, key.replace("f", "ph"), key.replace("w", "vv")}:
|
||||
own = {variant + form for form in FORMS}
|
||||
found += [(word, word.startswith(variant)) for word in self._find(self.text, variant, own)]
|
||||
short = collapse(variant)
|
||||
found += [(word, word.startswith(short)) for word in self._find(self.collapsed, short,
|
||||
{collapse(word) for word in own})]
|
||||
return found
|
||||
|
||||
def rule(self, key):
|
||||
others = self.containing(key)
|
||||
if len(key) >= 4 and not others:
|
||||
return "anywhere"
|
||||
if not any(starts for _, starts in others):
|
||||
return "start"
|
||||
return "whole"
|
||||
|
||||
|
||||
def build(csv_path, review, english):
|
||||
"""the terms: (group, severity, rule, key), and the base words the review
|
||||
has not named"""
|
||||
rows = list(csv.DictReader(open(csv_path, encoding="utf-8")))
|
||||
bases = defaultdict(list)
|
||||
for row in rows:
|
||||
forms = [row[f"canonical_form_{index}"] for index in (1, 2, 3) if row[f"canonical_form_{index}"]]
|
||||
for form in forms:
|
||||
bases[form].append((row, len(forms) == 1))
|
||||
unreviewed, kept = [], {}
|
||||
for base, base_rows in sorted(bases.items()):
|
||||
if base in review["drop"]:
|
||||
continue
|
||||
if base not in review["keep"]:
|
||||
unreviewed.append(base)
|
||||
continue
|
||||
own = [row for row, _ in base_rows if row["text"].lower() == base]
|
||||
category = own[0]["category_1"] if own else \
|
||||
Counter(row["category_1"] for row, _ in base_rows).most_common(1)[0][0]
|
||||
alone = [row for row, single in base_rows if single] or [row for row, _ in base_rows]
|
||||
group, severity, rule = review["keep"][base]
|
||||
kept[base] = (group or GROUPS[category],
|
||||
severity or max(SEVERITIES[row["severity_description"]] for row in alone),
|
||||
rule, [row["text"] for row, single in base_rows if single])
|
||||
terms = {}
|
||||
dropped = {letters(word) for word in review["drop"]}
|
||||
for base, (group, severity, rule, spellings) in kept.items():
|
||||
key = letters(base)
|
||||
if key:
|
||||
terms[key] = (group, severity, rule or english.rule(key))
|
||||
for base, (group, severity, rule, spellings) in kept.items():
|
||||
for spelling in spellings:
|
||||
key = letters(spelling)
|
||||
if not key or key in terms or key in dropped:
|
||||
continue
|
||||
terms[key] = (group, severity, None)
|
||||
for word, (group, severity, rule) in review["term"].items():
|
||||
key = letters(word)
|
||||
if not key:
|
||||
sys.exit(f"a review term has digits or symbols: {rot13(word)}")
|
||||
old = terms.get(key)
|
||||
terms[key] = (group, severity or (old[1] if old else 3), rule or (old[2] if old else None))
|
||||
for key, (group, severity, rule) in list(terms.items()):
|
||||
if rule is None:
|
||||
terms[key] = (group, severity, english.rule(key))
|
||||
# (a spelling another term finds: left out)
|
||||
found = {key for key, (group, severity, rule) in terms.items()
|
||||
if any(other != key and other in key and terms[other][2] == "anywhere" and terms[other][0] == group and
|
||||
terms[other][1] >= severity for other in terms)}
|
||||
result = sorted((group, severity, rule, key) for key, (group, severity, rule) in terms.items() if key not in found)
|
||||
return result, unreviewed, bases
|
||||
|
||||
|
||||
def template_line(base, base_rows, english):
|
||||
own = [row for row, _ in base_rows if row["text"].lower() == base]
|
||||
category = own[0]["category_1"] if own else Counter(row["category_1"] for row, _ in base_rows).most_common(1)[0][0]
|
||||
key = letters(base)
|
||||
rule = english.rule(key) if key else "-"
|
||||
return f"keep {GROUPS[category]} - {rule} {rot13(base)}"
|
||||
|
||||
|
||||
def c_string(key):
|
||||
return "".join(f"\\x{ord(character) ^ KEY:02X}" for character in key)
|
||||
|
||||
|
||||
def write_terms(terms, allowed, csv_path):
|
||||
digest = hashlib.sha256(Path(csv_path).read_bytes()).hexdigest() if csv_path else None
|
||||
lines = ["/* port/linux/game/text_filter_terms.inc: streamer mode's words, written by",
|
||||
"tools/text_filter_terms.py from Surge AI's profanity_en.csv",
|
||||
f"(sha256 {digest}) and port/assets/text_filter/review.txt and allow.txt.",
|
||||
"Each term's letters are xor'd with TEXT_FILTER_TERM_KEY. Do not edit. */", "",
|
||||
f"#define TEXT_FILTER_TERM_KEY 0x{KEY:02X}", "",
|
||||
"static struct text_filter_term_definition const text_filter_term_definitions[] =", "{"]
|
||||
for group, severity, rule, key in terms:
|
||||
lines.append(f'\t{{ {GROUP_NAMES[group]}, {severity}, {RULES[rule]}, "{c_string(key)}" }},')
|
||||
lines += ["};", "", "static char const *const text_filter_allowed_words[] =", "{"]
|
||||
lines += [f'\t"{word}",' for word in sorted(set(allowed))]
|
||||
lines += ["};", ""]
|
||||
TERMS_INC.write_text("\n".join(lines))
|
||||
|
||||
|
||||
def read_terms():
|
||||
"""the committed .inc's terms: (group, severity, rule, key)"""
|
||||
text = TERMS_INC.read_text()
|
||||
groups = {name: group for group, name in GROUP_NAMES.items()}
|
||||
rules = {name: rule for rule, name in RULES.items()}
|
||||
terms = []
|
||||
for group, severity, rule, encoded in re.findall(r'\{ (\w+), (\d), (\w+), "([^"]*)" \}', text):
|
||||
key = "".join(chr(int(byte, 16) ^ KEY) for byte in re.findall(r"\\x([0-9A-F]{2})", encoded))
|
||||
terms.append((groups[group], int(severity), rules[rule], key))
|
||||
return terms
|
||||
|
||||
|
||||
# ---------- characters above ASCII read as letters
|
||||
|
||||
# Greek and Cyrillic letters that look like Latin ones
|
||||
LOOK_ALIKES = {
|
||||
"α": "a", "β": "b", "γ": "y", "ε": "e", "η": "n", "ι": "i", "κ": "k", "ν": "v", "ο": "o", "ρ": "p", "τ": "t",
|
||||
"υ": "u", "χ": "x", "ω": "w", "Α": "a", "Β": "b", "Ε": "e", "Ζ": "z", "Η": "h", "Ι": "i", "Κ": "k", "Μ": "m",
|
||||
"Ν": "n", "Ο": "o", "Ρ": "p", "Τ": "t", "Υ": "y", "Χ": "x",
|
||||
"а": "a", "в": "b", "е": "e", "ё": "e", "ѕ": "s", "і": "i", "ї": "i", "ј": "j", "к": "k", "м": "m", "н": "h",
|
||||
"о": "o", "п": "n", "р": "p", "с": "c", "т": "t", "у": "y", "х": "x", "ь": "b", "ԁ": "d", "ӏ": "l", "г": "r",
|
||||
"А": "a", "В": "b", "Е": "e", "Ё": "e", "Ѕ": "s", "І": "i", "Ї": "i", "Ј": "j", "К": "k", "М": "m", "Н": "h",
|
||||
"О": "o", "Р": "p", "С": "c", "Т": "t", "У": "y", "Х": "x", "Ь": "b", "Ԁ": "d", "Ӏ": "l",
|
||||
"µ": "u", "ß": "sb", "ſ": "sf", "ı": "i", "ȷ": "j", "ł": "l", "Ł": "l", "ø": "o", "Ø": "o", "đ": "d", "Đ": "d",
|
||||
"ħ": "h", "Ħ": "h", "ŧ": "t", "Ŧ": "t", "æ": "a", "Æ": "a", "œ": "o", "Œ": "o", "þ": "p", "Þ": "p",
|
||||
}
|
||||
NAME = re.compile(r"^(?:LATIN|CIRCLED LATIN|PARENTHESIZED LATIN|LATIN SUBSCRIPT|SUPERSCRIPT LATIN) "
|
||||
r"(?:SMALL|CAPITAL) LETTER (?:SMALL CAPITAL )?([A-Z])(?: WITH .*| BAR| STROKE)?$")
|
||||
OTHER_NAMES = [re.compile(r"^LATIN LETTER SMALL CAPITAL ([A-Z])$"),
|
||||
re.compile(r"^MODIFIER LETTER (?:SMALL|CAPITAL) ([A-Z])$")]
|
||||
|
||||
|
||||
def fold_letters(code):
|
||||
character = chr(code)
|
||||
found = LOOK_ALIKES.get(character)
|
||||
if found is None:
|
||||
base = unicodedata.normalize("NFD", character)[0]
|
||||
found = LOOK_ALIKES.get(base) if base != character else None
|
||||
if found is None:
|
||||
name = unicodedata.name(character, "")
|
||||
for pattern in [NAME, *OTHER_NAMES]:
|
||||
match = pattern.match(name)
|
||||
if match:
|
||||
found = match.group(1).lower()
|
||||
break
|
||||
return found
|
||||
|
||||
|
||||
def write_fold():
|
||||
lines = ["/* port/linux/game/text_filter_fold.inc: the characters above ASCII that",
|
||||
"streamer mode reads as letters (a-z bits), written by tools/text_filter_terms.py",
|
||||
f"--fold from Unicode {unicodedata.unidata_version}'s names and its look-alikes. Do not edit. */", "",
|
||||
"static struct text_filter_fold const text_filter_folds[] =", "{"]
|
||||
for code in range(0x80, 0x10000):
|
||||
if 0xD800 <= code <= 0xDFFF or 0xFF01 <= code <= 0xFF5E:
|
||||
continue
|
||||
found = fold_letters(code)
|
||||
if found:
|
||||
bits = " | ".join(f"LETTER('{letter}')" for letter in sorted(set(found)))
|
||||
lines.append(f"\t{{ 0x{code:04X}, {bits} }},")
|
||||
lines += ["};", ""]
|
||||
FOLD_INC.write_text("\n".join(lines))
|
||||
|
||||
|
||||
def check(texts):
|
||||
"""the matcher's censoring of each text (all groups at mild), built as
|
||||
the harness builds it"""
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
sys.path.insert(0, str(ROOT / "tools" / "harness" / "tests"))
|
||||
from harness import build # noqa: E402
|
||||
from test_text_filter import generated # noqa: E402
|
||||
executable = build("text_filter", generated(real=True))
|
||||
result = subprocess.run([str(executable), "--censor", "1", "1", "1"], input="\n".join(texts) + "\n",
|
||||
capture_output=True, text=True, check=True)
|
||||
return result.stdout.splitlines()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument("--surge", help="Surge AI's profanity_en.csv: write text_filter_terms.inc")
|
||||
parser.add_argument("--template", action="store_true", help="with --surge: a review line for each base word")
|
||||
parser.add_argument("--list", nargs="?", const="all", help="the terms, decoded (a group, or all)")
|
||||
parser.add_argument("--check", nargs="+", help="texts to try: what is hidden")
|
||||
parser.add_argument("--fold", action="store_true", help="write text_filter_fold.inc")
|
||||
arguments = parser.parse_args()
|
||||
if arguments.fold:
|
||||
write_fold()
|
||||
if arguments.surge:
|
||||
english = English(ENGLISH)
|
||||
review = read_review(REVIEW)
|
||||
terms, unreviewed, bases = build(arguments.surge, review, english)
|
||||
if arguments.template:
|
||||
for base in sorted(bases):
|
||||
print(template_line(base, bases[base], english))
|
||||
return
|
||||
if unreviewed:
|
||||
print("base words the review does not name (review.txt lines to start from):", file=sys.stderr)
|
||||
for base in unreviewed:
|
||||
print(template_line(base, bases[base], english), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
write_terms(terms, read_allowed(ALLOW), arguments.surge)
|
||||
print(f"{len(terms)} terms: " + ", ".join(f"{count} {group}" for group, count in
|
||||
sorted(Counter(term[0] for term in terms).items())))
|
||||
if arguments.list:
|
||||
for group, severity, rule, key in read_terms():
|
||||
if arguments.list in ("all", group):
|
||||
print(f"{group:9} {SEVERITY_NAMES[severity]:7} {rule:9} {key}")
|
||||
if arguments.check:
|
||||
for text, shown in zip(arguments.check, check(arguments.check)):
|
||||
print(f"{text!r} -> {shown!r}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user