#include #include #include #include #include #include #include "gm-world.h" #include "gm-app.h" #include "mcp/gm-mcp-session.h" #include "gm-triggers.h" #include "gm-marshal.h" #include "gm-net.h" #include "gm-support.h" #include "gm-debug.h" #define GM_WORLD_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), \ GM_TYPE_WORLD, GmWorldPrivate)) static void gm_world_save_input_history(GmWorld *world); static void gm_world_load_input_history(GmWorld *world); static void gm_world_load_triggers(GmWorld *world); static void on_gm_world_net_state_changing(GmNet *net, GmNetState state, GmWorld *world); static void on_gm_world_net_state_changed(GmNet *net, GmNetState state, GmWorld *world); static void on_gm_world_net_net_error(GmNet *net, gchar *error, gint code, GmWorld *world); static void on_gm_world_net_bytes_recv(GmNet *net, gchar *text, gint len, GmWorld *world); static void on_gm_world_options_option_changed(GmOptions *options, gchar *key, GmWorld *world); static void on_gm_world_editor_save(GmEditor *editor, GmWorld *world); struct _GmWorldPrivate { gchar *path; gboolean loaded; gboolean active; guint activity; gchar *buffer; gboolean manual_disconnect; time_t manual_disconnect_timeout; guint reconnect_id; guint flush_history_id; time_t last_command; GmOptions *options; GmTriggers *triggers; GmNet *net; GmMcpSession *mcp; GList *history; GSList *editors; GmEditingInfo editing_info; gint last_day; gint fd_log; }; /* Properties */ enum { PROP_0, PROP_NAME, PROP_PATH, PROP_ACTIVE, PROP_ACTIVITY, PROP_OPTIONS, PROP_STATE, PROP_CURRENT_HOST, PROP_CURRENT_PORT }; /* Signals */ enum { ACTIVATE_REQUEST, LOAD, UNLOAD, STATE_CHANGING, WORLD_ERROR, TEXT_RECEIVED, EDITOR_ADDED, EDITOR_REMOVED, HIGHLIGHT, NOTIFY_MESSAGE, NUM_SIGNALS }; #define HISTORY_FLUSH_INTERVAL (10 * 60000) static guint world_signals[NUM_SIGNALS] = {0}; G_DEFINE_TYPE(GmWorld, gm_world, G_TYPE_OBJECT) static void gm_world_finalize(GObject *object) { GmWorld *world = GM_WORLD(object); if (world->priv->path) { gm_options_save(world->priv->options); gm_world_save_input_history(world); gm_triggers_save(world->priv->triggers); } if (world->priv->fd_log > 0) { close(world->priv->fd_log); } if (world->priv->reconnect_id) { g_source_remove(world->priv->reconnect_id); } if (world->priv->flush_history_id) { g_source_remove(world->priv->flush_history_id); } gm_g_list_free_simple(world->priv->history); g_free(world->priv->path); g_free(world->priv->buffer); g_free(world->priv->editing_info.name); g_free(world->priv->editing_info.upload); g_list_free(world->priv->editing_info.lines); g_object_unref(world->priv->triggers); g_object_unref(world->priv->options); g_object_unref(world->priv->net); g_object_unref(world->priv->mcp); G_OBJECT_CLASS(gm_world_parent_class)->finalize(object); } static void gm_world_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GmWorld *world = GM_WORLD(object); switch (prop_id) { case PROP_NAME: g_value_set_string(value, gm_world_name(world)); break; case PROP_PATH: g_value_set_string(value, world->priv->path); break; case PROP_ACTIVE: g_value_set_boolean(value, world->priv->active); break; case PROP_ACTIVITY: g_value_set_int(value, world->priv->activity); break; case PROP_OPTIONS: g_value_set_object(value, world->priv->options); break; case PROP_STATE: g_value_set_int(value, gm_world_state(world)); break; case PROP_CURRENT_HOST: g_value_set_string(value, gm_world_current_host(world)); break; case PROP_CURRENT_PORT: g_value_set_string(value, gm_world_current_port(world)); break; } } static void gm_world_set_property(GObject *object, guint prop_id, GValue const *value, GParamSpec *pspec) { GmWorld *world = GM_WORLD(object); switch (prop_id) { case PROP_ACTIVE: gm_world_set_active(world, g_value_get_boolean(value)); break; case PROP_ACTIVITY: gm_world_set_activity(world, g_value_get_int(value)); break; } } static void gm_world_class_init(GmWorldClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = gm_world_finalize; object_class->get_property = gm_world_get_property; object_class->set_property = gm_world_set_property; g_object_class_install_property(object_class, PROP_NAME, g_param_spec_string("name", "NAME", "The worlds name", NULL, G_PARAM_READABLE)); g_object_class_install_property(object_class, PROP_PATH, g_param_spec_string("path", "PATH", "The worlds path", NULL, G_PARAM_READABLE)); g_object_class_install_property(object_class, PROP_ACTIVE, g_param_spec_boolean("active", "ACTIVE", "If world is active", FALSE, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(object_class, PROP_ACTIVITY, g_param_spec_boolean("activity", "ACTIVITY", "Lines of activity", 0, G_PARAM_READABLE | G_PARAM_WRITABLE)); g_object_class_install_property(object_class, PROP_OPTIONS, g_param_spec_object("options", "OPTIONS", "Options object", GM_TYPE_OPTIONS, G_PARAM_READABLE)); g_object_class_install_property(object_class, PROP_STATE, g_param_spec_int("state", "STATE", "World state", 0, GM_NET_STATE_END, 0, G_PARAM_READABLE)); g_object_class_install_property(object_class, PROP_CURRENT_HOST, g_param_spec_string("current_host", "CURRENT_HOST", "Current host", 0, G_PARAM_READABLE)); g_object_class_install_property(object_class, PROP_CURRENT_PORT, g_param_spec_string("current_port", "CURRENT_PORT", "Current port", 0, G_PARAM_READABLE)); world_signals[ACTIVATE_REQUEST] = g_signal_new("activate_request", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmWorldClass, activate_request), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); world_signals[LOAD] = g_signal_new("load", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmWorldClass, load), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); world_signals[UNLOAD] = g_signal_new("unload", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmWorldClass, unload), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); world_signals[STATE_CHANGING] = g_signal_new("state_changing", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmWorldClass, state_changing), NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); world_signals[WORLD_ERROR] = g_signal_new("world_error", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmWorldClass, world_error), NULL, NULL, gm_marshal_VOID__STRING_INT, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT); world_signals[TEXT_RECEIVED] = g_signal_new("text_received", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmWorldClass, text_received), NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); world_signals[EDITOR_ADDED] = g_signal_new("editor_added", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmWorldClass, editor_added), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT); world_signals[EDITOR_REMOVED] = g_signal_new("editor_removed", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmWorldClass, editor_removed), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT); world_signals[HIGHLIGHT] = g_signal_new("highlight", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmWorldClass, highlight), NULL, NULL, gm_marshal_VOID__INT_INT_STRING, G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING); world_signals[NOTIFY_MESSAGE] = g_signal_new("notify_message", G_OBJECT_CLASS_TYPE(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(GmWorldClass, notify_message), NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); g_type_class_add_private(object_class, sizeof(GmWorldPrivate)); } static void gm_world_create_default_settings(GmWorld *world) { const gchar *loc = gm_default_charset(); world->priv->options = gm_options_new(); gm_options_set(world->priv->options, "name", ""); gm_options_set(world->priv->options, "autoload", "0"); gm_options_set(world->priv->options, "host", ""); gm_options_set(world->priv->options, "port", "1111"); gm_options_set(world->priv->options, "player_name", ""); gm_options_set(world->priv->options, "reconnect", "0"); gm_options_set(world->priv->options, "password", ""); gm_options_set(world->priv->options, "charset", (gchar *)loc); gm_options_set(world->priv->options, "history_length", "500"); // Non configurable options gm_options_set(world->priv->options, "pane_position", "150"); } static void gm_world_init(GmWorld *world) { world->priv = GM_WORLD_GET_PRIVATE(world); gm_world_create_default_settings(world); world->priv->path = NULL; world->priv->loaded = FALSE; world->priv->history = NULL; world->priv->activity = 0; world->priv->triggers = gm_triggers_new(); world->priv->net = gm_net_new(); world->priv->mcp = gm_mcp_session_new(G_OBJECT(world)); world->priv->buffer = NULL; world->priv->editing_info.is_editing = FALSE; g_signal_connect(world->priv->net, "state_changing", G_CALLBACK(on_gm_world_net_state_changing), world); g_signal_connect(world->priv->net, "state_changed", G_CALLBACK(on_gm_world_net_state_changed), world); g_signal_connect(world->priv->net, "net_error", G_CALLBACK(on_gm_world_net_net_error), world); g_signal_connect(world->priv->net, "bytes_recv", G_CALLBACK(on_gm_world_net_bytes_recv), world); g_signal_connect(world->priv->options, "option_changed", G_CALLBACK(on_gm_world_options_option_changed), world); } static void gm_world_load_input_history(GmWorld *world) { FILE *f; gchar line[1024], *filename; GString *str; filename = g_strconcat(world->priv->path, G_DIR_SEPARATOR_S, "history", NULL); gm_debug_msg(DEBUG_DEFAULT, "GmWorld.LoadInputHistory: loading history " "(%s)!", filename); if ((f = fopen(filename, "r")) != NULL) { str = g_string_new(""); while (fgets(line, 1024 - 1, f) != NULL) { g_string_append(str, line); if (line[strlen(line) - 1] == '\n') { if (line[1] != '\0') { // Empty lines, we don't need to process those world->priv->history = g_list_append(world->priv->history, g_strndup(str->str, strlen(str->str) - 1)); } g_string_erase(str, 0, -1); } } g_string_free(str, TRUE); fclose(f); } else { gm_debug_msg(DEBUG_DEFAULT, "GmWorld.LoadInputHistory: could not " "retrieve contents of file %s (%s)", filename, strerror(errno)); } g_free(filename); } static void gm_world_save_input_history(GmWorld *world) { FILE *f; gchar *filename; GList *elem; if (world->priv->path == NULL) { return; } filename = g_strconcat(world->priv->path, G_DIR_SEPARATOR_S, "history", NULL); f = fopen(filename, "w"); if (f) { gm_debug_msg(DEBUG_DEFAULT, "GmWorld.SaveInputHistory: saving input " "history to %s", filename); for (elem = world->priv->history; elem; elem = elem->next) { fprintf(f, "%s\n", (gchar *) (elem->data)); } fclose(f); chmod(filename, 0660); } else { gm_debug_msg(DEBUG_DEFAULT, "GmWorld.SaveInputHistory: couldn't open " "history file (%s) for saving: %s", filename, strerror(errno)); } g_free(filename); } static gboolean flush_history_cb(gpointer data) { GmWorld *world = GM_WORLD(data); gm_world_save_input_history(world); world->priv->flush_history_id = 0; return FALSE; } static void gm_world_load_triggers(GmWorld *world) { gchar *path; gchar *oldpath; if (world->priv->triggers) { g_object_unref(world->priv->triggers); } oldpath = g_strconcat(world->priv->path, G_DIR_SEPARATOR_S, "triggers", NULL); path = g_strconcat(world->priv->path, G_DIR_SEPARATOR_S, "triggers.xml", NULL); if (g_file_test(oldpath, G_FILE_TEST_EXISTS) && !g_file_test(path, G_FILE_TEST_EXISTS)) { rename(oldpath, path); } g_free(oldpath); world->priv->triggers = gm_triggers_new_from_file(path); g_free(path); } static gboolean gm_world_reconnect(GmWorld *world) { world->priv->reconnect_id = 0; const gchar *host, *port; host = gm_net_current_host(world->priv->net); port = gm_net_current_port(world->priv->net); if (!host) { host = gm_options_get(world->priv->options, "host"); } if (!port) { port = gm_options_get(world->priv->options, "port"); } gm_world_connect_to(world, host, port); return FALSE; } void gm_world_check_dirs(GmWorld *world) { gchar *tmp_path; tmp_path = g_strconcat(world->priv->path, G_DIR_SEPARATOR_S, "logs", NULL); if (!g_file_test(tmp_path, G_FILE_TEST_EXISTS)) { mkdir(tmp_path, 0750); } else { chmod(tmp_path, 0750); } g_free(tmp_path); } /* Public */ GmWorld * gm_world_new(gchar *path) { GmWorld *world = GM_WORLD(g_object_new(GM_TYPE_WORLD, NULL)); gchar *options_path; gchar *basename; if (path != NULL) { options_path = g_strconcat(path, G_DIR_SEPARATOR_S, "settings.xml", NULL); gm_debug_msg(DEBUG_DEFAULT, "GmWorld.new: creating new world for %s", path); world->priv->path = g_strdup(path); gm_debug_msg(DEBUG_DEFAULT, "GmWorld.new: creating default world " "settings for %s", path); _gm_options_check_old_options(options_path); if (!gm_options_load(world->priv->options, options_path)) { /* Set the name then */ basename = g_path_get_basename(path); gm_options_set(world->priv->options, "name", basename); g_free(basename); } if (strlen(gm_options_get(world->priv->options, "charset")) == 0) { gm_options_set(world->priv->options, "charset", gm_default_charset()); } g_free(options_path); gm_world_load_input_history(world); gm_world_load_triggers(world); gm_world_check_dirs(world); } /* CHECK: all done? */ return world; } GmWorld * gm_world_dup(GmWorld *source) { GmWorld *copy = gm_world_new(NULL); g_object_unref(copy->priv->options); copy->priv->options = gm_options_dup(source->priv->options); copy->priv->triggers = gm_triggers_dup(source->priv->triggers); return copy; } void gm_world_load(GmWorld *world) { if (world->priv->loaded) { g_signal_emit(world, world_signals[ACTIVATE_REQUEST], 0); return; } world->priv->loaded = TRUE; g_signal_emit(world, world_signals[LOAD], 0); g_signal_emit(world, world_signals[ACTIVATE_REQUEST], 0); gm_world_connect(world); } void gm_world_unload(GmWorld *world) { if (world->priv->loaded) { world->priv->loaded = FALSE; gm_world_disconnect(world); while (world->priv->editors) { gm_world_remove_editor(world, GM_EDITOR(world->priv->editors->data)); } if (world->priv->reconnect_id) { g_source_remove(world->priv->reconnect_id); world->priv->reconnect_id = 0; } g_signal_emit(world, world_signals[UNLOAD], 0); if (world->priv->flush_history_id) { g_source_remove(world->priv->flush_history_id); flush_history_cb(world); } } } GmTriggers * gm_world_triggers(GmWorld *world) { return world->priv->triggers; } GmOptions * gm_world_options(GmWorld *world) { return world->priv->options; } const gchar * gm_world_name(GmWorld *world) { return gm_options_get(world->priv->options, "name"); } const gchar * gm_world_path(GmWorld *world) { return world->priv->path; } GList ** gm_world_history(GmWorld *world) { return &(world->priv->history); } gboolean gm_world_loaded(GmWorld *world) { return world->priv->loaded; } gboolean gm_world_connected(GmWorld *world) { return gm_net_state(world->priv->net) == GM_NET_STATE_CONNECTED; } gboolean gm_world_disconnected(GmWorld *world) { return gm_net_state(world->priv->net) == GM_NET_STATE_DISCONNECTED; } GmNetState gm_world_state(GmWorld *world) { return gm_net_state(world->priv->net); } const gchar * gm_world_current_host(GmWorld *world) { return gm_net_current_host(world->priv->net); } const gchar * gm_world_current_port(GmWorld *world) { return gm_net_current_port(world->priv->net); } void gm_world_connect_to(GmWorld *world, gchar const *host, gchar const *port) { if (world->priv->reconnect_id) { g_source_remove(world->priv->reconnect_id); } gm_net_connect(world->priv->net, host, port); } void gm_world_connect(GmWorld *world) { gm_world_connect_to(world, gm_options_get(world->priv->options, "host"), gm_options_get(world->priv->options, "port")); } void gm_world_disconnect(GmWorld *world) { world->priv->manual_disconnect = TRUE; world->priv->manual_disconnect_timeout = time(0) + 5; gm_net_disconnect(world->priv->net); } void gm_world_prepare_disconnect(GmWorld *world) { world->priv->manual_disconnect = TRUE; world->priv->manual_disconnect_timeout = time(0) + 5; } gboolean gm_world_log_allowed(GmWorld *world, GmLogType type) { GmOptions *options = gm_app_options(gm_app_instance()); if (!gm_options_get_int(options, "logging_enable")) { return FALSE; } if (gm_options_get_int(world->priv->options, "logging_override")) { options = world->priv->options; } switch (type) { case LOG_IN: return gm_options_get_int(options, "logging_in"); break; case LOG_OUT: return gm_options_get_int(options, "logging_out"); break; case LOG_STATUS: return gm_options_get_int(options, "logging_status"); break; case LOG_MCP_IN: return gm_options_get_int(options, "logging_mcp_in"); break; case LOG_MCP_OUT: return gm_options_get_int(options, "logging_mcp_out"); break; case LOG_MCP_STATUS: return gm_options_get_int(options, "logging_mcp_status"); break; default: return FALSE; break; } } void gm_world_log(GmWorld *world, GmLogType type, gchar const *text) { GString *s; gchar *start, *log, *no_ansi; struct tm *timet; time_t timer; gint len; GmOptions *options; if (type == LOG_NONE) { return; } // Check whether to log this type if (!gm_world_log_allowed(world, type)) { return; } options = gm_app_options(gm_app_instance()); if (gm_options_get_int(world->priv->options, "logging_override")) { options = world->priv->options; } timer = time(0); timet = localtime(&timer); if (world->priv->fd_log <= 0 || world->priv->last_day != timet->tm_mday) { if (world->priv->fd_log > 0) { close(world->priv->fd_log); } log = g_strdup_printf("%s/logs/%04d-%02d-%02d.log", world->priv->path, timet->tm_year + 1900, timet->tm_mon + 1, timet->tm_mday); world->priv->fd_log = open(log, O_APPEND | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); g_free(log); world->priv->last_day = timet->tm_mday; } if (world->priv->fd_log == -1) { return; } if (gm_options_get_int(options, "logging_add_timestamp")) { start = g_strdup_printf("[%02d:%02d] ", timet->tm_hour, timet->tm_min); s = g_string_new(start); g_free(start); } else { s = g_string_new(""); } if (gm_options_get_int(options, "logging_add_log_type")) { switch (type) { case LOG_IN: s = g_string_append_c(s, '<'); break; case LOG_OUT: s = g_string_append_c(s, '>'); break; case LOG_STATUS: s = g_string_append_c(s, '#'); break; case LOG_MCP_IN: s = g_string_append(s, "[MCP] <"); break; case LOG_MCP_OUT: s = g_string_append(s, "[MCP] >"); break; case LOG_MCP_STATUS: s = g_string_append(s, "[MCP] #"); break; default: break; } s = g_string_append_c(s, ' '); } s = g_string_append(s, text); no_ansi = gm_ansi_strip(g_strdup(s->str)); len = strlen(no_ansi); write(world->priv->fd_log, no_ansi, strlen(no_ansi)); if (no_ansi[len - 1] != '\n') { write(world->priv->fd_log, "\n", 1); } g_free(no_ansi); g_string_free(s, TRUE); } void gm_world_parse_legacy_editing_start(GmWorld *world, gchar *line) { gchar *name_start, *upload_start; GmEditingInfo *einfo = &(world->priv->editing_info); name_start = strstr(line, "name: "); if (!name_start) { return; } upload_start = strstr(line, " upload: "); if (!upload_start) { return; } einfo->is_editing = TRUE; einfo->name = g_strndup(name_start + 6, (upload_start - name_start) - 6); einfo->upload = g_strndup(upload_start + 9, (upload_start - line) + 9); einfo->lines = NULL; } #define MAX_MATCHES 10 gchar * gm_world_triggers_subst(gchar const *data, gchar const *text, regmatch_t *matches, gint nummatches) { gchar *tmp = (gchar *)data; gchar *p, *ptr, *tmp2; GString *result = NULL; gint n; gunichar c; result = g_string_new(NULL); while ((p = g_utf8_strchr(tmp, -1, '\\'))) { n = 0; ptr = g_utf8_next_char(p); g_string_append_len(result, tmp, g_utf8_strlen(tmp, -1) - g_utf8_strlen(p, -1)); if (g_unichar_isdigit(g_utf8_get_char(ptr))) { while ((c = (g_utf8_get_char(ptr))) != 0 && g_unichar_isdigit(c)) { n = (n * 10) + g_unichar_digit_value(c); ptr = g_utf8_next_char(ptr); } /* Replace it */ if (n < nummatches) { tmp2 = g_strndup(g_utf8_offset_to_pointer(text, matches[n].rm_so), matches[n].rm_eo); g_string_append(result, tmp2); g_free(tmp2); } tmp = ptr; } else { tmp = ptr; } } g_string_append(result, tmp); tmp = result->str; g_string_free(result, FALSE); return tmp; } void gm_world_apply_trigger(GmWorld *world, GmTrigger *trigger, gchar const *text, regmatch_t *matches, gint nummatches) { GList *item; GmTriggerData *data; gint i, nargs; gchar **spawn_args, *tmp; #ifdef HAVE_RUBY gchar *argstr; #endif for (item = trigger->actions; item; item = item->next) { data = (GmTriggerData *)(item->data); switch (data->type) { case TAT_HIGHLIGHT_LINE: g_signal_emit(world, world_signals[HIGHLIGHT], 0, -1, -1, data->data); break; case TAT_HIGHLIGHT_MATCH: for (i = 0; matches[i].rm_so != -1; ++i) { g_signal_emit(world, world_signals[HIGHLIGHT], 0, matches[i].rm_so, matches[i].rm_eo, data->data); } break; case TAT_BEEP: gdk_beep(); break; case TAT_PLAY_SOUND: tmp = gm_world_triggers_subst(data->data, text, matches, nummatches); // TODO g_free(tmp); break; case TAT_NOTIFY: tmp = gm_world_triggers_subst(data->data, text, matches, nummatches); g_signal_emit(world, world_signals[NOTIFY_MESSAGE], 0, tmp); g_free(tmp); break; case TAT_RUN_SCRIPT: #ifdef HAVE_RUBY tmp = gm_world_triggers_subst(data->data, text, matches, nummatches); argstr = g_utf8_strchr(tmp, -1, ' '); if (argstr) { *argstr = '\0'; argstr = g_utf8_next_char(argstr); } gm_scripts_run(gm_app_scripts(gm_app_instance()), world, tmp, argstr); g_free(tmp); #endif break; case TAT_RUN: tmp = gm_world_triggers_subst(data->data, text, matches, nummatches); if (g_shell_parse_argv(tmp, &nargs, &spawn_args, NULL)) { g_spawn_async(NULL, spawn_args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL); g_strfreev(spawn_args); } g_free(tmp); break; default: break; } } } void gm_world_process_triggers(GmWorld *world, gchar *text) { GList const *item; GmTrigger *trigger; regmatch_t matches[MAX_MATCHES]; gint num; for (item = gm_triggers_list(world->priv->triggers); item; item = item->next) { trigger = (GmTrigger *)(item->data); if (trigger->event == TT_OUTPUT) { if ((num = gm_trigger_match(trigger, text, matches, MAX_MATCHES))) { gm_world_apply_trigger(world, trigger, text, matches, num); } } } } void gm_world_process_line(GmWorld *world, gchar *line, gint len) { gchar *non_text_start = NULL, *from; GmEditingInfo *einfo = &(world->priv->editing_info); GmEditor *editor; gchar *no_ansi; gboolean has_ansi_start; if (strncmp(line, "\x1B[0m", 4) == 0) { has_ansi_start = TRUE; from = line + 4; if (len != -1) { len = len - 4; } } else { has_ansi_start = FALSE; from = line; } if (len == -1) { non_text_start = g_strdup(from); } else { non_text_start = g_strndup(from, len); } if (einfo->is_editing) { if (strcmp(non_text_start, ".") == 0) { editor = gm_editor_new(einfo->name, einfo->upload, einfo->lines); g_signal_connect(editor, "save", G_CALLBACK(on_gm_world_editor_save), world); gm_world_add_editor(world, editor); einfo->is_editing = FALSE; g_free(einfo->name); einfo->name = NULL; g_free(einfo->upload); einfo->upload = NULL; g_list_free(einfo->lines); einfo->lines = NULL; } else { einfo->lines = g_list_append(einfo->lines, g_strdup(non_text_start)); } } else if (strncmp(non_text_start, "#$#", 3) == 0) { if (strncasecmp(non_text_start + 3, " edit ", 6) == 0) { gm_world_parse_legacy_editing_start(world, non_text_start + 9); } else { gm_mcp_session_handle_oob(world->priv->mcp, non_text_start + 3); gm_world_log(world, LOG_MCP_IN, non_text_start); } } else { if (!gm_world_active(world)) { gm_world_set_activity(world, world->priv->activity + 1); } if (strncmp(non_text_start, "#$\"", 3) == 0) { if (has_ansi_start) { g_signal_emit(world, world_signals[TEXT_RECEIVED], 0, "\x1B[0m"); } g_signal_emit(world, world_signals[TEXT_RECEIVED], 0, non_text_start + 3); g_signal_emit(world, world_signals[TEXT_RECEIVED], 0, "\n"); gm_world_log(world, LOG_IN, non_text_start + 3); no_ansi = gm_ansi_strip(g_strdup(non_text_start + 3)); } else { if (has_ansi_start) { g_signal_emit(world, world_signals[TEXT_RECEIVED], 0, "\x1B[0m"); } g_signal_emit(world, world_signals[TEXT_RECEIVED], 0, non_text_start); g_signal_emit(world, world_signals[TEXT_RECEIVED], 0, "\n"); gm_world_log(world, LOG_IN, non_text_start); no_ansi = gm_ansi_strip(g_strdup(non_text_start)); } /* Process triggers */ gm_world_process_triggers(world, no_ansi); g_free(no_ansi); } g_free(non_text_start); } void gm_world_process_input(GmWorld *world, gchar const *text) { #ifdef HAVE_RUBY gchar *space, *script, *argstr; gboolean script_ran = FALSE; if (g_utf8_strlen(text, -1) > 1) { if (text[0] == '/' && text[1] != '/') { space = strstr(text, " "); if (space == NULL) { script = g_strdup(text + 1); argstr = g_strdup(""); } else { script = g_strndup(text + 1, (space - text) - 1); argstr = g_strdup(space + 1); } gm_debug_msg(DEBUG_DEFAULT, "GmWorld.ProcessInput: Trying script " "%s (%s)", script, argstr); script_ran = gm_scripts_run(gm_app_scripts(gm_app_instance()), world, script, argstr); g_free(script); g_free(argstr); if (script_ran) { return; } } else if (text[0] == '/' && text[1] == '/') { text = text + 1; } } #endif gm_world_sendln(world, text); } void gm_world_status(GmWorld *world, gchar const *text) { gchar *line = g_strconcat("\x1B[1;33m# ", text, "\x1B[0m", NULL); gm_world_writeln(world, line); gm_world_log(world, LOG_STATUS, line); g_free(line); } void gm_world_writeln(GmWorld *world, gchar const *text) { gchar *newline = g_strconcat(text, "\n", NULL); g_signal_emit(world, world_signals[TEXT_RECEIVED], 0, newline); g_free(newline); } void gm_world_remove_editor(GmWorld *world, GmEditor *editor) { g_return_if_fail(g_slist_find(world->priv->editors, editor) != NULL); world->priv->editors = g_slist_remove(world->priv->editors, editor); g_signal_emit(world, world_signals[EDITOR_REMOVED], 0, G_OBJECT(editor)); g_object_unref(editor); } void gm_world_add_editor(GmWorld *world, GmEditor *editor) { world->priv->editors = g_slist_append(world->priv->editors, editor); g_signal_connect_swapped(editor, "close", G_CALLBACK(gm_world_remove_editor), world); g_signal_emit(world, world_signals[EDITOR_ADDED], 0, editor); } GSList const * gm_world_editors(GmWorld *world) { return world->priv->editors; } void gm_world_sendln_log(GmWorld *world, gchar const *text, GmLogType logtype) { gchar *normal; // Convert text from utf-8 to the correct locale normal = gm_from_utf8_with_fallback(text, -1, gm_options_get(world->priv->options, "charset"), "?"); if (!normal) { gm_debug_msg(DEBUG_DEFAULT, "GmWorld.SendlnLog: conversion failed!"); normal = g_strdup(text); } if (logtype != LOG_NONE && (logtype != LOG_OUT || gm_mcp_session_initialized(world->priv->mcp))) { gm_world_log(world, logtype, text); } gm_net_send_line(world->priv->net, normal); g_free(normal); if (world->priv->flush_history_id != 0) g_source_remove(world->priv->flush_history_id); world->priv->flush_history_id = g_timeout_add(HISTORY_FLUSH_INTERVAL, flush_history_cb, world); } void gm_world_sendln(GmWorld *world, gchar const *text) { world->priv->last_command = time(0); gm_world_sendln_log(world, text, LOG_OUT); } void gm_world_set_active(GmWorld *world, gboolean active) { world->priv->active = active; g_object_notify(G_OBJECT(world), "active"); if (active) { gm_world_set_activity(world, 0); } } gboolean gm_world_active(GmWorld *world) { return world->priv->active; } void gm_world_set_activity(GmWorld *world, gint activity) { world->priv->activity = activity; g_object_notify(G_OBJECT(world), "activity"); } gint gm_world_activity(GmWorld *world) { return world->priv->activity; } void gm_world_name_changed(GmWorld *world) { gchar *tmp_path = world->priv->path ? g_strdup(world->priv->path) : NULL; gchar *new_path = g_strconcat(gm_app_worlds_path(gm_app_instance()), "/", gm_options_get(world->priv->options, "name"), NULL); if (tmp_path && strcmp(tmp_path, new_path) == 0) { g_free(new_path); g_free(tmp_path); return; } g_free(world->priv->path); world->priv->path = new_path; if (tmp_path && g_file_test(tmp_path, G_FILE_TEST_EXISTS)) { // Then! rename the old_path to the new_path rename(tmp_path, world->priv->path); } else if (!g_file_test(world->priv->path, G_FILE_TEST_EXISTS)) { // There was no old path, so a new dir should be made mkdir(world->priv->path, 0755); } g_free(tmp_path); tmp_path = g_strconcat(world->priv->path, G_DIR_SEPARATOR_S, "settings.xml", NULL); gm_options_save_as(world->priv->options, tmp_path); g_free(tmp_path); tmp_path = g_strconcat(world->priv->path, G_DIR_SEPARATOR_S, "triggers.xml", NULL); gm_triggers_save_as(world->priv->triggers, tmp_path); g_free(tmp_path); gm_world_check_dirs(world); g_object_notify(G_OBJECT(world), "name"); } GmMcpSession * gm_world_get_mcp_session(GmWorld *world) { return world->priv->mcp; } void gm_world_auto_login(GmWorld *world) { gchar const *player_name = gm_options_get(world->priv->options, "player_name"); gchar const *passwd = gm_options_get(world->priv->options, "password"); gchar *sendln; if (player_name && *player_name != '\0') { sendln = g_strconcat("connect ", player_name, " ", passwd, NULL); gm_world_sendln_log(world, sendln, LOG_NONE); g_free(sendln); } } /* Callbacks */ static void on_gm_world_editor_save(GmEditor *editor, GmWorld *world) { GList *line; if (gm_net_state(world->priv->net) == GM_NET_STATE_CONNECTED) { gm_world_sendln(world, gm_editor_upload_cmd(editor)); for (line = gm_editor_lines(editor); line; line = line->next) { gm_world_sendln(world, (gchar *)(line->data)); } gm_world_sendln(world, "."); gm_editor_saved(editor); } else { gm_world_status(world, _("Could not save editor text, world is " "not connected at the moment")); } } static void on_gm_world_net_state_changing(GmNet *net, GmNetState state, GmWorld *world) { GmNetState prev_state = gm_net_state(net); g_signal_emit(world, world_signals[STATE_CHANGING], 0, state); if ((prev_state == GM_NET_STATE_CONNECTED || prev_state == GM_NET_STATE_DISCONNECTING) && state == GM_NET_STATE_DISCONNECTED) { gm_mcp_session_reset(world->priv->mcp); #ifdef HAVE_RUBY gm_scripts_run(gm_app_scripts(gm_app_instance()), world, "on_disconnect", NULL); #endif if (!world->priv->reconnect_id && gm_options_get_int(world->priv->options, "reconnect")) { // Manual disconnect timeout check if (world->priv->manual_disconnect || time(0) < world->priv->manual_disconnect_timeout) { return; } // Last command send check if (difftime(time(0), world->priv->last_command) <= 3) { return; } world->priv->reconnect_id = g_timeout_add(3000, (GSourceFunc)gm_world_reconnect, world); gm_world_status(world, _("Reconnecting in 3 seconds...")); } } } static void on_gm_world_net_state_changed(GmNet *net, GmNetState state, GmWorld *world) { if (state == GM_NET_STATE_CONNECTED) { world->priv->manual_disconnect = FALSE; gm_world_auto_login(world); #ifdef HAVE_RUBY gm_scripts_run(gm_app_scripts(gm_app_instance()), world, "on_connect", NULL); #endif } } static void on_gm_world_net_net_error(GmNet *net, gchar *error, gint code, GmWorld *world) { g_signal_emit(world, world_signals[WORLD_ERROR], 0, error, code); gm_world_log(world, LOG_STATUS, error); } static void on_gm_world_net_bytes_recv(GmNet *net, gchar *text, gint len, GmWorld *world) { gchar *all, *utext, *ptr, *start; gunichar ch, prev; utext = gm_to_utf8_with_fallback(text, len, gm_options_get(world->priv->options, "charset"), "?"); if (!utext) { gm_debug_msg(DEBUG_DEFAULT, "GmWorld.NetBytesRecv: conversion failed!"); utext = g_strndup(text, len); } if (world->priv->buffer != NULL) { all = g_strconcat(world->priv->buffer, utext, NULL); g_free(utext); g_free(world->priv->buffer); world->priv->buffer = NULL; } else { all = utext; } ptr = start = all; prev = 0; /* Find lines in `all' and process them */ while ((ch = g_utf8_get_char(ptr)) != '\0') { if (ch == '\n') { gm_world_process_line(world, start, ptr - start - (prev == '\r' ? 1 : 0)); ptr = start = g_utf8_next_char(ptr); // Skip \r if (g_utf8_get_char(ptr) == '\r') { ptr = start = g_utf8_next_char(ptr); } } else { ptr = g_utf8_next_char(ptr); } prev = ch; } if (*start != '\0') { world->priv->buffer = g_strdup(start); } g_free(all); } static void on_gm_world_options_option_changed(GmOptions *options, gchar *key, GmWorld *world) { if (strcmp(key, "name") == 0) { gm_world_name_changed(world); } }