@@ -47,7 +47,7 @@ bool achievement_system_startup() {
disable_achievements();
return false;
}

achievement_system_initialized = true;
return true;
}
@@ -61,51 +61,65 @@ int award_achievement(int achievement_id){
return add_achievement_progress(achievement_id, get_achievement_max_progress(achievement_id));
}

// Return 1 on success, 0 on failure
int add_achievement_progress(int achievement_id, int add_progress_count){
if(achievement_system_disabled){ return ACHIEVEMENT_PUSH_FAILURE; }
if(ACHIEVEMENT_DEBUG){pline("DEBUG: add_achievement_progress(%i, %i)", achievement_id, add_progress_count);}
int add_achievement_progress(int achievement_id, int add_progress){
if (achievement_system_disabled) return ACHIEVEMENT_PUSH_FAILURE;
int old_progress = get_achievement_progress(achievement_id);
if (old_progress < 0) old_progress = 0;
return push_achievement_progress(achievement_id, old_progress + add_progress);
}

int push_achievement_progress(int achievement_id, int new_progress){
if (!check_db_connection()) disable_achievements();
if (achievement_system_disabled) return ACHIEVEMENT_PUSH_FAILURE;

if(check_db_connection()){
disable_achievements();
}
int push_failed = 0;
int old_progress = get_achievement_progress(achievement_id);
int max_progress = get_achievement_max_progress(achievement_id);

//Check if user exists
if(!user_exists()){
register_user();
}
//Calculate user's completion on this achievement
int pre_achievement_progress = get_achievement_progress(achievement_id);
int max_achievement_progress = get_achievement_max_progress(achievement_id);
if(ACHIEVEMENT_DEBUG){pline("DEBUG: get_achievement_max_progress(%i)=%i", achievement_id, max_achievement_progress);}

if(pre_achievement_progress < max_achievement_progress){ //user still needs achievement
if(pre_achievement_progress + add_progress_count >= max_achievement_progress){ //Achievement fully achieved!
if(push_achievement_progress(achievement_id, max_achievement_progress)){ //floor the value to max_progress
char * achievement_name = get_achievement_name(achievement_id);
pline("Congratulations! You've earned the achievement: %s", achievement_name);
free(achievement_name);
return ACHIEVEMENT_PUSH_SUCCESS;
}
else{
pline("Er, oops. You got an achievement, but it can't be recorded.");
return ACHIEVEMENT_PUSH_FAILURE;
}
// Clamp the new progress to a valid range
if (new_progress < 0) new_progress = 0;
if (new_progress > max_progress) new_progress = max_progress;

char* query;
if (old_progress == -2) { // Progress record doesn't exist yet
char *name = mysql_library_escape_string(&db, plname);
if( asprintf(&query, "INSERT INTO `achievement_progress` (`user_id`, `achievement_id`, `progress`) VALUES ((SELECT user_id FROM `users_in_apps` WHERE app_id=%i AND app_username='%s'), %i, %i);", ACHIEVEMENT_APP_ID, name, achievement_id, new_progress) == -1 ) panic("asprintf: %s", strerror(errno));
free(name);
if(mysql.real_query(&db, query, (unsigned int) strlen(query)) != 0){
pline("Real_query failed in push_achievement_progress: %s", mysql.error(&db));
disable_achievements();
push_failed = 1;
}
else{ //user stills needs achievement, but isn't quite there yet
if(push_achievement_progress(achievement_id, pre_achievement_progress+add_progress_count)){
return ACHIEVEMENT_PUSH_SUCCESS;
}
else{
return ACHIEVEMENT_PUSH_FAILURE;
}
} else {
char *name = mysql_library_escape_string(&db, plname);
if( asprintf(&query, "UPDATE `achievement_progress` SET `progress`=%i WHERE `achievement_id`=%i AND `user_id`=(SELECT user_id FROM `users_in_apps` WHERE app_id=%i AND app_username='%s');", new_progress, achievement_id, ACHIEVEMENT_APP_ID, name) == -1 ) panic("asprintf: %s", strerror(errno));
free(name);
if(mysql.real_query(&db, query, (unsigned int) strlen(query)) != 0){
pline("Real_query failed in push_achievement_progress: %s", mysql.error(&db));
disable_achievements();
push_failed = 1;
}
}
free(query);

if (push_failed) {
return ACHIEVEMENT_PUSH_FAILURE;
} else {
if (old_progress < max_progress && new_progress == max_progress) {
char * achievement_name = get_achievement_name(achievement_id);
You("unlock an achievement: \"%s\"", achievement_name);
free(achievement_name);
}
return ACHIEVEMENT_PUSH_SUCCESS;
}
return ACHIEVEMENT_PUSH_SUCCESS;
}

// Returns -1 if achievement system disabled
// Returns -2 to indicate that no progress record exists
// (as opposed to 0 when the record exists but is set to 0)
int get_achievement_progress(int achievement_id){
if( achievement_system_disabled ) return -1;
if (!check_db_connection()) disable_achievements();
if (achievement_system_disabled) return -1;

char* query;
MYSQL_RES *res = NULL;
@@ -114,14 +128,14 @@ int get_achievement_progress(int achievement_id){
int achievement_progress = -1;

char *name = mysql_library_escape_string(&db, plname);
if( asprintf(&query, "SELECT `achievement_progress`.`progress` FROM `achievement_progress` JOIN `users_in_apps` ON `users_in_apps`.`user_id` = `achievement_progress`.`user_id` where app_username = '%s' and app_id = %i and achievement_id = %i;", name, GAME_ID, achievement_id) == -1 ) panic("asprintf: %s", strerror(errno));
if( asprintf(&query, "SELECT `achievement_progress`.`progress` FROM `achievement_progress` JOIN `users_in_apps` ON `users_in_apps`.`user_id` = `achievement_progress`.`user_id` WHERE app_username = '%s' AND app_id = %i AND achievement_id = %i;", name, ACHIEVEMENT_APP_ID, achievement_id) == -1 ) panic("asprintf: %s", strerror(errno));
free(name);
if( mysql.real_query(&db, query, (unsigned int) strlen(query)) != 0 ) goto fail;
free(query);
if( (res = mysql.use_result(&db)) == NULL ) goto fail;
if( (row = mysql.fetch_row(res)) == NULL ) {
if( mysql.num_rows(res) == 0 ) {
achievement_progress = 0;
achievement_progress = -2;
goto out;
} else {
goto fail;
@@ -149,7 +163,8 @@ int get_achievement_progress(int achievement_id){
}

int get_achievement_max_progress(int achievement_id){
if( achievement_system_disabled ) return -1;
if (!check_db_connection()) disable_achievements();
if (achievement_system_disabled) return -1;

MYSQL_RES *res = NULL;
MYSQL_ROW row;
@@ -170,6 +185,8 @@ int get_achievement_max_progress(int achievement_id){
max_progress = atoi(str_max_progress);

free(str_max_progress);

if(ACHIEVEMENT_DEBUG){pline("DEBUG: get_achievement_max_progress(%i)=%i", achievement_id, max_progress);}

row = mysql.fetch_row(res);
assert(row == NULL);
@@ -185,30 +202,19 @@ int get_achievement_max_progress(int achievement_id){
return max_progress;
}

//REQUIRES timeout on non-success!!!!!!!
int push_achievement_progress(int achievement_id, int updated_progress_count){
char* query;

int progress = get_achievement_progress(achievement_id);
if( progress == -1 ) return -1;
if( progress == 0) { //no previous progress on achievement, INSERT time
char *name = mysql_library_escape_string(&db, plname);
if( asprintf(&query, "INSERT INTO `achievement_progress`(`user_id`, `achievement_id`, `progress`) VALUES ((select user_id from `users_in_apps` where app_id=1 and app_username='%s'), %i, %i);", name, achievement_id, updated_progress_count) == -1 ) panic("asprintf: %s", strerror(errno));
free(name);
if(mysql.real_query(&db, query, (unsigned int) strlen(query)) != 0){
pline("Real_query failed in push_achievement_progress: %s", mysql.error(&db));
disable_achievements();
}
free(query);
int get_achievement_awarded(int achievement_id){
if (achievement_system_disabled) return 0;
if (get_achievement_progress(achievement_id) >= get_achievement_max_progress(achievement_id))
return 1;
} else { //TODO: Implement Multi-part achievements (aka UPDATE)
else
return 0;
}
}

// It is the caller's responsibility to free() the return value of this function.
char *get_achievement_name( int achievement_id ){
MYSQL_RES *res = NULL;
if (!check_db_connection()) disable_achievements();

MYSQL_RES *res = NULL;
MYSQL_ROW row;
char* query;
char* str_name;
@@ -241,13 +247,26 @@ char *get_achievement_name( int achievement_id ){
return str_name;
}

// Reset progress of all the "in a single game" achievements
// TODO: Handle the error case somehow, otherwise progress for these
// achievements could "leak over" from a previous game if any of
// these calls fails
void reset_single_game_achievements(){
push_achievement_progress(AID_WALK_5K, 0);
push_achievement_progress(AID_WALK_10K, 0);
}

void disable_achievements(){
pline("Disabling the achievements system for the duration of this session.");
if (!achievement_system_disabled)
pline("Disabling the achievements system for the duration of this session.");
achievement_system_disabled = 1;
}

//TODO Odd unregistered case where app_username is already taken
int register_user(){
// TODO: Handle case where user already exists in users, but not in users_in_apps
int achievements_register_user(){
if (!check_db_connection()) disable_achievements();
if (achievement_system_disabled) return 0;

pline("Hey! Listen! You're not registered in the achievements database.");
pline("If you don't have a CSH account, you can probably leave this blank.");
char str[BUFSZ];
@@ -261,34 +280,37 @@ int register_user(){
if( asprintf(&query, "INSERT INTO `users` (`username`) VALUES ('%s')", str ) == -1) panic("asprintf: %s", strerror(errno));

if(mysql.real_query(&db, query, (unsigned int) strlen(query)) != 0){
pline("Real_query failed in register_user: %s", mysql.error(&db));
pline("Real_query failed in achievements_register_user: %s", mysql.error(&db));
disable_achievements();
}
free(query);

char *allocstr = mysql_library_escape_string(&db, str),
*name = mysql_library_escape_string(&db, plname);
if( asprintf(&query, "INSERT INTO `users_in_apps` VALUES ((SELECT `id` FROM `users` WHERE `users`.`username` = '%s'), %i, '%s')", allocstr, GAME_ID, name) == -1) panic("asprintf: %s", strerror(errno));
if( asprintf(&query, "INSERT INTO `users_in_apps` VALUES ((SELECT `id` FROM `users` WHERE `users`.`username` = '%s'), %i, '%s')", allocstr, ACHIEVEMENT_APP_ID, name) == -1) panic("asprintf: %s", strerror(errno));
free(allocstr);
free(name);

if(mysql.real_query(&db, query, (unsigned int) strlen(query)) != 0){
pline("Real_query failed in register_user: %s", mysql.error(&db));
pline("Real_query failed in achievements_register_user: %s", mysql.error(&db));
disable_achievements();
}
free(query);

return 1;
}

int user_exists(){
int achievements_user_exists(){
if (!check_db_connection()) disable_achievements();
if (achievement_system_disabled) return 0;

MYSQL_RES *res = NULL;
MYSQL_ROW row;
char* query;
int user_exists = 0;

char *name = mysql_library_escape_string(&db, plname);
if( asprintf(&query, "SELECT `app_username` FROM `users_in_apps` WHERE app_username = '%s' AND app_id = %i ;", name, GAME_ID) == -1 ) panic("asprintf: %s", strerror(errno));
if( asprintf(&query, "SELECT `app_username` FROM `users_in_apps` WHERE app_username = '%s' AND app_id = %i ;", name, ACHIEVEMENT_APP_ID) == -1 ) panic("asprintf: %s", strerror(errno));
free(name);
if( mysql.real_query(&db, query, (unsigned int) strlen(query)) != 0 ){
goto fail;
@@ -313,21 +335,25 @@ int user_exists(){
goto out;

fail:
pline("Error in user_exists() (Error: %s)", mysql.error(&db));
pline("Error in achievements_user_exists() (Error: %s)", mysql.error(&db));
disable_achievements();
out:
if( res != NULL ) mysql.free_result(res);

return user_exists;
}


/* Make sure the database connection is still there and if not, attempt
to reconnect it ("ping" == worst-named function). A check involving this
function MUST be placed at the top of any function that directly interacts
with the database (see e.g. get_achievement_name for the exact code). */
int check_db_connection(){
if(mysql.ping(&db)){ // what we have here is a failure to communicate
if (achievement_system_disabled) return 0;

if (mysql.ping(&db)) { // what we have here is a failure to communicate
pline("Error while attempting to reconnect to DB: %s", mysql.error(&db));
return 1;
}
else{
return 0;
} else {
return 1;
}
}
@@ -10,12 +10,7 @@
#include <errno.h>
#include <unistd.h>

#include <mysql.h>
#include <libconfig.h>

#include "achieve.h"
#include "configfile.h"
#include "mysql_library.h"
#include "hack.h"

#ifndef NO_SIGNAL
@@ -34,18 +29,6 @@ struct sockaddr_in mcast_addr;

struct u_stat_t u_stat;

void segv_award( int sig ) {
if( signal(SIGSEGV, SIG_DFL) == SIG_ERR ) {
perror("signal");
exit(EXIT_FAILURE);
}
award_achievement(AID_CRASH);
if( kill(getpid(), SIGSEGV) == -1 ) {
perror("kill");
exit(EXIT_FAILURE);
}
}

void
moveloop()
{
@@ -58,8 +41,6 @@ moveloop()
int last_dnum = -1;
int i;

if( signal(SIGSEGV, segv_award) == SIG_ERR ) pline("Unable to register signal handler: %s", strerror(errno));

bzero(u_stat.plname, sizeof(u_stat.plname));
strncpy(u_stat.plname, plname, sizeof(u_stat.plname) - 1);

@@ -78,10 +59,6 @@ moveloop()
pline("setsockopt: %s", strerror(errno));
}

configfile_init();
mysql_library_startup();
achievement_system_startup();

flags.moonphase = phase_of_the_moon();
if(flags.moonphase == FULL_MOON) {
You("are lucky! Full moon tonight.");
@@ -545,8 +522,12 @@ moveloop()
continue;
}
if (flags.mv) {
if(multi < COLNO && !--multi)
flags.travel = iflags.travel1 = flags.mv = flags.run = 0;
if(multi < COLNO && !--multi) {
flags.travel = 0;
iflags.travel1 = 0;
flags.mv = 0;
flags.run = 0;
}
domove();
} else {
--multi;
@@ -682,7 +663,7 @@ newgame()
flush_screen(1);
com_pager(1);
}

#ifdef INSURANCE
save_currentstate();
#endif
@@ -5,6 +5,7 @@
#include <stdbool.h>
#include "hack.h"
#include "edog.h"
#include "achieve.h"

#ifdef OVLB

@@ -55,6 +56,7 @@ use_camera(obj)
struct obj *obj;
{
register struct monst *mtmp;
int flash_hit = 0;

if(Underwater) {
pline("Using your camera underwater would void the warranty.");
@@ -83,7 +85,10 @@ use_camera(obj)
(int FDECL((*),(OBJ_P,OBJ_P)))0,
obj, NULL)) != 0) {
obj->ox = u.ux, obj->oy = u.uy;
(void) flash_hits_mon(mtmp, obj);
flash_hit = flash_hits_mon(mtmp, obj);
if (flash_hit && is_endgamenasty(mtmp->data)) {
award_achievement(AID_BLIND_RIDER_WITH_CAMERA);
}
}
return 1;
}
@@ -1455,6 +1460,9 @@ register struct obj *obj;

if ((can = mksobj(TIN, FALSE, FALSE)) != 0) {
static const char you_buy_it[] = "You tin it, you bought it!";

if (corpse->corpsenm == PM_WIZARD_OF_YENDOR)
award_achievement(AID_TIN_OF_RODNEY);

can->corpsenm = corpse->corpsenm;
can->cursed = obj->cursed;
@@ -1763,7 +1771,8 @@ struct obj **optr;
return;
}
if(!getdir((char *)0)) {
flags.move = multi = 0;
flags.move = 0;
multi = 0;
return;
}
x = u.ux + u.dx; y = u.uy + u.dy;
@@ -5,6 +5,7 @@
#include <stdbool.h>
#include "hack.h"
#include "artifact.h"
#include "achieve.h"
#ifdef OVLB
#include "artilist.h"
#else
@@ -562,6 +563,7 @@ touch_artifact(obj,mon)

if (!yours) return 0;
You("are blasted by %s power!", s_suffix(the(xname(obj))));
award_achievement(AID_ARTIFACT_BLAST);
dmg = d((Antimagic ? 2 : 4), (self_willed ? 10 : 4));
Sprintf(buf, "touching %s", oart->name);
losehp(dmg, buf, KILLED_BY);
@@ -1068,6 +1070,7 @@ int dieroll; /* needed for Magicbane and vorpal blades */
}
*dmgptr = 2 * mdef->mhp + FATAL_DAMAGE_MODIFIER;
pline("%s cuts %s in half!", wepdesc, mon_nam(mdef));
if (Role_if(PM_SAMURAI)) award_achievement(AID_USE_ARTIFACT_SAM);
otmp->dknown = TRUE;
return TRUE;
} else {
@@ -1277,6 +1280,7 @@ arti_invoke(obj)
if(Sick) make_sick(0L,(char *)0,FALSE,SICK_ALL);
if(Slimed) Slimed = 0L;
if (Blinded > creamed) make_blinded(creamed, FALSE);
if (Role_if(PM_HEALER)) award_achievement(AID_USE_ARTIFACT_HEA);
flags.botl = 1;
break;
}
@@ -1287,6 +1291,7 @@ arti_invoke(obj)
if(epboost) {
You_feel("re-energized.");
u.uen += epboost;
if (Role_if(PM_PRIEST)) award_achievement(AID_USE_ARTIFACT_PRI);
flags.botl = 1;
} else
goto nothing_special;
@@ -1297,6 +1302,7 @@ arti_invoke(obj)
obj->age = 0; /* don't charge for changing their mind */
return 0;
}
if (Role_if(PM_ROGUE)) award_achievement(AID_USE_ARTIFACT_ROG);
break;
}
case CHARGE_OBJ: {
@@ -1311,10 +1317,12 @@ arti_invoke(obj)
(Role_switch == oart->role || !oart->role);
recharge(otmp, b_effect ? 1 : obj->cursed ? -1 : 0);
update_inventory();
if (Role_if(PM_TOURIST)) award_achievement(AID_USE_ARTIFACT_TOU);
break;
}
case LEV_TELE:
level_tele();
if (Role_if(PM_VALKYRIE)) award_achievement(AID_USE_ARTIFACT_VAL);
break;
case CREATE_PORTAL: {
int i, num_ok_dungeons, last_ok_dungeon = 0;
@@ -1368,12 +1376,14 @@ arti_invoke(obj)
} else {
if(!Blind) You("are surrounded by a shimmering sphere!");
else You_feel("weightless for a moment.");
if (Role_if(PM_WIZARD)) award_achievement(AID_USE_ARTIFACT_WIZ);
goto_level(&newlev, FALSE, FALSE, FALSE);
}
break;
}
case ENLIGHTENING:
enlightenment(0);
if (Role_if(PM_MONK)) award_achievement(AID_USE_ARTIFACT_MON);
break;
case CREATE_AMMO: {
struct obj *otmp = mksobj(ARROW, TRUE, FALSE);
@@ -1392,6 +1402,7 @@ arti_invoke(obj)
otmp->owt = weight(otmp);
otmp = hold_another_object(otmp, "Suddenly %s out.",
aobjnam(otmp, "fall"), (const char *)0);
if (Role_if(PM_RANGER)) award_achievement(AID_USE_ARTIFACT_RAN);
break;
}
}
@@ -1427,13 +1438,15 @@ arti_invoke(obj)
{
You_feel("like a rabble-rouser.");
u.uconduct.conflict++;
if (Role_if(PM_CAVEMAN)) award_achievement(AID_USE_ARTIFACT_CAV);
}
else You_feel("the tension decrease around you.");
break;
case LEVITATION:
if(on) {
float_up();
spoteffects(FALSE);
if (Role_if(PM_BARBARIAN)) award_achievement(AID_USE_ARTIFACT_BAR);
} else (void) float_down(I_SPECIAL|TIMEOUT, W_ARTI);
break;
case INVIS:
@@ -1442,6 +1455,7 @@ arti_invoke(obj)
if (on)
Your("body takes on a %s transparency...",
Hallucination ? "normal" : "strange");
if (Role_if(PM_ARCHEOLOGIST)) award_achievement(AID_USE_ARTIFACT_ARC);
else
Your("body seems to unfade...");
break;
@@ -0,0 +1,57 @@
#include "chat.h"
int rx_fd;

void chat_tx(){
int chat_socket = -1;
struct sockaddr_in chat_addr;
char str[BUFSZ];
getlin("Send chat:", str);
if(isspace(str[0]) || str[0] == '\033' || str[0] == '\n' || str[0] == 0){ /* user cancel */
pline("Fine, chat aborted. They probably didn't want to talk to you anyway.");
}
else{
if( (chat_socket = socket(PF_INET, SOCK_DGRAM, 0)) < 0 ) { /* error: can't create socket */
pline("You try and call out, but your voice catches in your throat.");
}
memset(&chat_addr, 0, sizeof(chat_addr));
chat_addr.sin_family = AF_INET;
chat_addr.sin_port = htons(chat_port);
chat_addr.sin_addr.s_addr = inet_addr(chat_ip_address);
if(sendto(chat_socket, str, strlen(str), 0, (const struct sockaddr *) &chat_addr, sizeof(chat_addr)) < 0){ /* error: couldn't transmit */
pline("You call out, but you have a nagging feeling nobody can hear you...");
}
else{
pline("Message sent.");
}
}
}

void setup_chat_rx(){
struct sockaddr_in chat_addr;
struct ip_mreq mreq;

u_int u_one = 1;
memset(&chat_addr, 0, sizeof(chat_addr));
addr.sin_family = AF_INET;
addr.sin.port = htons(chat_port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
mreq.imr_multiaddr.s_addr=inet_addr(chat_ip_address);
mreq.imr_interface.s_addr=htonl(INADDR_ANY);

if(
((rx_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) &&
(setsockopt(rx_fd,SOL_SOCKET,SO_REUSEADDR,&u_one,sizeof(u_one)) < 0) &&
(bind(rx_fd,(struct sockaddr *) &chat_addr,sizeof(chat_addr)) < 0) &&
(setsockopt(rx_fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)) < 0)
){
pline("Error while setting up chat system recieve socket.");
}
}

//TODO: Implement non-blocking message queue system.
void chat_rx(){
char str[BUFSZ];
recvfrom(rx_fd, str, BUFSZ, 0, (struct sockaddr *) &chat_addr, sizeof(chat_addr)){
pline(str); //ideally, enqueue here
}
}
320 src/cmd.c

Large diffs are not rendered by default.

@@ -4,6 +4,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"

int NDECL((*afternmv));
int NDECL((*occupation));
@@ -191,6 +192,12 @@ NEARDATA long moves = 1L, monstermoves = 1L;
/* These diverge when player is Fast */
NEARDATA long wailmsg = 0L;

/* Number of steps (not moves) the player has taken, used for awarding
the step count achievements. Since it's not stored in savegames, this
can only count as high as the highest step count achievement, and
some steps will be lost on save/load as per the "granularity" in hack.c */
int step_count_for_achievements = 0;

/* objects that are moving to another dungeon level */
NEARDATA struct obj *migrating_objs = (struct obj *)0;
/* objects not yet paid for */
@@ -7,6 +7,7 @@
#include <stdbool.h>
#include "hack.h"
#include "lev.h"
#include "achieve.h"

#ifdef SINKS
# ifdef OVLB
@@ -1180,6 +1181,7 @@ boolean at_stairs, falling, portal;
else if (u.dz &&
(near_capacity() > UNENCUMBERED || Punished || Fumbling)) {
You("fall down the %s.", at_ladder ? "ladder" : "stairs");
add_achievement_progress(AID_FALL_DOWN_STAIRS, 1);
if (Punished) {
drag_down();
if (carried(uball)) {
@@ -1299,6 +1301,8 @@ boolean at_stairs, falling, portal;
#endif
You_hear("groans and moans everywhere.");
} else pline("It is hot here. You smell smoke...");

award_achievement(AID_ENTER_GEHENNOM);

#ifdef RECORD_ACHIEVE
achieve.enter_gehennom = 1;
@@ -1380,6 +1384,9 @@ boolean at_stairs, falling, portal;
#ifdef WHEREIS_FILE
touch_whereis();
#endif

if (moves <= 2000 && Is_stronghold(&u.uz))
award_achievement(AID_DIG_FOR_VICTORY);
}

STATIC_OVL void
@@ -4,6 +4,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"

#ifndef OVLB

@@ -1648,6 +1649,10 @@ find_ac()
if(uac != u.uac){
u.uac = uac;
flags.botl = 1;
/* AC achievements (only check when AC has changed) */
if (u.uac > 10) award_achievement(AID_TERRIBLE_AC);
if (u.uac <= -30 && magic_negation(&youmonst) >= 3)
award_achievement(AID_AWESOME_AC);
}
}

@@ -5,6 +5,7 @@
#include <stdbool.h>
#include "hack.h"
#include "edog.h"
#include "achieve.h"

#ifdef OVLB

@@ -122,6 +123,12 @@ boolean quietly;
mtmp->weapon_check = NEED_HTH_WEAPON;
(void) mon_wield_item(mtmp);
}

if (mtmp->mtame &&
(mtmp->data == &mons[PM_SUCCUBUS] ||
mtmp->data == &mons[PM_INCUBUS])
) award_achievement(AID_TAME_FOOCUBUS);

return mtmp;
}

@@ -865,6 +872,9 @@ register struct obj *obj;
return mtmp2; /* oops, it died... */
/* `obj' is now obsolete */
}

if (mtmp2->data == &mons[PM_SUCCUBUS] || mtmp2->data == &mons[PM_INCUBUS])
award_achievement(AID_TAME_FOOCUBUS);

newsym(mtmp2->mx, mtmp2->my);
if (attacktype(mtmp2->data, AT_WEAP)) {
@@ -4,6 +4,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"
/* #define DEBUG */ /* uncomment to enable new eat code debugging */

#ifdef DEBUG
@@ -436,6 +437,9 @@ boolean message;
cpostfx(victual.piece->corpsenm);
else
fpostfx(victual.piece);

if (victual.piece->otyp == HUGE_CHUNK_OF_MEAT)
award_achievement(AID_EAT_HUGE_CHUNK_OF_MEAT);

if (carried(victual.piece)) useup(victual.piece);
else useupf(victual.piece, 1L);
@@ -454,6 +458,7 @@ boolean allowmsg;
You("have a bad feeling deep inside.");
You("cannibal! You will regret this!");
}
award_achievement(AID_CANNIBALISM);
HAggravate_monster |= FROMOUTSIDE;
change_luck(-rn1(4,2)); /* -5..-2 */
return TRUE;
@@ -1416,8 +1421,10 @@ struct obj *otmp;
"Mmm, tripe... not bad!");
else {
pline("Yak - dog food!");
int oldlevel = u.ulevel;
more_experienced(1,0);
newexplevel();
if (u.ulevel > oldlevel) award_achievement(AID_TRIVIAL_LEVEL_UP);
/* not cannibalism, but we use similar criteria
for deciding whether to be sickened by this meal */
if (rn2(2) && !CANNIBAL_ALLOWED())
@@ -348,6 +348,10 @@ register struct monst *mtmp;
Sprintf(eos(buf), ", while %s", multi_txt);
else
Strcat(buf, ", while helpless");
// Covers some things that aren't strictly "while helpless", but
// since multi_txt is now used for practically everything that used
// to be "while helpless", making the distinction is difficult
if (!Lifesaved) award_achievement(AID_KILLED_HELPLESS);
}
killer = buf;
if (mtmp->data->mlet == S_WRAITH)
@@ -361,6 +365,12 @@ register struct monst *mtmp;
if (u.ugrave_arise >= LOW_PM &&
(mvitals[u.ugrave_arise].mvflags & G_GENOD))
u.ugrave_arise = NON_PM;

/* Death achievements (see also "while helpless" above) */
if (!Lifesaved) {
if (mtmp->data->mlet == S_ANT) award_achievement(AID_KILLED_BY_ANT);
}

if (touch_petrifies(mtmp->data))
done(STONING);
else
@@ -957,6 +967,21 @@ int how;
}
#endif
}

/* Conduct achievements - placed after conduct disclosure */
if (how != PANICKED && u.ulevel >= 15) {
if (!u.uconduct.food) award_achievement(AID_CONDUCT_FOODLESS);
if (!u.uconduct.unvegetarian) award_achievement(AID_CONDUCT_VEGETARIAN);
if (!u.uconduct.gnostic) award_achievement(AID_CONDUCT_ATHEIST);
if (!u.uconduct.killer) award_achievement(AID_CONDUCT_PACIFIST);
if (!u.uconduct.weaphit) award_achievement(AID_CONDUCT_WEAPONLESS);
if (!u.uconduct.literate) award_achievement(AID_CONDUCT_ILLITERATE);
if (!u.uconduct.polypiles && !u.uconduct.polyselfs)
award_achievement(AID_CONDUCT_NOPOLYMORPH);
if (num_genocides() == 0) award_achievement(AID_CONDUCT_GENOCIDELESS);
if (!u.uconduct.wishes) award_achievement(AID_CONDUCT_WISHLESS);
}

/* finish_paybill should be called after disclosure but before bones */
if (bones_ok && taken) finish_paybill();
#ifdef DEATH_EXPLORE
@@ -1246,6 +1271,8 @@ int how;
if (dump_fp) dump_exit();
#endif

reset_single_game_achievements();

if(done_stopprint) { raw_print(""); raw_print(""); }
terminate(EXIT_SUCCESS);
}
@@ -400,7 +400,7 @@ register xchar e_type;
}
#endif
if(!strcmp(s, "Elbereth")){
award_achievement(AID_ELBERETH);
award_achievement(AID_ENGRAVE_ELBERETH);
}
ep->engr_time = e_time;
ep->engr_type = e_type > 0 ? e_type : rnd(N_ENGRAVE-1);
@@ -6,6 +6,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"

STATIC_DCL void NDECL(dowatersnakes);
STATIC_DCL void NDECL(dowaterdemon);
@@ -574,8 +575,10 @@ drinksink()
pline("But it quiets down.");
break;
case 8: pline("Yuk, this water tastes awful.");
int oldlevel = u.ulevel;
more_experienced(1,0);
newexplevel();
if (u.ulevel > oldlevel) award_achievement(AID_TRIVIAL_LEVEL_UP);
break;
case 9: pline("Gaggg... this tastes like sewage! You vomit.");
morehungry(rn1(30-ACURR(A_CON), 11));
@@ -4,6 +4,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"

#ifdef OVL1
STATIC_DCL void NDECL(maybe_wail);
@@ -1404,6 +1405,13 @@ domove()
/* Since the hero has moved, adjust what can be seen/unseen. */
vision_recalc(1); /* Do the work now in the recover time. */
invocation_message();

/* Do step achievement progress */
step_count_for_achievements++;
if (!(step_count_for_achievements % 100)) {
add_achievement_progress(AID_WALK_5K, 100);
add_achievement_progress(AID_WALK_10K, 100);
}
}

if (Punished) /* put back ball and chain */
@@ -1457,6 +1465,8 @@ invocation_message()
if (otmp && otmp->spe == 7 && otmp->lamplit)
pline("%s %s!", The(xname(otmp)),
Blind ? "throbs palpably" : "glows with a strange light");

award_achievement(AID_FIND_VIBE_SQUARE);
}
}

