]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into TimePath/guide
authorTimePath <andrew.hardaker1995@gmail.com>
Wed, 14 Oct 2015 23:29:49 +0000 (10:29 +1100)
committerTimePath <andrew.hardaker1995@gmail.com>
Thu, 15 Oct 2015 00:00:08 +0000 (11:00 +1100)
# Conflicts:
# qcsrc/common/nades.qh
# qcsrc/common/vehicles/vehicle.qh
# qcsrc/menu/xonotic/dialog_multiplayer_media.qc

1  2 
qcsrc/common/mapinfo.qc
qcsrc/common/nades/all.inc
qcsrc/common/nades/all.qh
qcsrc/common/vehicles/vehicle.qh
qcsrc/menu/progs.inc
qcsrc/menu/xonotic/dialog_multiplayer_media.qc
qcsrc/menu/xonotic/guide/pages.qh
qcsrc/menu/xonotic/guide/tab.qc

Simple merge
index 0000000000000000000000000000000000000000,90326ede1f7bc9b083c88b32414b27c3634a997d..2c16907240e7f702438c923b5a8607d94fc05b43
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,58 +1,72 @@@
+ #define NADE_PROJECTILE(i, projectile, trail) do { \
+     this.m_projectile[i] = projectile; \
+     this.m_trail[i] = trail; \
+ } while (0)
+ REGISTER_NADE(NORMAL) {
+     this.m_color = '1 1 1';
++#ifndef MENUQC
+     NADE_PROJECTILE(0, PROJECTILE_NADE, EFFECT_Null);
+     NADE_PROJECTILE(1, PROJECTILE_NADE_BURN, EFFECT_Null);
++#endif
+ }
+ REGISTER_NADE(NAPALM) {
+     this.m_color = '2 0.5 0';
+     this.m_name = _("Napalm grenade");
+     this.m_icon = "nade_napalm";
++#ifndef MENUQC
+     NADE_PROJECTILE(0, PROJECTILE_NADE_NAPALM, EFFECT_TR_ROCKET);
+     NADE_PROJECTILE(1, PROJECTILE_NADE_NAPALM_BURN, EFFECT_SPIDERBOT_ROCKET_TRAIL);
++#endif
+ }
+ REGISTER_NADE(ICE) {
+     this.m_color = '0 0.5 2';
+     this.m_name = _("Ice grenade");
+     this.m_icon = "nade_ice";
++#ifndef MENUQC
+     NADE_PROJECTILE(0, PROJECTILE_NADE_ICE, EFFECT_TR_NEXUIZPLASMA);
+     NADE_PROJECTILE(1, PROJECTILE_NADE_ICE_BURN, EFFECT_RACER_ROCKET_TRAIL);
++#endif
+ }
+ REGISTER_NADE(TRANSLOCATE) {
+     this.m_color = '1 0 1';
+     this.m_name = _("Translocate grenade");
+     this.m_icon = "nade_translocate";
++#ifndef MENUQC
+     NADE_PROJECTILE(0, PROJECTILE_NADE_TRANSLOCATE, EFFECT_TR_CRYLINKPLASMA);
+     NADE_PROJECTILE(1, PROJECTILE_NADE_TRANSLOCATE, EFFECT_TR_CRYLINKPLASMA);
++#endif
+ }
+ REGISTER_NADE(SPAWN) {
+     this.m_color = '1 0.9 0';
+     this.m_name = _("Spawn grenade");
+     this.m_icon = "nade_spawn";
++#ifndef MENUQC
+     NADE_PROJECTILE(0, PROJECTILE_NADE_SPAWN, EFFECT_NADE_TRAIL_YELLOW);
+     NADE_PROJECTILE(1, PROJECTILE_NADE_SPAWN, EFFECT_NADE_TRAIL_YELLOW);
++#endif
+ }
+ REGISTER_NADE(HEAL) {
+     this.m_color = '1 0 0';
+     this.m_name = _("Heal grenade");
+     this.m_icon = "nade_heal";
++#ifndef MENUQC
+     NADE_PROJECTILE(0, PROJECTILE_NADE_HEAL, EFFECT_NADE_TRAIL_RED);
+     NADE_PROJECTILE(1, PROJECTILE_NADE_HEAL_BURN, EFFECT_NADE_TRAIL_BURN_RED);
++#endif
+ }
+ REGISTER_NADE(MONSTER) {
+     this.m_color = '0.25 0.75 0';
+     this.m_name = _("Monster grenade");
+     this.m_icon = "nade_monster";
++#ifndef MENUQC
+     NADE_PROJECTILE(0, PROJECTILE_NADE_MONSTER, EFFECT_NADE_TRAIL_RED);
+     NADE_PROJECTILE(1, PROJECTILE_NADE_MONSTER_BURN, EFFECT_NADE_TRAIL_BURN_RED);
++#endif
+ }
index 0000000000000000000000000000000000000000,ad51a1e55e3fd73a0a28f56db0f467685df7c472..42d66920a47878ffcb13e9843a1d575819ca9c84
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,82 +1,84 @@@
+ #ifndef NADES_ALL_H
+ #define NADES_ALL_H
+ #include "../teams.qh"
+ .float healer_lifetime;
+ .float healer_radius;
+ // use slots 70-100
+ const int PROJECTILE_NADE = 71;
+ const int PROJECTILE_NADE_BURN = 72;
+ const int PROJECTILE_NADE_NAPALM = 73;
+ const int PROJECTILE_NADE_NAPALM_BURN = 74;
+ const int PROJECTILE_NAPALM_FOUNTAIN = 75;
+ const int PROJECTILE_NADE_ICE = 76;
+ const int PROJECTILE_NADE_ICE_BURN = 77;
+ const int PROJECTILE_NADE_TRANSLOCATE = 78;
+ const int PROJECTILE_NADE_SPAWN = 79;
+ const int PROJECTILE_NADE_HEAL = 80;
+ const int PROJECTILE_NADE_HEAL_BURN = 81;
+ const int PROJECTILE_NADE_MONSTER = 82;
+ const int PROJECTILE_NADE_MONSTER_BURN = 83;
+ REGISTRY(Nades, BIT(3))
+ REGISTER_REGISTRY(RegisterNades)
+ #define REGISTER_NADE(id) REGISTER(RegisterNades, NADE_TYPE, Nades, id, m_id, NEW(Nade))
+ CLASS(Nade, Object)
+     ATTRIB(Nade, m_id, int, 0)
+     ATTRIB(Nade, m_color, vector, '0 0 0')
+     ATTRIB(Nade, m_name, string, _("Grenade"))
+     ATTRIB(Nade, m_icon, string, "nade_normal")
+     ATTRIBARRAY(Nade, m_projectile, int, 2)
+     ATTRIBARRAY(Nade, m_trail, entity, 2)
+     METHOD(Nade, display, void(entity this, void(string name, string icon) returns)) {
+         returns(this.m_name, sprintf("/gfx/hud/%s/%s", cvar_string("menu_skin"), this.m_icon));
+     }
+ ENDCLASS(Nade)
+ REGISTER_NADE(Null);
+ #ifdef SVQC
+ bool healer_send(entity this, entity to, int sf);
+ #endif
++#ifndef MENUQC
+ entity Nade_FromProjectile(float proj)
+ {
+     FOREACH(Nades, true, LAMBDA(
+         for (int j = 0; j < 2; j++)
+         {
+             if (it.m_projectile[j] == proj) return it;
+         }
+     ));
+     return NADE_TYPE_Null;
+ }
+ entity Nade_TrailEffect(int proj, int nade_team)
+ {
+     switch (proj)
+     {
+         case PROJECTILE_NADE:       return EFFECT_NADE_TRAIL(nade_team);
+         case PROJECTILE_NADE_BURN:  return EFFECT_NADE_TRAIL_BURN(nade_team);
+     }
+     FOREACH(Nades, true, LAMBDA(
+         for (int j = 0; j < 2; j++)
+         {
+             if (it.m_projectile[j] == proj)
+             {
+                 string trail = it.m_trail[j].eent_eff_name;
+                 if (trail) return it.m_trail[j];
+                 break;
+             }
+         }
+     ));
+     return EFFECT_Null;
+ }
++#endif
+ #include "all.inc"
+ #endif
index a9e247f602739c627cc60e5f46e0a38727bd6637,80787b4ae619eb8acda9abb09e101c698be384d3..f38490ca43e3751517f7671218f6acfeb267bd84
@@@ -36,9 -32,41 +32,45 @@@ CLASS(Vehicle, Object
      /** vehicle hitbox size */
      ATTRIB(Vehicle, maxs, vector, '0 0 0')
  
 +    METHOD(Vehicle, display, void(entity this, void(string name, string icon) returns)) {
 +        returns(this.vehicle_name, this.m_icon ? sprintf("/gfx/hud/%s/%s", cvar_string("menu_skin"), this.m_icon) : string_null);
 +    }
++
+     /** (BOTH) setup vehicle data */
+     METHOD(Vehicle, vr_setup, void(Vehicle)) { }
+     /** (SERVER) logic to run every frame */
+     METHOD(Vehicle, vr_think, void(Vehicle)) { }
+     /** (SERVER) called when vehicle dies */
+     METHOD(Vehicle, vr_death, void(Vehicle)) { }
+     /** (BOTH) precaches models/sounds used by this vehicle */
+     METHOD(Vehicle, vr_precache, void(Vehicle)) { }
+     /** (SERVER) called when a player enters this vehicle */
+     METHOD(Vehicle, vr_enter, void(Vehicle)) { }
+     /** (SERVER) called when the vehicle re-spawns */
+     METHOD(Vehicle, vr_spawn, void(Vehicle)) { }
+     /** (SERVER) called when a vehicle hits something */
+     METHOD(Vehicle, vr_impact, void(Vehicle)) { }
+     /** (CLIENT) logic to run every frame */
+     METHOD(Vehicle, vr_hud, void(Vehicle)) { }
  ENDCLASS(Vehicle)
  
+ // vehicle spawn flags (need them here for common registrations)
+ const int VHF_ISVEHICLE                       = 2; /// Indicates vehicle
+ const int VHF_HASSHIELD                       = 4; /// Vehicle has shileding
+ const int VHF_SHIELDREGEN             = 8; /// Vehicles shield regenerates
+ const int VHF_HEALTHREGEN             = 16; /// Vehicles health regenerates
+ const int VHF_ENERGYREGEN             = 32; /// Vehicles energy regenerates
+ const int VHF_DEATHEJECT              = 64; /// Vehicle ejects pilot upon fatal damage
+ const int VHF_MOVE_GROUND             = 128; /// Vehicle moves on gound
+ const int VHF_MOVE_HOVER              = 256; /// Vehicle hover close to gound
+ const int VHF_MOVE_FLY                        = 512; /// Vehicle is airborn
+ const int VHF_DMGSHAKE                        = 1024; /// Add random velocity each frame if health < 50%
+ const int VHF_DMGROLL                 = 2048; /// Add random angles each frame if health < 50%
+ const int VHF_DMGHEADROLL             = 4096; /// Add random head angles each frame if health < 50%
+ const int VHF_MULTISLOT                       = 8192; /// Vehicle has multiple player slots
+ const int VHF_PLAYERSLOT              = 16384; /// This ent is a player slot on a multi-person vehicle
+ // fields:
+ .entity tur_head;
  #endif
Simple merge
index f37b36a677b7424c68cd9b1d3c6485c8ffaa92e3,abecd1ed5bd18e27576748b3ebc0f6dd0c2287e8..40c434aff79ade4d9645671a4bf344d4bac6c0a7
@@@ -25,9 -25,7 +25,8 @@@ void XonoticMediaTab_fill(entity me
        mc = makeXonoticTabController(me.rows - 2);
  
        me.gotoRC(me, 0.5, 0);
-               me.TD(me, 1, 1, e = mc.makeTabButton_T(mc, _("Demos"), makeXonoticDemoBrowserTab(),
-                       _("Browse and view demos")));
 +              me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Guide"), NEW(XonoticGuideTab)));
+               me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Demos"), makeXonoticDemoBrowserTab()));
                me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Screenshots"), makeXonoticScreenshotBrowserTab()));
                me.TD(me, 1, 1, e = mc.makeTabButton(mc, _("Music Player"), makeXonoticMusicPlayerTab()));
  
index 86150af5ffc062bd0fde24491e048b6bf41a34d0,0000000000000000000000000000000000000000..8654b2529f3214df3a246daa262e8c92e93d2fc5
mode 100644,000000..100644
--- /dev/null
@@@ -1,32 -1,0 +1,29 @@@
- void RegisterGuidePages();
- const int GUIDE_PAGES_MAX = 16;
- entity GUIDE_PAGES[GUIDE_PAGES_MAX], GUIDE_PAGES_first, GUIDE_PAGES_last;
- int GUIDE_PAGES_COUNT;
 +#ifndef GUIDE_PAGES_H
 +#define GUIDE_PAGES_H
 +
 +CLASS(GuidePage, Object);
 +    ATTRIB(GuidePage, m_id, int, 0);
 +    ATTRIB(GuidePage, m_title, string, "");
 +    ATTRIB(GuidePage, m_description, string, "");
 +    METHOD(GuidePage, describe, string(GuidePage this)) {
 +        return this.m_description;
 +    }
 +    METHOD(GuidePage, display, void(GuidePage this, void(string name, string icon) returns)) {
 +        returns(this.m_title, "nopreview_map");
 +    }
 +    CONSTRUCTOR(GuidePage, string _title) {
 +        CONSTRUCT(GuidePage);
 +        this.m_title = _title;
 +    }
 +ENDCLASS(GuidePage)
 +
-     REGISTER(RegisterGuidePages, GUIDE_PAGE, GUIDE_PAGES, GUIDE_PAGES_COUNT, id, m_id, NEW(GuidePage, title))
++REGISTRY(GuidePages, 16)
 +#define REGISTER_GUIDE_PAGE(id, title) \
++    REGISTER(RegisterGuidePages, GUIDE_PAGE, GuidePages, id, m_id, NEW(GuidePage, title))
 +REGISTER_REGISTRY(RegisterGuidePages)
 +
 +REGISTER_GUIDE_PAGE(0, _("Intro")) { this.m_description = _(
 +"Welcome to xonotic"
 +);}
 +
 +#endif
index 209abc80fe431cf6f7350756592725e05b1e0b4d,0000000000000000000000000000000000000000..8d4b8867691e98f6b31fe83a738ce17ad5651492
mode 100644,000000..100644
--- /dev/null
@@@ -1,230 -1,0 +1,230 @@@
- #define REGISTRY_SOURCE(id, arr, count) \
 +#ifndef DIALOG_MEDIA_GUIDE_H
 +#define DIALOG_MEDIA_GUIDE_H
 +#include "../datasource.qc"
 +
 +#define TOPICS(X) \
 +    X(NEW(FreetextSource),  _("Guide"),     "gametype_tdm") \
 +    X(NEW(GametypeSource),  _("Gametypes"), "gametype_dm") \
 +    X(NEW(WeaponSource),    _("Weapons"),   "gametype_ka") \
 +    X(NEW(ItemSource),      _("Items"),     "gametype_kh") \
 +    X(NEW(BuffSource),      _("Buffs"),     "gametype_dom") \
 +    X(NEW(NadeSource),      _("Nades"),     "gametype_ft") \
 +    X(NEW(MonsterSource),   _("Monsters"),  "gametype_lms") \
 +    X(NEW(VehicleSource),   _("Vehicles"),  "gametype_rc") \
 +    X(NEW(TurretSource),    _("Turrets"),   "gametype_as") \
 +    X(NEW(MutatorSource),   _("Mutators"),  "gametype_nb") \
 +    X(NEW(MapSource),       _("Maps"),      "gametype_ctf") \
 +    if (cvar("developer")) X(NEW(DebugSource), _("Debug"), "gametype_ons") \
 +    /**/
 +CLASS(TopicSource, DataSource)
 +    METHOD(TopicSource, getEntry, entity(TopicSource this, int i, void(string, string) returns)) {
 +        int idx = 0;
 +        #define TOPIC(src, name, icon) if (idx++ == i) { if (returns) returns(name, icon); return DataSource_true; }
 +        TOPICS(TOPIC);
 +        #undef TOPIC
 +        if (returns) returns("undefined", "undefined");
 +        return DataSource_false;
 +    }
 +    METHOD(TopicSource, reload, int(TopicSource this, string filter)) {
 +        int n = 0;
 +        #define TOPIC(src, name, icon) n++;
 +        TOPICS(TOPIC);
 +        #undef TOPIC
 +        return n;
 +    }
 +ENDCLASS(TopicSource)
 +
 +CLASS(DebugSource, DataSource)
 +    .entity nextdebug;
 +    entity find_debug() {
 +        entity head = NULL, tail = NULL;
 +        for (entity it = NULL; (it = nextent(it)); ) {
 +            if (!it.instanceOfObject) continue;
 +            if (it.instanceOfItem) continue;
 +            if (it.instanceOfAnimHost) continue;
 +            if (it.instanceOfDataSource) continue;
 +            if (it.classname == "Object") continue;
 +            if (it.classname == "vtbl") continue;
 +            if (!tail) {
 +                tail = head = it;
 +            } else {
 +                tail.nextdebug = it;
 +                tail = it;
 +            }
 +        }
 +        return head;
 +    }
 +    string DebugSource_activeFilter = "";
 +    METHOD(DebugSource, getEntry, entity(DebugSource this, int i, void(string, string) returns)) {
 +        int idx = 0;
 +        entity e;
 +        for (e = find_debug(); e; e = e.nextdebug) {
 +            if (strstrofs(sprintf("entity %i", e), DebugSource_activeFilter, 0) < 0) continue;
 +            if (idx++ == i) break;
 +        }
 +        if (returns) e.display(e, returns);
 +        return e;
 +    }
 +    METHOD(DebugSource, reload, int(DebugSource this, string filter)) {
 +        DebugSource_activeFilter = filter;
 +        int idx = 0;
 +        entity e;
 +        for (e = find_debug(); e; e = e.nextdebug) {
 +            if (strstrofs(sprintf("entity %i", e), DebugSource_activeFilter, 0) < 0) continue;
 +            idx++;
 +        }
 +        return idx;
 +    }
 +ENDCLASS(DebugSource)
 +
-     METHOD(id, reload, int(id this, string filter)) { return count; } \
++#define REGISTRY_SOURCE(id, arr) \
 +CLASS(id, DataSource) \
 +    METHOD(id, getEntry, entity(id this, int i, void(string, string) returns)) { \
 +        entity e = arr[i]; \
 +        if (returns) e.display(e, returns); \
 +        return e; \
 +    } \
- REGISTRY_SOURCE(FreetextSource, GUIDE_PAGES, GUIDE_PAGES_COUNT)
++    METHOD(id, reload, int(id this, string filter)) { return arr##_COUNT; } \
 +ENDCLASS(id)
 +
 +#include "pages.qh"
- REGISTRY_SOURCE(GametypeSource, MAPINFO_TYPES, MAPINFO_TYPE_COUNT)
++REGISTRY_SOURCE(FreetextSource, GuidePages)
 +
 +#include "../../../common/mapinfo.qh"
- REGISTRY_SOURCE(ItemSource, ITEMS, ITEM_COUNT)
++REGISTRY_SOURCE(GametypeSource, Gametypes)
 +
 +#include "../../../common/items/all.qh"
- #include "../../../common/buffs.qh"
- REGISTRY_SOURCE(BuffSource, BUFFS, BUFFS_COUNT)
++REGISTRY_SOURCE(ItemSource, Items)
 +
- #include "../../../common/nades.qh"
- REGISTRY_SOURCE(NadeSource, NADES, NADES_COUNT)
++#include "../../../common/buffs/all.qh"
++REGISTRY_SOURCE(BuffSource, Buffs)
 +
- REGISTRY_SOURCE(WeaponSource, weapon_info, WEP_COUNT)
++#include "../../../common/nades/all.qh"
++REGISTRY_SOURCE(NadeSource, Nades)
 +
 +#include "../../../common/weapons/all.qh"
- REGISTRY_SOURCE(MonsterSource, monster_info, MON_COUNT)
++REGISTRY_SOURCE(WeaponSource, Weapons)
 +
 +#include "../../../common/monsters/all.qh"
- REGISTRY_SOURCE(VehicleSource, vehicle_info, VEH_COUNT)
++REGISTRY_SOURCE(MonsterSource, Monsters)
 +
 +#include "../../../common/vehicles/all.qh"
- REGISTRY_SOURCE(TurretSource, turret_info, TUR_COUNT)
++REGISTRY_SOURCE(VehicleSource, Vehicles)
 +
 +#include "../../../common/turrets/all.qh"
- REGISTRY_SOURCE(MutatorSource, MUTATORS, MUTATORS_COUNT)
++REGISTRY_SOURCE(TurretSource, Turrets)
 +
 +#include "../../../common/mutators/base.qh"
++REGISTRY_SOURCE(MutatorSource, MUTATORS)
 +
 +CLASS(MapSource, DataSource)
 +    METHOD(MapSource, getEntry, entity(MapSource this, int i, void(string, string) returns)) {
 +        if (!MapInfo_Get_ByID(i)) return DataSource_false;
 +        string path = strcat("/maps/", MapInfo_Map_bspname);
 +        string img = draw_PictureSize(path) ? path : "nopreview_map";
 +        if (returns) returns(MapInfo_Map_titlestring, img);
 +        MapInfo_ClearTemps();
 +        return DataSource_true;
 +    }
 +    METHOD(MapSource, indexOf, int(MapSource this, string s)) {
 +        MapInfo_FindName(s);
 +        return MapInfo_FindName_firstResult;
 +    }
 +    METHOD(MapSource, reload, int(MapSource this, string s)) {
 +        MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, 0, 0, 0);
 +        if (s) MapInfo_FilterString(s);
 +        return MapInfo_count;
 +    }
 +    METHOD(MapSource, destroy, void(MapSource this)) { MapInfo_Shutdown(); }
 +ENDCLASS(MapSource)
 +
 +#include "topics.qc"
 +#include "entries.qc"
 +#include "description.qc"
 +#include "../tab.qc"
 +CLASS(XonoticGuideTab, XonoticTab)
 +    ATTRIB(XonoticGuideTab, rows, float, 21)
 +    ATTRIB(XonoticGuideTab, columns, float, 6)
 +      ATTRIB(XonoticGuideTab, intendedWidth, float, 1)
 +    METHOD(XonoticGuideTab, fill, void(entity));
 +    METHOD(XonoticGuideTab, topicChangeNotify, void(entity, entity));
 +    METHOD(XonoticGuideTab, entryChangeNotify, void(entity, entity));
 +
 +    ATTRIB(XonoticGuideTab, topicList, entity, NEW(XonoticTopicList, NEW(TopicSource)))
 +    ATTRIB(XonoticGuideTab, entryList, entity, NEW(XonoticEntryList, NULL))
 +    ATTRIB(XonoticGuideTab, descriptionPane, entity, NEW(XonoticGuideDescription))
 +
 +    INIT(XonoticGuideTab) {
 +        this.configureDialog(this);
 +    }
 +ENDCLASS(XonoticGuideTab)
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +
 +void XonoticGuideTab_fill(entity this)
 +{
 +    entity topics = this.topicList;
 +        topics.onChange = XonoticGuideTab_topicChangeNotify;
 +        topics.onChangeEntity = this;
 +    entity entries = this.entryList;
 +        entries.onChange = XonoticGuideTab_entryChangeNotify;
 +        entries.onChangeEntity = this;
 +    entity filter = entries.stringFilterBox = makeXonoticInputBox(false, string_null);
 +        filter.keyDown = MapList_StringFilterBox_keyDown;
 +        filter.onChange = MapList_StringFilterBox_Change;
 +        filter.onChangeEntity = entries;
 +    entries.controlledTextbox = filter;
 +    entity description = this.descriptionPane;
 +
 +    int
 +    col = 0, width = 1.5;
 +    this.gotoRC(this, 0, col);
 +        this.TD(this, 1, width, makeXonoticHeaderLabel(_("Topic")));
 +    this.TR(this);
 +        this.TD(this, this.rows - 1, width, topics);
 +
 +    col += width, width = 2;
 +    this.gotoRC(this, 0, col); this.setFirstColumn(this, this.currentColumn);
 +        this.TD(this, 1, width, makeXonoticHeaderLabel(_("Entry")));
 +    this.TR(this);
 +        this.TD(this, this.rows - 1 - 1, width, entries);
 +    this.gotoRC(this, this.rows - 1, col);
 +        this.TD(this, 1, 0.3, makeXonoticTextLabel(0, _("Filter:")));
 +        this.TD(this, 1, width - 0.3, filter);
 +
 +    col += width, width = 2.5;
 +    this.gotoRC(this, 0, col); this.setFirstColumn(this, this.currentColumn);
 +        this.TD(this, 1, width, makeXonoticHeaderLabel(_("Description")));
 +    this.TR(this);
 +        this.TD(this, this.rows - 1, width, description);
 +
 +    this.topicChangeNotify(topics, this);
 +}
 +
 +void XonoticGuideTab_topicChangeNotify(entity, entity this)
 +{
 +    entity topics = this.topicList;
 +    entity entries = this.entryList;
 +    int i = topics.selectedItem;
 +    int idx = 0;
 +    entity found = NULL;
 +    #define TOPIC(src, name, icon) if (idx++ == i) { static entity e; if (!e) e = src; found = e; break; }
 +    do { TOPICS(TOPIC); } while (0);
 +    #undef TOPIC
 +    entries.source = found;
 +    entries.refilter(entries);
 +    entries.setSelected(entries, 0);
 +}
 +
 +void XonoticGuideTab_entryChangeNotify(entity, entity this)
 +{
 +    entity desc = this.descriptionPane;
 +    entity entries = this.entryList;
 +    entity e = entries.source.getEntry(entries.source, entries.selectedItem, func_null);
 +    string s = e.describe(e);
 +    if (cvar("developer")) { s = sprintf("entity %i\n%s", e, s); }
 +    desc.setDescription(desc, s);
 +}
 +
 +#endif