This repository has been archived on 2020-04-11. You can view files and clone it, but cannot push or open issues or pull requests.
gnoemoe/gnoemoe/gm-world.c

1425 lines
34 KiB
C

#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#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);
}
}