@@ -2085,7 +2095,10 @@ const char *txt;
(void) strncpy(multi_txt, txt, BUFSZ);
else
(void) memset(multi_txt, 0, BUFSZ);
flags.travel = iflags.travel1 = flags.mv = flags.run = 0;
flags.travel = 0;
iflags.travel1 = 0;
flags.mv = 0;
flags.run = 0;
}

/* called when a non-movement, multi-turn action has completed */
@@ -4,6 +4,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"

#define NOINVSYM '#'
#define CONTAINED_SYM '>' /* designator for inside a container */
@@ -278,24 +279,28 @@ struct obj *obj;
} else if (obj->otyp == AMULET_OF_YENDOR) {
if (u.uhave.amulet) impossible("already have amulet?");
u.uhave.amulet = 1;
award_achievement(AID_GET_AMULET);
#ifdef RECORD_ACHIEVE
achieve.get_amulet = 1;
#endif
} else if (obj->otyp == CANDELABRUM_OF_INVOCATION) {
if (u.uhave.menorah) impossible("already have candelabrum?");
u.uhave.menorah = 1;
award_achievement(AID_GET_CANDELABRUM);
#ifdef RECORD_ACHIEVE
achieve.get_candelabrum = 1;
#endif
} else if (obj->otyp == BELL_OF_OPENING) {
if (u.uhave.bell) impossible("already have silver bell?");
u.uhave.bell = 1;
award_achievement(AID_GET_BELL);
#ifdef RECORD_ACHIEVE
achieve.get_bell = 1;
#endif
} else if (obj->otyp == SPE_BOOK_OF_THE_DEAD) {
if (u.uhave.book) impossible("already have the book?");
u.uhave.book = 1;
award_achievement(AID_GET_BOOK);
#ifdef RECORD_ACHIEVE
achieve.get_book = 1;
#endif
@@ -390,6 +395,18 @@ struct obj *obj;
addinv_core2(obj);
carry_obj_effects(obj); /* carrying affects the obj */
update_inventory();

/* Inventory-related achievements */
if (obj->oclass == AMULET_CLASS) {
int amulets_exist = 0;
int amulets_gotten = 0;
for(int atyp = AMULET_OF_ESP; atyp <= AMULET_OF_YENDOR; atyp++) {
amulets_exist++;
if (in_possession(atyp)) amulets_gotten++;
}
if (amulets_exist == amulets_gotten) award_achievement(AID_GET_ALL_AMULETS);
}

return(obj);
}

