#include #include #include "gm-mcp-vmoo-userlist.h" #include "gm-mcp-userlist-view.h" #include "gm-iuserlist.h" #include "gm-mcp-session.h" #include "gm-mcp.h" #include "gm-support.h" #include "gm-marshal.h" #include "gm-debug.h" #include "gm-world.h" #include "list.h" #define GM_MCP_VMOO_USERLIST_GET_PRIVATE(object)( \ G_TYPE_INSTANCE_GET_PRIVATE((object), \ GM_TYPE_MCP_VMOO_USERLIST, GmMcpVmooUserlistPrivate)) static const GmKeyValuePair icon_mapping[] = { {"newbie", "userlist/newbie.svg"}, {"inhabitant", "userlist/inhabitant.svg"}, {"inhabitant+", "userlist/inhabitantplus.svg"}, {"schooled", "userlist/schooled.svg"}, {"key", "userlist/key.svg"}, {"star", "userlist/star.svg"}, {"wizard", "userlist/wizard.svg"}, {NULL, NULL} }; typedef enum _UserState { U_NORMAL, U_AWAY, U_IDLE, U_IDLEAWAY } UserState; typedef struct _UserInfo { gint nr; guint icon; gchar *name; UserState state; } UserInfo; struct _GmMcpVmooUserlistPrivate { gint you; GList *fields; GList *icons; GList *users; GList *menu; gboolean initializing; }; /* Signals */ enum { PLAYER_ADDED, PLAYER_REMOVED, NAME_CHANGED, STATE_CHANGED, RANK_CHANGED, NUM_SIGNALS }; static void gm_mcp_vmoo_userlist_iface_init(GmIUserlistInterface *iface); static guint gm_mcp_vmoo_userlist_signals[NUM_SIGNALS] = {0}; G_DEFINE_TYPE_EXTENDED(GmMcpVmooUserlist, gm_mcp_vmoo_userlist, \ GM_TYPE_MCP_PACKAGE, 0, G_IMPLEMENT_INTERFACE(GM_TYPE_IUSERLIST, \ gm_mcp_vmoo_userlist_iface_init)) void gm_mcp_vmoo_userlist_handle_simple(GmMcpPackage *package, gchar *suffix, GList *fields); gboolean gm_mcp_vmoo_userlist_handle_multi(GmMcpPackage *package, gchar const *data_tag, gchar const *key, gchar const *value, GList *all_values); void gm_mcp_vmoo_userlist_create_view(GmMcpPackage *package, GObject *parent); GList *gm_mcp_vmoo_userlist_get_menu(GmIUserlist *userlist, gint id); gchar const *gm_mcp_vmoo_userlist_get_name(GmIUserlist *userlist, gint id); static void gm_mcp_vmoo_userlist_iface_init( GmIUserlistInterface *iface) { iface->get_menu = gm_mcp_vmoo_userlist_get_menu; iface->get_name = gm_mcp_vmoo_userlist_get_name; } void gm_mcp_vmoo_userlist_remove_users(GmMcpVmooUserlist *package) { GList *users = g_list_copy(package->priv->users); GList *field; UserInfo *info; for (field = users; field; field = field->next) { info = (UserInfo *)(field->data); package->priv->users = g_list_remove(package->priv->users, field->data); g_free(info->name); g_free(info); } g_list_free(users); } static void gm_mcp_vmoo_userlist_free_menu(GmMcpVmooUserlist *obj) { GList *field; GmKeyValuePair *pair; for (field = obj->priv->menu; field; field = field->next) { pair = (GmKeyValuePair *)(field->data); g_free(pair->key); g_free(pair->value); g_free(pair); } obj->priv->menu = NULL; } static void gm_mcp_vmoo_userlist_finalize(GObject *object) { GmMcpVmooUserlist *obj = GM_MCP_VMOO_USERLIST(object); GList *field; for (field = obj->priv->fields; field; field = field->next) { g_free(field->data); } g_list_free(obj->priv->fields); for (field = obj->priv->icons; field; field = field->next) { g_free(field->data); } g_list_free(obj->priv->icons); gm_mcp_vmoo_userlist_free_menu(obj); gm_mcp_vmoo_userlist_remove_users(obj); g_list_free(obj->priv->users); G_OBJECT_CLASS(gm_mcp_vmoo_userlist_parent_class)->finalize(object); } static void gm_mcp_vmoo_userlist_class_init(GmMcpVmooUserlistClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); GmMcpPackageClass *pklass = GM_MCP_PACKAGE_CLASS(klass); object_class->finalize = gm_mcp_vmoo_userlist_finalize; gm_mcp_vmoo_userlist_signals[PLAYER_ADDED] = g_signal_new("player_added", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmMcpVmooUserlistClass, player_added), NULL, NULL, gm_marshal_VOID__INT_STRING_STRING, G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING); gm_mcp_vmoo_userlist_signals[PLAYER_REMOVED] = g_signal_new("player_removed", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmMcpVmooUserlistClass, player_removed), NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); gm_mcp_vmoo_userlist_signals[NAME_CHANGED] = g_signal_new("name_changed", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmMcpVmooUserlistClass, name_changed), NULL, NULL, gm_marshal_VOID__INT_STRING, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_STRING); gm_mcp_vmoo_userlist_signals[STATE_CHANGED] = g_signal_new("state_changed", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmMcpVmooUserlistClass, state_changed), NULL, NULL, gm_marshal_VOID__INT_STRING_STRING, G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING); gm_mcp_vmoo_userlist_signals[RANK_CHANGED] = g_signal_new("rank_changed", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmMcpVmooUserlistClass, rank_changed), NULL, NULL, gm_marshal_VOID__INT_STRING_STRING, G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING); pklass->name = "dns-com-vmoo-userlist"; pklass->handle_simple = &gm_mcp_vmoo_userlist_handle_simple; pklass->handle_multi = &gm_mcp_vmoo_userlist_handle_multi; pklass->create_view = &gm_mcp_vmoo_userlist_create_view; g_type_class_add_private(object_class, sizeof(GmMcpVmooUserlistPrivate)); } static void gm_mcp_vmoo_userlist_init(GmMcpVmooUserlist *obj) { obj->priv = GM_MCP_VMOO_USERLIST_GET_PRIVATE(obj); obj->priv->you = 0; obj->priv->fields = NULL; obj->priv->icons = NULL; obj->priv->users = NULL; } GmMcpVmooUserlist * gm_mcp_vmoo_userlist_new() { GmMcpVmooUserlist *obj = GM_MCP_VMOO_USERLIST(g_object_new( \ GM_TYPE_MCP_VMOO_USERLIST, NULL)); return obj; } #define MAX_MATCHES 10 void gm_mcp_vmoo_userlist_process_triggers(GmMcpVmooUserlist *package, gchar const *username, GmTriggerConditionType condition) { GmWorld *world; GmTriggers *triggers; GList const *item; GmTrigger *trigger; gint num; regmatch_t matches[MAX_MATCHES]; if (package->priv->initializing) { return; } world = GM_MCP_SESSION_WORLD(GM_MCP_PACKAGE_SESSION(package)); triggers = gm_world_triggers(world); for (item = gm_triggers_list(triggers); item; item = item->next) { trigger = (GmTrigger *)(item->data); if (trigger->event == TT_USERS) { if ((num = gm_trigger_match_user(trigger, username, condition, matches, MAX_MATCHES))) { gm_world_apply_trigger(world, trigger, username, matches, num); } } } } void gm_mcp_vmoo_userlist_handle_fields(GmMcpVmooUserlist *package, MOOVar *list) { MOOVar *field; for (field = list->list; field; field = field->next) { if (field->type == STRING) { package->priv->fields = g_list_append(package->priv->fields, g_strdup(field->s)); } } } gchar const * gm_mcp_vmoo_userlist_get_icon(GmMcpVmooUserlist *package, UserInfo *ui) { gchar *field; guint index = ui->icon; GmKeyValuePair const *pair; if (index < 1 || index > g_list_length(package->priv->icons)) { return NULL; } else { switch (ui->state) { case U_NORMAL: field = (gchar *)(g_list_nth_data(package->priv->icons, index - 1)); pair = icon_mapping; while (!(pair->key == NULL && pair->value == NULL)) { if (strcasecmp(pair->key, field) == 0) { return pair->value; } ++pair; } break; case U_IDLE: return "userlist/idle.svg"; break; case U_AWAY: return "userlist/away.svg"; break; case U_IDLEAWAY: return "userlist/idleaway.svg"; break; default: break; } } return NULL; } void gm_mcp_vmoo_userlist_handle_icons(GmMcpVmooUserlist *package, MOOVar *list) { MOOVar *field; for (field = list->list; field; field = field->next) { if (field->type == STRING) { package->priv->icons = g_list_append(package->priv->icons, g_strdup(field->s)); } } } void gm_mcp_vmoo_userlist_handle_menu(GmMcpVmooUserlist *package, MOOVar *list) { MOOVar *field; GmKeyValuePair *pair; for (field = list->list; field; field = field->next) { pair = g_new0(GmKeyValuePair, 1); if (field->type == LIST && field->i == 2) { pair->key = g_strdup(field->list->s); pair->value = g_strdup(field->list->next->s); package->priv->menu = g_list_append(package->priv->menu, pair); } else if (field->type != LIST) { package->priv->menu = g_list_append(package->priv->menu, pair); } else { g_free(pair); } } } gchar * gm_mcp_vmoo_userlist_get_string(GmMcpVmooUserlist *package, MOOVar *v, MOOType type, gchar *cmp) { GList *fields = package->priv->fields; while (fields && strcasecmp(((gchar *) (fields->data)), cmp) != 0) { fields = fields->next; if (v) { v = v->next; } } if (v && v->type == type) { return g_strdup(v->s); } return NULL; } gint gm_mcp_vmoo_userlist_get_int(GmMcpVmooUserlist *package, MOOVar *v, MOOType type, gchar *cmp) { GList *fields = package->priv->fields; while (fields && strcasecmp(((gchar *) (fields->data)), cmp) != 0) { fields = fields->next; if (v) { v = v->next; } } if (v && v->type == type) { return v->i; } return 0; } UserInfo * gm_mcp_vmoo_userlist_find_user(GmMcpVmooUserlist *package, gint nr) { UserInfo *result; GList *users; for (users = package->priv->users; users; users = users->next) { result = (UserInfo *) (users->data); if (result->nr == nr) { return result; } } return NULL; } gchar * gm_mcp_vmoo_userlist_menu_item_subst(GmMcpVmooUserlist *package, gchar *str, UserInfo *info) { GString *result; gchar *ptr = str, *subst, *tmp; gunichar ch, cnum; gint num; gboolean substituted; if (str == NULL) { return NULL; } result = g_string_sized_new(strlen(str)); while ((ch = g_utf8_get_char(ptr)) != '\0') { substituted = FALSE; if (ch == '&') { result = g_string_append_c(result, '_'); substituted = TRUE; } else if (ch == '$') { subst = g_utf8_next_char(ptr); if (g_utf8_get_char(subst) == '(') { subst = g_utf8_next_char(subst); num = 0; while (g_unichar_isdigit((cnum = g_utf8_get_char(subst)))) { num = (num * 10) + g_unichar_digit_value(cnum); subst = g_utf8_next_char(subst); } if (g_utf8_get_char(subst) == ')') { switch (num) { case 1: tmp = g_strdup_printf("#%d", info->nr); result = g_string_append(result, tmp); g_free(tmp); break; case 2: for (tmp = info->name; *tmp != '\0'; tmp = g_utf8_next_char(tmp)) { ch = g_utf8_get_char(tmp); if (ch == '_') { result = g_string_append_c(result, '_'); } result = g_string_append_unichar(result, ch); } break; default: break; } ptr = subst; substituted = TRUE; } } } if (!substituted) { result = g_string_append_unichar(result, ch); } ptr = g_utf8_next_char(ptr); } ptr = result->str; g_string_free(result, FALSE); return ptr; } GmKeyValuePair * gm_mcp_vmoo_userlist_get_menu_item(GmMcpVmooUserlist *package, GmKeyValuePair *menuitem, UserInfo *info) { GmKeyValuePair *result = g_new0(GmKeyValuePair, 1); if (!menuitem) { return result; } result->key = gm_mcp_vmoo_userlist_menu_item_subst(package, menuitem->key, info); result->value = gm_mcp_vmoo_userlist_menu_item_subst(package, menuitem->value, info); return result; } GList * gm_mcp_vmoo_userlist_get_menu(GmIUserlist *userlist, gint id) { GmMcpVmooUserlist *package = (GmMcpVmooUserlist *)(userlist); UserInfo *info = gm_mcp_vmoo_userlist_find_user(package, id); GList *item, *menu = NULL; GmKeyValuePair *pair; if (!info) { return NULL; } for (item = package->priv->menu; item; item = item->next) { pair = (GmKeyValuePair *)(item->data); menu = g_list_append(menu, gm_mcp_vmoo_userlist_get_menu_item(package, pair, info)); } return menu; } gchar const * gm_mcp_vmoo_userlist_get_name(GmIUserlist *userlist, gint id) { GmMcpVmooUserlist *package = (GmMcpVmooUserlist *)(userlist); UserInfo *info = gm_mcp_vmoo_userlist_find_user(package, id); return info->name; } void gm_mcp_vmoo_userlist_remove_user(GmMcpVmooUserlist *package, gint nr) { GList *elem; UserInfo *ui; for (elem = package->priv->users; elem; elem = elem->next) { ui = (UserInfo *) (elem->data); if (ui->nr == nr) { gm_mcp_vmoo_userlist_process_triggers(package, ui->name, TCT_USER_OFFLINE); package->priv->users = g_list_remove(package->priv->users, ui); break; } } g_signal_emit(package, gm_mcp_vmoo_userlist_signals[PLAYER_REMOVED], 0, nr); } gchar * gm_mcp_vmoo_userlist_sort_string(GmMcpVmooUserlist *package, UserInfo *ui) { guint sortid = 0; switch (ui->state) { case U_NORMAL: sortid = g_list_length(package->priv->icons) - ui->icon; break; case U_AWAY: sortid = g_list_length(package->priv->icons) + 1; break; case U_IDLE: sortid = g_list_length(package->priv->icons) + 2; break; case U_IDLEAWAY: sortid = g_list_length(package->priv->icons) + 3; break; } return g_strdup_printf("%.2d%s", sortid, ui->name); } void gm_mcp_vmoo_userlist_handle_user_update(GmMcpVmooUserlist *package, MOOVar *v) { gint nr = gm_mcp_vmoo_userlist_get_int(package, v, OBJECT, "object"); gchar *name = gm_mcp_vmoo_userlist_get_string(package, v, STRING, "name"); gint icon = gm_mcp_vmoo_userlist_get_int(package, v, INT, "icon"); gchar *sort; UserInfo *ui; if ((ui = gm_mcp_vmoo_userlist_find_user(package, nr)) == NULL) { // Add the user ui = g_new(UserInfo, 1); ui->name = name; ui->state = U_NORMAL; ui->icon = icon; ui->nr = nr; // package->priv->users = g_list_append(package->priv->users, ui); sort = gm_mcp_vmoo_userlist_sort_string(package, ui); g_signal_emit(package, gm_mcp_vmoo_userlist_signals[PLAYER_ADDED], 0, nr, gm_mcp_vmoo_userlist_get_icon(package, ui), sort); g_free(sort); gm_mcp_vmoo_userlist_process_triggers(package, ui->name, TCT_USER_ONLINE); } else { if (ui->name || strcmp(ui->name, name) != 0) { g_free(ui->name); ui->name = name; sort = gm_mcp_vmoo_userlist_sort_string(package, ui); g_signal_emit(package, gm_mcp_vmoo_userlist_signals[NAME_CHANGED], 0, nr, sort); g_free(sort); } else { g_free(name); } if (ui->icon != (guint)icon) { ui->icon = (guint)icon; sort = gm_mcp_vmoo_userlist_sort_string(package, ui); g_signal_emit(package, gm_mcp_vmoo_userlist_signals[RANK_CHANGED], 0, nr, gm_mcp_vmoo_userlist_get_icon(package, ui), sort); g_free(sort); } } } void gm_mcp_vmoo_userlist_handle_set(GmMcpVmooUserlist *package, MOOVar *v) { MOOVar *ui; if (v->type != LIST) { return; } package->priv->initializing = TRUE; gm_mcp_vmoo_userlist_remove_users(package); for (ui = v->list; ui; ui = ui->next) { if (ui->type != LIST) { return; } gm_mcp_vmoo_userlist_handle_user_update(package, ui->list); } package->priv->initializing = FALSE; } void gm_mcp_vmoo_userlist_handle_add(GmMcpVmooUserlist *package, MOOVar *v) { if (v->type != LIST) { return; } gm_mcp_vmoo_userlist_handle_user_update(package, v->list); } void gm_mcp_vmoo_userlist_handle_remove(GmMcpVmooUserlist *package, MOOVar *v) { MOOVar *nr; if (v->type != LIST) { return; } for (nr = v->list; nr; nr = nr->next) { if (nr->type == OBJECT) { gm_mcp_vmoo_userlist_remove_user(package, nr->i); } } } void gm_mcp_vmoo_userlist_handle_update(GmMcpVmooUserlist *package, MOOVar *v) { if (v->type != LIST) { return; } gm_mcp_vmoo_userlist_handle_user_update(package, v->list); } void gm_mcp_vmoo_userlist_handle_state(GmMcpVmooUserlist *package, MOOVar *v, UserState state, gboolean onOff) { MOOVar *nr; UserInfo *u; gchar *sort; GmTriggerConditionType condition = 0; if (v->type != LIST) { return; } for (nr = v->list; nr; nr = nr->next) { if (nr->type == OBJECT) { if ((u = gm_mcp_vmoo_userlist_find_user(package, nr->i))) { switch (state) { case U_IDLE: if (u->state == U_IDLEAWAY && !onOff) { u->state = U_AWAY; } else if (u->state == U_AWAY && onOff) { u->state = U_IDLEAWAY; } else if (onOff) { u->state = U_IDLE; } else if (u->state == U_IDLE) { u->state = U_NORMAL; } if (onOff) { condition = TCT_USER_IDLE; } else { condition = TCT_USER_IDLE_OFF; } break; case U_AWAY: if (u->state == U_IDLEAWAY && !onOff) { u->state = U_IDLE; } else if (u->state == U_IDLE && onOff) { u->state = U_IDLEAWAY; } else if (onOff) { u->state = U_AWAY; } else if (u->state == U_AWAY) { u->state = U_NORMAL; } if (onOff) { condition = TCT_USER_AWAY; } else { condition = TCT_USER_AWAY_OFF; } break; default: break; } sort = gm_mcp_vmoo_userlist_sort_string(package, u); g_signal_emit(package, gm_mcp_vmoo_userlist_signals[STATE_CHANGED], 0, nr->i, gm_mcp_vmoo_userlist_get_icon(package, u), sort); if (condition != 0) { gm_mcp_vmoo_userlist_process_triggers(package, u->name, condition); } g_free(sort); } else { gm_debug_msg(DEBUG_MCP, "User %d does not exist!", nr->i); } } else { gm_debug_msg(DEBUG_MCP, "Nr is not an object: %d", nr->type); } } } void gm_mcp_vmoo_userlist_handle_simple(GmMcpPackage *package, gchar *suffix, GList *fields) { MOOVar *v = NULL; GmMcpVmooUserlist *userlist = GM_MCP_VMOO_USERLIST(package); gchar const *value; if (suffix) { if (strcmp(suffix, "you") == 0) { v = MOOVar_parse(gm_mcp_find_value(fields, "nr")); if (v->type == OBJECT) { userlist->priv->you = v->i; } else { gm_debug_msg(DEBUG_MCP, "GmMcpVmooUserlist.HandleSimple: " "you is not an object!"); } } else if (strcmp(suffix, "menu") == 0) { value = gm_mcp_find_value(fields, "menu"); v = MOOVar_parse(value); if (!v || v->type != LIST) { gm_debug_msg(DEBUG_MCP, "GmMcpVmooUserlist.HandleMulti: " "invalid value: %s", value); } else { gm_mcp_vmoo_userlist_free_menu(userlist); gm_mcp_vmoo_userlist_handle_menu(userlist, v); } } } if (v) { MOOVar_free(v); } } gboolean gm_mcp_vmoo_userlist_handle_multi(GmMcpPackage *package, gchar const *data_tag, gchar const *key, gchar const *value, GList *all_values) { MOOVar *v = NULL; GmMcpVmooUserlist *userlist = GM_MCP_VMOO_USERLIST(package); if (key) { if (strcmp(key, "fields") == 0) { v = MOOVar_parse(value); if (!v || v->type != LIST) { gm_debug_msg(DEBUG_MCP, "GmMcpVmooUserlist.HandleMulti: " "invalid value: %s", value); } else { gm_mcp_vmoo_userlist_handle_fields(userlist, v); } } else if (strcmp(key, "icons") == 0) { v = MOOVar_parse(value); if (!v || v->type != LIST) { gm_debug_msg(DEBUG_MCP, "GmMcpVmooUserlist.HandleMulti: " "invalid value: %s", value); } else { gm_mcp_vmoo_userlist_handle_icons(userlist, v); } } else if (strcmp(key, "d") == 0) { v = MOOVar_parse(value + 1); if (!v) { gm_debug_msg(DEBUG_MCP, "GmMcpVmooUserlist.HandleMulti: " "invalid value: %s", value + 1); } else { switch (*value) { case '=': gm_mcp_vmoo_userlist_handle_set(userlist, v); break; case '+': gm_mcp_vmoo_userlist_handle_add(userlist, v); break; case '-': gm_mcp_vmoo_userlist_handle_remove(userlist, v); break; case '*': gm_mcp_vmoo_userlist_handle_update(userlist, v); break; case '<': gm_mcp_vmoo_userlist_handle_state(userlist, v, U_IDLE, TRUE); break; case '>': gm_mcp_vmoo_userlist_handle_state(userlist, v, U_IDLE, FALSE); break; case '[': gm_mcp_vmoo_userlist_handle_state(userlist, v, U_AWAY, TRUE); break; case ']': gm_mcp_vmoo_userlist_handle_state(userlist, v, U_AWAY, FALSE); break; case '(': gm_mcp_vmoo_userlist_handle_remove(userlist, v); break; default: break; } } } } if (v) { MOOVar_free(v); } return TRUE; } void gm_mcp_vmoo_userlist_create_view(GmMcpPackage *package, GObject *parent) { gm_mcp_userlist_view_new(package, parent); }