@@ -719,6 +736,27 @@ register struct obj *objchn;
return((struct obj *) 0);
}

boolean
otype_on(type, objchn)
register int type;
register struct obj *objchn;
{
while(objchn) {
if(objchn->otyp == type) return(TRUE);
if (Has_contents(objchn) && otype_on(type, objchn->cobj))
return(TRUE);
objchn = objchn->nobj;
}
return(FALSE);
}

boolean
in_possession(type)
register int type;
{
return otype_on(type, invent);
}

boolean
obj_here(obj, x, y)
register struct obj *obj;
@@ -390,7 +390,7 @@ struct mail_info *info;
message_seen = TRUE;
verbalize("%s, %s! %s", Hello(md), plname, info->display_txt);

award_achievement(AID_MAIL);
award_achievement(AID_MAIL_RECEIVE);

if (info->message_typ) {
struct obj *obj = mksobj(SCR_MAIL, FALSE, FALSE);
@@ -12,6 +12,7 @@
#include "mfndpos.h"
#include "edog.h"
#include <ctype.h>
#include "achieve.h"

STATIC_DCL boolean FDECL(restrap,(struct monst *));
STATIC_DCL long FDECL(mm_aggression, (struct monst *,struct monst *));
@@ -1910,6 +1911,27 @@ xkilled(mtmp, dest)
change_luck(-5);
You_feel("guilty...");
}

/* Kill-related achievements */
if (u.uhp <= 5) award_achievement(AID_SKIN_OF_TEETH);
if (is_undead(mdat)) add_achievement_progress(AID_KILL_UNDEAD, 1);
if (mdat == &mons[PM_DEMOGORGON]) award_achievement(AID_KILL_DEMOGORGON);
if (mdat == &mons[PM_GHOST]) add_achievement_progress(AID_KILL_GHOSTS, 1);
if (mdat == &mons[PM_GRID_BUG]) add_achievement_progress(AID_KILL_GRID_BUGS, 1);
if (mdat == &mons[PM_WOODCHUCK]) award_achievement(AID_KILL_WOODCHUCK);
if (mdat->mlet == S_ORC) add_achievement_progress(AID_KILL_ORCS, 1);
if (mdat->mlet == S_ORC) add_achievement_progress(AID_KILL_ORCS, 1);
if (mdat->mlet == S_DRAGON) add_achievement_progress(AID_KILL_DRAGONS, 1);
#ifdef KOPS
if (mdat->mlet == S_KOP) award_achievement(AID_FOUGHT_THE_LAW);
#endif
if (
(is_dprince(mdat) || is_dlord(mdat)) &&
(mvitals[PM_JUIBLEX].mvflags & G_GONE) &&
(mvitals[PM_ORCUS].mvflags & G_GONE) &&
(mvitals[PM_BAALZEBUB].mvflags & G_GONE) &&
(mvitals[PM_ASMODEUS].mvflags & G_GONE)
) award_achievement(AID_KILL_GUARANTEED_DEMONS);

/* give experience points */
tmp = experience(mtmp, (int)mvitals[mndx].died + 1);
@@ -1958,6 +1980,7 @@ mon_to_stone(mtmp)
if (newcham(mtmp, &mons[PM_STONE_GOLEM], FALSE, FALSE)) {
if(canseemon(mtmp))
pline("Now it's %s.", an(mtmp->data->mname));
award_achievement(AID_STONE_A_GOLEM);
} else {
if(canseemon(mtmp))
pline("... and returns to normal.");
@@ -2612,7 +2635,11 @@ boolean msg; /* "The oldmon turns into a newmon!" */
}
}
}


if (mtmp->mtame &&
(mdat == &mons[PM_SUCCUBUS] || mdat == &mons[PM_INCUBUS])
) award_achievement(AID_TAME_FOOCUBUS);

return(1);
}

@@ -13,8 +13,8 @@

static bool mysql_library_disabled = false;

static char *nh_dlerror() {
char *err = dlerror();
static const char *nh_dlerror() {
const char *err = dlerror();
return err == NULL ? "dylib routine failed and no error message" : err;
}

@@ -781,6 +781,9 @@ POTION("water", "clear", 0, 0, 92, 100, CLR_CYAN),
SCROLL((char *)0, "GARVEN DEH", 1, 0, 100),
SCROLL((char *)0, "SEGFAULT", 1, 0, 100),
SCROLL((char *)0, "READ ME", 1, 0, 100),
SCROLL((char *)0, "NEBBOC", 1, 0, 100),
SCROLL((char *)0, "WORDHORD", 1, 0, 100),
SCROLL((char *)0, "PALIMPSEST PLURALIS", 1, 0, 100),
/* these must come last because they have special descriptions */
#ifdef MAIL
SCROLL("mail", "stamped", 0, 0, 0),
@@ -8,6 +8,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"

STATIC_DCL void FDECL(simple_look, (struct obj *,BOOLEAN_P));
#ifndef GOLDOBJ
@@ -2158,6 +2159,7 @@ struct obj *box;
Hallucination ? rndmonnam() : "housecat");
}
box->owt = weight(box);
award_achievement(AID_SCHROEDINGERS_CAT);
return;
}

@@ -12,6 +12,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"

#ifdef OVLB
STATIC_DCL void FDECL(polyman, (const char *,const char *));
@@ -577,6 +578,7 @@ int mntmp;
exercise(A_CON, FALSE);
exercise(A_WIS, TRUE);
(void) encumber_msg();
award_achievement(AID_POLYMORPH_SELF);
return(1);
}

@@ -4,6 +4,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"

#ifdef OVLB
boolean notonhead = FALSE;
@@ -1901,6 +1902,7 @@ dodip()
if (obj->cursed || obj->otyp == POT_ACID ||
potion->cursed || potion->otyp == POT_ACID || !rn2(10)) {
pline("BOOM! They explode!");
award_achievement(AID_ALCHEMIZE_EXPLOSION);
exercise(A_STR, FALSE);
if (!breathless(youmonst.data) || haseyes(youmonst.data))
potionbreathe(obj);
@@ -1918,6 +1920,8 @@ dodip()

if ((mixture = mixtype(obj, potion)) != 0) {
obj->otyp = mixture;
if (mixture == POT_GAIN_LEVEL)
award_achievement(AID_ALCHEMIZE_GAIN_LEVEL);
} else {
switch (obj->odiluted ? 1 : rnd(8)) {
case 1:
@@ -2173,6 +2177,7 @@ dodip()
pline("Warning, Captain! The warp core has been breached!");
}
pline("BOOM! %s explodes!", The(xname(singlegem)));
award_achievement(AID_ALCHEMIZE_EXPLOSION);
exercise(A_STR, FALSE);
if (!breathless(youmonst.data) || haseyes(youmonst.data))
potionbreathe(singlepotion);
@@ -2277,6 +2282,7 @@ register struct obj *obj;
case 0 : verbalize("I am in your debt. I will grant one wish!");
makewish();
mongone(mtmp);
add_achievement_progress(AID_DJINN_WISHES, 1);
break;
case 1 : verbalize("Thank you for freeing me!");
(void) tamedog(mtmp, (struct obj *)0);
@@ -5,6 +5,7 @@
#include <stdbool.h>
#include "hack.h"
#include "epri.h"
#include "achieve.h"

STATIC_PTR int NDECL(prayer_done);
STATIC_DCL struct obj *NDECL(worst_cursed_item);
@@ -524,6 +525,7 @@ aligntyp resp_god;
else {
You("bask in its %s glow for a minute...", NH_BLACK);
godvoice(resp_god, "I believe it not!");
award_achievement(AID_SURVIVE_GOD_DISINTEGRATION);
}
if (Is_astralevel(&u.uz) || Is_sanctum(&u.uz)) {
/* one more try for high altars */
@@ -840,7 +842,8 @@ pleased(g_align)
break;

case 1: if (trouble > 0) fix_worst_trouble(trouble);
case 0: break; /* your god blows you off, too bad */
case 0: if (trouble > 0) award_achievement(AID_UNCARING_GOD);
break; /* your god blows you off, too bad */
}
}

@@ -1299,6 +1302,8 @@ dosacrifice()
verbalize("In return for thy service, I grant thee the gift of Immortality!");
You("ascend to the status of Demigod%s...",
flags.female ? "dess" : "");
award_achievement(AID_ASCEND);
add_achievement_progress(AID_ASCEND_THRICE, 1);
done(ASCENDED);
}
}
@@ -1400,7 +1405,9 @@ verbalize("In return for thy service, I grant thee the gift of Immortality!");
hcolor(
u.ualign.type == A_LAWFUL ? NH_WHITE :
u.ualign.type ? NH_BLACK : (const char *)"gray"));


award_achievement(AID_CONVERT_ALTAR);

if (rnl(u.ulevel) > 6 && u.ualign.record > 0 &&
rnd(u.ualign.record) > (3*ALIGNLIM)/4)
summon_minion(altaralign, TRUE);
@@ -9,6 +9,7 @@

#include "quest.h"
#include "qtext.h"
#include "achieve.h"

#define Not_firsttime (on_level(&u.uz0, &u.uz))
#define Qstat(x) (quest_status.x)
@@ -266,6 +267,7 @@ chat_with_leader()
qt_pager(QT_ASSIGNQUEST);
exercise(A_WIS, TRUE);
Qstat(got_quest) = TRUE;
award_achievement(AID_START_QUEST);
}
}
}
@@ -4,6 +4,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"

/* KMH -- Copied from pray.c; this really belongs in a header file */
#define DEVOUT 14
@@ -859,6 +860,10 @@ register struct obj *sobj;
adj_abon(otmp, s);
known = otmp->known;
}

/* Weapon condition located in wield.c */
if (otmp->spe >= (is_elven_armor(otmp) ? 7 : 5))
award_achievement(AID_ENCHANT_HIGH);

if ((otmp->spe > (special_armor ? 5 : 3)) &&
(special_armor || !rn2(7)))
@@ -1758,6 +1763,7 @@ int how;
mvitals[mndx].mvflags |= (G_GENOD | G_NOCORPSE);
pline("Wiped out %s%s.", which,
(*which != 'a') ? buf : makeplural(buf));
add_achievement_progress(AID_GENOCIDES, 1);

if (killplayer) {
/* might need to wipe out dual role */
@@ -1845,6 +1851,8 @@ register struct obj *sobj;
if (Blind) set_bc(1); /* set up ball and chain variables */
newsym(u.ux,u.uy); /* see ball&chain if can't see self */
}

award_achievement(AID_PUNISHMENT);
}

void
@@ -6,6 +6,7 @@
#include "hack.h"
#include "lev.h"
#include "tcap.h" /* for TERMLIB and ASCIIGRAPH */
#include "achieve.h"

#if defined(MICRO)
extern int dotcnt; /* shared with save */
@@ -682,6 +683,9 @@ register int fd;
/* but before docrt(). */
vision_reset();
vision_full_recalc = 1; /* recompute vision (not saved) */

/* Restore step count (roughly) from the highest step achievement */
step_count_for_achievements = get_achievement_progress(AID_WALK_10K);

run_timers(); /* expire all timers that have gone off while away */
docrt();
@@ -181,7 +181,7 @@ int mechanism;
}
else{
truth=3; /* We're talking to potter, we want the Potter quotes */
award_achievement(AID_POTTER);
award_achievement(AID_POTTER_CHAT);
}
line = getrumor(truth, buf, reading ? FALSE : TRUE);
if (!*line)
@@ -4,6 +4,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"

static NEARDATA schar delay; /* moves left for this spell */
static NEARDATA struct obj *book; /* last/current book being xscribed */
@@ -258,6 +259,25 @@ struct obj *book2;
you just did */
u.uevent.udemigod = 1; /* wizdead() */
if (!u.udg_cnt || u.udg_cnt > soon) u.udg_cnt = soon;

/* "ascension kit" achievement */
/* TODO: determine how much this would suck for performance if it
were moved to addinv (making it a bit more achievable) */
if (
in_possession(WAN_DIGGING) &&
in_possession(SCR_GOLD_DETECTION) &&
in_possession(RIN_LEVITATION) &&
in_possession(POT_FULL_HEALING) &&
(
in_possession(AMULET_OF_REFLECTION) ||
in_possession(AMULET_OF_LIFE_SAVING)
) &&
in_possession(UNICORN_HORN) &&
in_possession(LUCKSTONE) &&
in_possession(TOWEL) &&
in_possession(CAN_OF_GREASE) &&
in_possession(BAG_OF_HOLDING)
) award_achievement(AID_ASCENSION_KIT);
} else { /* at least one artifact not prepared properly */
You("have a feeling that %s is amiss...", something);
goto raise_dead;
@@ -841,6 +861,9 @@ boolean atme;
flags.botl = 1;
return(1);
}

if (chance == 100 && spellev(spell) >= 7)
award_achievement(AID_MASTER_CASTER);

if (crball_divin(uwep,spell)) {
You_feel("your mind expand as you focus on %s.",yname(uwep));
@@ -966,8 +989,11 @@ boolean atme;
break;

case SPE_CURE_BLINDNESS:
healup(0, 0, FALSE, TRUE);
break;
if(haseyes(youmonst.data))
healup(0, 0, FALSE, TRUE);
else
You("spend a few minutes willing your face to have eyes, then give up in disgust.");
break;
case SPE_CURE_SICKNESS:
if (Sick) You("are no longer ill.");
if (Slimed) {
@@ -5,6 +5,7 @@
#include <stdlib.h>
#include <stdbool.h>
#include <strings.h>
#include <stdint.h>
#include <assert.h>
#include <errno.h>

@@ -4,6 +4,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"

extern const char * const destroy_strings[]; /* from zap.c */

@@ -3289,8 +3290,13 @@ struct trap *ttmp;
You("repair the squeaky board."); /* no madeby_u */
deltrap(ttmp);
newsym(u.ux + u.dx, u.uy + u.dy);
/* Implementation of this achievement is not great (see also
fountain.c and eat.c). Maybe change newexplevel to return
whether or not a level-up occurred? */
int oldlevel = u.ulevel;
more_experienced(1, 5);
newexplevel();
if (u.ulevel > oldlevel) award_achievement(AID_TRIVIAL_LEVEL_UP);
return 1;
}

@@ -4,6 +4,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"

STATIC_DCL boolean FDECL(known_hitum, (struct monst *,int *,struct attack *));
STATIC_DCL void FDECL(steal_it, (struct monst *, struct attack *));
@@ -537,6 +538,7 @@ int thrown;
* a hit message.
*/
boolean hittxt = FALSE, destroyed = FALSE, already_killed = FALSE;
boolean smashed_mirror = FALSE; /* for achievement */
boolean get_dmg_bonus = TRUE;
boolean ispoisoned = FALSE, needpoismsg = FALSE, poiskilled = FALSE;
boolean silvermsg = FALSE, silverobj = FALSE;
@@ -726,6 +728,7 @@ int thrown;
unarmed = FALSE; /* avoid obj==0 confusion */
get_dmg_bonus = FALSE;
hittxt = TRUE;
smashed_mirror = TRUE;
}
tmp = 1;
break;
@@ -747,8 +750,11 @@ int thrown;
obj->dknown ? the(mons[obj->corpsenm].mname) :
an(mons[obj->corpsenm].mname),
(obj->quan > 1) ? makeplural(withwhat) : withwhat);
if (!munstone(mon, TRUE))
if (!munstone(mon, TRUE)) {
minstapetrify(mon, TRUE);
if (!resists_ston(mon) && !poly_when_stoned(mon->data))
award_achievement(AID_RUBBER_CHICKEN);
}
if (resists_ston(mon)) break;
/* note: hp may be <= 0 even if munstoned==TRUE */
return (boolean) (mon->mhp > 0);
@@ -862,6 +868,8 @@ int thrown;
pline(obj->otyp==CREAM_PIE ? "Splat!" : "Splash!");
setmangry(mon);
}
if (obj->otyp == CREAM_PIE && attacktype(mon->data, AT_GAZE))
award_achievement(AID_BLIND_GAZER_WITH_PIE);
if (thrown) obfree(obj, (struct obj *)0);
else useup(obj);
hittxt = TRUE;
@@ -1009,8 +1017,10 @@ int thrown;
/* adjustments might have made tmp become less than what
a level draining artifact has already done to max HP */
if (mon->mhp > mon->mhpmax) mon->mhp = mon->mhpmax;
if (mon->mhp < 1)
if (mon->mhp < 1) {
destroyed = TRUE;
if (smashed_mirror) award_achievement(AID_MIRROR_SMASH_KILL);
}
if (mon->mtame && (!mon->mflee || mon->mfleetim) && tmp > 0) {
abuse_dog(mon);
monflee(mon, 10 * rnd(tmp), FALSE, FALSE);
@@ -283,7 +283,7 @@ incr_radius: ;
if (!mvitals[PM_CROESUS].died) {
verbalize("Oh, yes, of course. Sorry to have disturbed you.");
mongone(guard);
award_achievement(AID_VAULT);
award_achievement(AID_VAULT_LIE);
} else {
setmangry(guard);
verbalize("Back from the dead, are you? I'll remedy that!");
@@ -4,6 +4,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"

/* KMH -- Differences between the three weapon slots.
*
@@ -751,6 +752,9 @@ register int amount;
}
uwep->spe += amount;
if(amount > 0) uwep->cursed = 0;

/* Armor condition located in read.c */
if (uwep->spe >= 7) award_achievement(AID_ENCHANT_HIGH);

/*
* Enchantment, which normally improves a weapon, has an
@@ -4,6 +4,7 @@

#include <stdbool.h>
#include "hack.h"
#include "achieve.h"

/* Disintegration rays have special treatment; corpses are never left.
* But the routine which calculates the damage is separate from the routine
@@ -373,6 +374,7 @@ struct obj *otmp;
!canspotmon(mtmp))
map_invisible(bhitpos.x, bhitpos.y);
}
if (dbldam) award_achievement(AID_USE_ARTIFACT_KNI);
return 0;
}

@@ -675,6 +677,7 @@ register struct obj *obj;
Monnam(ghost));
mondead(ghost);
recorporealization = TRUE;
add_achievement_progress(AID_RESURRECT_PLAYERS, 1);
newsym(x2, y2);
}
/* don't mess with obj->oxlth here */
@@ -1270,8 +1273,10 @@ poly_obj(obj, id)
boolean can_merge = (id == STRANGE_OBJECT);
int obj_location = obj->where;

if (obj->otyp == BOULDER && In_sokoban(&u.uz))
if (obj->otyp == BOULDER && In_sokoban(&u.uz)) {
change_luck(-1); /* Sokoban guilt */
award_achievement(AID_SOKOBAN_DESTROY_BOULDER);
}
if (id == STRANGE_OBJECT) { /* preserve symbol */
int try_limit = 3;
/* Try up to 3 times to make the magic-or-not status of
@@ -1816,8 +1821,12 @@ register struct obj *wand;
{
if(wand->spe < 0 || (wand->spe == 0 && rn2(121)))
return 0;
if(wand->spe == 0)
if(wand->spe == 0) {
You("wrest one last charge from the worn-out wand.");
if (wand->otyp == WAN_WISHING && wand->recharged > 0) {
award_achievement(AID_WREST_FROM_RECHARGED_WOW);
}
}
wand->spe--;
return 1;
}
@@ -3103,6 +3112,9 @@ struct obj **ootmp; /* to return worn armor for caller to disintegrate */
sho_shieldeff = TRUE;
break;
}
if (mon->data == &mons[PM_WIZARD_OF_YENDOR]) {
award_achievement(AID_ZAP_RODNEY_WITH_DEATH);
}
type = -1; /* so they don't get saving throws */
} else {
struct obj *otmp2;
@@ -3177,8 +3189,10 @@ struct obj **ootmp; /* to return worn armor for caller to disintegrate */
break;
}
if (sho_shieldeff) shieldeff(mon->mx, mon->my);
if (is_hero_spell(type) && (Role_if(PM_KNIGHT) && u.uhave.questart))
if (is_hero_spell(type) && (Role_if(PM_KNIGHT) && u.uhave.questart)) {
tmp *= 2;
award_achievement(AID_USE_ARTIFACT_KNI);
}
if (tmp > 0 && type >= 0 &&
resist(mon, type < ZT_SPELL(0) ? WAND_CLASS : '\0', 0, NOTELL))
tmp /= 2;
@@ -3890,8 +3904,10 @@ fracture_rock(obj) /* fractured by pick-axe or wand of striking */
register struct obj *obj; /* no texts here! */
{
/* A little Sokoban guilt... */
if (obj->otyp == BOULDER && In_sokoban(&u.uz) && !flags.mon_moving)
if (obj->otyp == BOULDER && In_sokoban(&u.uz) && !flags.mon_moving) {
change_luck(-1);
award_achievement(AID_SOKOBAN_DESTROY_BOULDER);
}

obj->otyp = ROCK;
obj->quan = (long) rn1(60, 7);

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

@@ -5,16 +5,23 @@
/* main.c - Unix NetHack */

#include <stdbool.h>
#include "hack.h"
#include "dlb.h"

#include <errno.h>
#include <sys/stat.h>
#include <signal.h>
#include <pwd.h>
#ifndef O_RDONLY
#include <fcntl.h>
#endif

#include <mysql.h>
#include <libconfig.h>

#include "hack.h"
#include "dlb.h"
#include "mysql_library.h"
#include "achieve.h"
#include "configfile.h"

#if !defined(_BULL_SOURCE) && !defined(__sgi) && !defined(_M_UNIX)
# if !defined(SUNOS4) && !(defined(ULTRIX) && defined(__GNUC__))
# if defined(POSIX_TYPES) || defined(SVR4) || defined(HPUX)
@@ -49,6 +56,18 @@ static void NDECL(wd_message);
static boolean wiz_error_flag = FALSE;
#endif

static void segv_award( int sig ) {
if( signal(SIGSEGV, SIG_DFL) == SIG_ERR ) {
perror("signal");
exit(EXIT_FAILURE);
}
award_achievement(AID_CRASH);
if( kill(getpid(), SIGSEGV) == -1 ) {
perror("kill");
exit(EXIT_FAILURE);
}
}

int
main(argc,argv)
int argc;
@@ -255,6 +274,12 @@ char *argv[];

display_gamewindows();

if( signal(SIGSEGV, segv_award) == SIG_ERR ) pline("Unable to register signal handler: %s", strerror(errno));

configfile_init();
mysql_library_startup();
achievement_system_startup();

if ((fd = restore_saved_game()) >= 0) {
#ifdef WIZARD
/* Since wizard is actually flags.debug, restoring might
@@ -281,6 +306,8 @@ char *argv[];
#endif
check_special_room(FALSE);
wd_message();
// Can't do this until after plname is set!
if (!achievements_user_exists()) achievements_register_user();

if (discover || wizard) {
if(yn("Do you want to keep the save file?") == 'n')
@@ -296,12 +323,15 @@ char *argv[];
player_selection();
newgame();
wd_message();
// See above comment
if (!achievements_user_exists()) achievements_register_user();
reset_single_game_achievements();

flags.move = 0;
set_wear();
(void) pickup(1);
}

moveloop();
exit(EXIT_SUCCESS);
/*NOTREACHED*/
@@ -130,7 +130,6 @@ main( int argc, char *argv[] )
struct u_stat_t u_stat;
struct sockaddr_in addr;
socklen_t addrlen;
struct hostent *hent;
u_int yes = 1;
bool first_time = true;
const char topl[] =
@@ -147,8 +146,6 @@ main( int argc, char *argv[] )

gethostname(name, sizeof(name));

hent = gethostbyname(name);

if( setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) != 0 ) {
perror("setsockopt");
exit(EXIT_FAILURE);
0 util/rotator 100644 → 100755
Empty file.
@@ -129,7 +129,6 @@ main( int argc, char *argv[] )
struct u_stat_t u_stat;
struct sockaddr_in addr, from;
socklen_t fromlen;
struct hostent *hent;
u_int yes = 1;
bool first_time = true;
int i;
@@ -142,8 +141,6 @@ main( int argc, char *argv[] )

gethostname(name, sizeof(name));

hent = gethostbyname(name);

if( setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) != 0 ) {
perror("setsockopt");
exit(EXIT_FAILURE);

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.