Index: lang/english.txt =================================================================== --- lang/english.txt (revision 6502) +++ lang/english.txt (working copy) @@ -3012,3 +3012,39 @@ STR_HUB_AIRPORTS :{BLACK}Hub airports STR_HELIPORTS :{BLACK}Helicopter airports ######## + + +############ New face screen +STR_FACE_CHIN :{BLACK}Chin +STR_FACE_CHIN_TIP :{BLACK}Change chin +STR_FACE_EYES :{BLACK}Eyes +STR_FACE_EYES_TIP :{BLACK}Change eyes +STR_FACE_EYECOLOUR :{BLACK}Eye Colour +STR_FACE_EYECOLOUR_TIP :{BLACK}Change eye colour +STR_FACE_MOUTHNOSE :{BLACK}Mouth & Nose +STR_FACE_MOUTHNOSE_TIP :{BLACK}Change mouth & nose +STR_FACE_HAIR :{BLACK}Hair +STR_FACE_HAIR_TIP :{BLACK}Change hair +STR_FACE_SUITCOLLAR :{BLACK}Suit & Collar +STR_FACE_SUITCOLLAR_TIP :{BLACK}Change suit & collar +STR_FACE_TIEEARRING :{BLACK}Tie / Earring +STR_FACE_TIEEARRING_TIP :{BLACK}Change tie / earring +STR_FACE_GLASSES :{BLACK}Glasses +STR_FACE_GLASSES_TIP :{BLACK}Change glasses +STR_FACE_GENDERETH :{BLACK}Gender & Ethnicity +STR_FACE_GENDERETH_TIP :{BLACK}Change gender & ethnicity +STR_FACE_RANDOMIZE :{BLACK}Randomize +STR_FACE_RANDOMIZE_TIP :{BLACK}Generate random new face (gender & ethnicity are retained) +STR_FACE_LOAD :{BLACK}Load +STR_FACE_LOAD_TIP :{BLACK}Load favourite face +STR_FACE_LOAD_DONE :{WHITE}Your favourite face has been loaded from the OpenTTD config file. +STR_FACE_LOAD_NOFAVE :{WHITE}Couldn't load face - you do not have a favourite face saved! +STR_FACE_SAVE :{BLACK}Save +STR_FACE_SAVE_TIP :{BLACK}Save favourite face +STR_FACE_SAVE_DONE :{WHITE}This face will be saved as your favourite in the OpenTTD config file. +STR_FACE_FACECODE :{BLACK}Face Number Code +STR_FACE_FACECODE_TIP :{BLACK}View / set face number code +STR_FACE_FACECODE_CAPTION :{WHITE}View / Set Face Number Code +STR_FACE_FACECODE_SET :{WHITE}New face number code has been set. +STR_FACE_FACECODE_ERR :{WHITE}Couldn't set face number code - must be numeric, between 0 and 4,294,967,295! +######## Index: player.h =================================================================== --- player.h (revision 6502) +++ player.h (working copy) @@ -199,6 +199,53 @@ uint16 num_engines[TOTAL_NUM_ENGINES]; // caches the number of engines of each type the player owns (no need to save this) } Player; +/* Some useful player face macros */ +#define FC_GENDER_ETH_VAL GB(WP(w,facesel_d).face, 30, 2) +#define FC_GENDER_ETH_COUNT gender_eth_lookup[FC_GENDER_ETH_VAL] +#define FC_CM 0 +#define FC_CF 1 +#define FC_BM 2 +#define FC_BF 3 +#define FC_HAIR 0 +#define FC_GLASSES 1 +#define FC_EYES 2 +#define FC_EYECOL 3 +#define FC_MOUTHNOSE 4 +#define FC_CHIN 5 +#define FC_SUIT 6 +#define FC_COLLAR 7 +#define FC_SUITCOL 8 +#define FC_TIEEAR 9 +#define FC_GETEYECOL GB(WP(w,facesel_d).face, 0, 4) +#define FC_SETEYECOL(val) SB(WP(w,facesel_d).face, 0, 4, val) +#define FC_ADDEYECOL(amt) AB(WP(w,facesel_d).face, 0, 4, amt) +#define FC_GETCHIN GB(WP(w,facesel_d).face, 4, 2) +#define FC_SETCHIN(val) SB(WP(w,facesel_d).face, 4, 2, val) +#define FC_ADDCHIN(amt) AB(WP(w,facesel_d).face, 4, 2, amt) +#define FC_GETEYES GB(WP(w,facesel_d).face, 6, 4) +#define FC_SETEYES(val) SB(WP(w,facesel_d).face, 6, 4, val) +#define FC_ADDEYES(amt) AB(WP(w,facesel_d).face, 6, 4, amt) +#define FC_GETMOUTHNOSE GB(WP(w,facesel_d).face, 10, 7) +#define FC_SETMOUTHNOSE(val) SB(WP(w,facesel_d).face, 10, 7, val) +#define FC_ADDMOUTHNOSE(amt) AB(WP(w,facesel_d).face, 10, 7, amt) +#define FC_GETHAIR GB(WP(w,facesel_d).face, 17, 4) +#define FC_SETHAIR(val) SB(WP(w,facesel_d).face, 17, 4, val) +#define FC_ADDHAIR(amt) AB(WP(w,facesel_d).face, 17, 4, amt) +#define FC_GETSUITCOL GB(WP(w,facesel_d).face, 21, 4) +#define FC_SETSUITCOL(val) SB(WP(w,facesel_d).face, 21, 4, val) +#define FC_ADDSUITCOL(amt) AB(WP(w,facesel_d).face, 21, 4, amt) +#define FC_GETTIEEAR GB(WP(w,facesel_d).face, 25, 3) +#define FC_SETTIEEAR(val) SB(WP(w,facesel_d).face, 25, 3, val) +#define FC_ADDTIEEAR(amt) AB(WP(w,facesel_d).face, 25, 3, amt) +#define FC_GETGLASSES GB(WP(w,facesel_d).face, 28, 2) +#define FC_SETGLASSES(val) SB(WP(w,facesel_d).face, 28, 2, val) +#define FC_ADDGLASSES(amt) AB(WP(w,facesel_d).face, 28, 2, amt) +#define FC_GETGENDERETH GB(WP(w,facesel_d).face, 30, 2) +#define FC_SETGENDERETH(val) SB(WP(w,facesel_d).face, 30, 2, val) +#define FC_ADDGENDERETH(amt) AB(WP(w,facesel_d).face, 30, 2, amt) + +uint32 GetRandomFace(uint32 face, int retain); + uint16 GetDrawStringPlayerColor(PlayerID player); void ChangeOwnershipOfPlayerItems(PlayerID old_player, PlayerID new_player); Index: player_gui.c =================================================================== --- player_gui.c (revision 6502) +++ player_gui.c (working copy) @@ -17,6 +17,8 @@ #include "variables.h" #include "train.h" #include "date.h" +#include "strings.h" +#include #ifdef ENABLE_NETWORK #include "network_data.h" @@ -464,52 +466,561 @@ SelectPlayerLiveryWndProc }; +static const int gender_eth_lookup[4] = { + 0, 2, 1, 3 +}; +static const int gender_eth_next[4] = { + 2, 3, 1, 0 +}; + +/* Facial features' upper limits, for each of the 4 genders/ethnicities */ +static const uint face_limits[4][10] = { + {8, 2, 11, 12, 126, 3, 2, 3, 14, 5}, + {4, 2, 15, 12, 41, 0, 2, 3, 14, 3}, + {4, 2, 10, 0, 59, 1, 2, 3, 14, 5}, + {4, 2, 15, 0, 72, 1, 2, 3, 14, 3} +}; + static void SelectPlayerFaceWndProc(Window *w, WindowEvent *e) { + uint32 cl; + uint width, widgno, thisval; + int toadd; + int rand_lrtb[] = {3, 92, 137, 148}; + int load_lrtb[] = {95, 164, 125, 136}; + int save_lrtb[] = {165, 233, 125, 136}; + int code_lrtb[] = {95, 233, 137, 148}; + unsigned long facecode; + Widget btn; + char buffer[512], buffer2[1024]; + switch (e->event) { + case WE_CREATE: { + WP(w,facesel_d).data_1 = 0; // Fake-widget click state + } break; + case WE_PAINT: { Player *p; - w->click_state = (w->click_state & ~(1<<5|1<<6)) | ((1<<5) << WP(w,facesel_d).gender); + + /* Keep values within bounds */ + /* Hair */ + switch (FC_GENDER_ETH_COUNT) { + case 0: if (FC_GETHAIR > face_limits[FC_CM][FC_HAIR]) {FC_SETHAIR(face_limits[FC_CM][FC_HAIR]);} break; + case 1: + case 2: + case 3: if (FC_GETHAIR > face_limits[FC_CF][FC_HAIR]) {FC_SETHAIR(face_limits[FC_CF][FC_HAIR]);} break; + } + + /* Glasses */ + if (FC_GETGLASSES > face_limits[FC_CM][FC_GLASSES]) { FC_SETGLASSES(face_limits[FC_CM][FC_GLASSES]); } + + /* Eyes */ + switch (FC_GENDER_ETH_COUNT) { + case 0: if (FC_GETEYES > face_limits[FC_CM][FC_EYES]) {FC_SETEYES(face_limits[FC_CM][FC_EYES]);} break; + case 1: if (FC_GETEYES > face_limits[FC_CF][FC_EYES]) {FC_SETEYES(face_limits[FC_CF][FC_EYES]);} break; + case 2: if (FC_GETEYES > face_limits[FC_BM][FC_EYES]) {FC_SETEYES(face_limits[FC_BM][FC_EYES]);} break; + case 3: if (FC_GETEYES > face_limits[FC_BF][FC_EYES]) {FC_SETEYES(face_limits[FC_BF][FC_EYES]);} break; + } + + /* Eye colour */ + SB(w->disabled_state, 8, 1, 0); + SB(w->disabled_state, 20, 1, 0); + SB(w->disabled_state, 21, 1, 0); + switch (FC_GENDER_ETH_COUNT) { + case 0: + case 1: if (FC_GETEYECOL > face_limits[FC_CM][FC_EYECOL]) {FC_SETEYECOL(face_limits[FC_CM][FC_EYECOL]);} break; + case 2: + case 3: FC_SETEYECOL(0); + SB(w->disabled_state, 8, 1, 1); + SB(w->disabled_state, 20, 1, 1); + SB(w->disabled_state, 21, 1, 1); + break; + } + + /* Mouth & Nose */ + thisval = FC_GETMOUTHNOSE; + switch (FC_GENDER_ETH_COUNT) { + case 0: if (thisval > face_limits[FC_CM][FC_MOUTHNOSE]) {thisval=face_limits[FC_CM][FC_MOUTHNOSE];} + if ((thisval % 16) >= 15) {thisval-=((thisval%16)-14);} + break; + case 1: if (thisval > face_limits[FC_CF][FC_MOUTHNOSE]) {thisval=face_limits[FC_CF][FC_MOUTHNOSE];} + if ((thisval % 16) >= 10) {thisval-=((thisval%16)-9);} + break; + case 2: if (thisval > face_limits[FC_BM][FC_MOUTHNOSE]) {thisval=face_limits[FC_BM][FC_MOUTHNOSE];} + if ((thisval % 16) >= 12) {thisval-=((thisval%16)-11);} + break; + case 3: if (thisval > face_limits[FC_BF][FC_MOUTHNOSE]) {thisval=face_limits[FC_BF][FC_MOUTHNOSE];} + if ((thisval % 16) >= 9) {thisval-=((thisval%16)-8);} + break; + } + FC_SETMOUTHNOSE(thisval); + + /* Chin */ + SB(w->disabled_state, 10, 1, 0); + SB(w->disabled_state, 24, 1, 0); + SB(w->disabled_state, 25, 1, 0); + switch (FC_GENDER_ETH_COUNT) { + case 0: if (FC_GETCHIN > face_limits[FC_CM][FC_CHIN]) {FC_SETCHIN(face_limits[FC_CM][FC_CHIN]);} break; + case 1: SB(w->disabled_state, 10, 1, 1); + SB(w->disabled_state, 24, 1, 1); + SB(w->disabled_state, 25, 1, 1); + FC_SETCHIN(0); + break; + case 2: + case 3: if (FC_GETCHIN > face_limits[FC_BM][FC_CHIN]) {FC_SETCHIN(face_limits[FC_BM][FC_CHIN]);} break; + } + + /* Suit / Collar */ + thisval = FC_GETSUITCOL; + switch (FC_GENDER_ETH_COUNT) { + case 0: + case 1: + case 2: + case 3: if ((thisval % 4) >= 3) {thisval-=((thisval%4)-2);} + } + FC_SETSUITCOL(thisval); + + /* Tie / Earring */ + thisval = FC_GETTIEEAR; + switch (FC_GENDER_ETH_COUNT) { + case 0: + case 2: if (FC_GETTIEEAR > face_limits[FC_CM][FC_TIEEAR]) {FC_SETTIEEAR(face_limits[FC_CM][FC_TIEEAR]);} break; + case 1: + case 3: if (FC_GETTIEEAR > face_limits[FC_CF][FC_TIEEAR]) {FC_SETTIEEAR(face_limits[FC_CF][FC_TIEEAR]);} break; + } + DrawWindowWidgets(w); + + /* Draw dynamic button strings */ + /* Hair */ + widgno = 5; + GetString(buffer, STR_FACE_HAIR); + snprintf(buffer2, 1024, "%s (%u)", buffer, FC_GETHAIR); + width = GetStringBoundingBox(buffer2).width; + btn = w->widget[widgno]; + cl = w->click_state & (1<widget[widgno]; + cl = w->click_state & (1<widget[widgno]; + cl = w->click_state & (1<widget[widgno]; + cl = w->click_state & (1<disabled_state, widgno, 1)) { // Disabled? + GfxFillRect(btn.left+1, btn.top+1, btn.right-1, btn.bottom-1, _colour_gradient[btn.color&0xF][2] | PALETTE_MODIFIER_GREYOUT); + } + + /* Mouth & Nose */ + widgno = 9; + GetString(buffer, STR_FACE_MOUTHNOSE); +// thisval = GB(WP(w,facesel_d).face, 10, 7); +// switch (FC_GENDER_ETH_COUNT) { +// /* As there are different numbers of valid values for different */ +// /* genders and ethnicities, don't count the 'duplicate' entries */ +// /* in between each set of mouth/nose combinations. */ +// case 0: snprintf(buffer2, 1024, "%s (%u)", buffer, thisval-(thisval/16)*1); break; +// case 1: snprintf(buffer2, 1024, "%s (%u)", buffer, thisval-(thisval/16)*6); break; +// case 2: snprintf(buffer2, 1024, "%s (%u)", buffer, thisval-(thisval/16)*4); break; +// case 3: snprintf(buffer2, 1024, "%s (%u)", buffer, thisval-(thisval/16)*7); break; +// } + snprintf(buffer2, 1024, "%s (%u)", buffer, FC_GETMOUTHNOSE); + width = GetStringBoundingBox(buffer2).width; + btn = w->widget[widgno]; + cl = w->click_state & (1<widget[widgno]; + cl = w->click_state & (1<disabled_state, widgno, 1)) { + GfxFillRect(btn.left+1, btn.top+1, btn.right-1, btn.bottom-1, _colour_gradient[btn.color&0xF][2] | PALETTE_MODIFIER_GREYOUT); + } + + /* Suit / Collar */ + widgno = 11; + GetString(buffer, STR_FACE_SUITCOLLAR); + snprintf(buffer2, 1024, "%s (%u)", buffer, FC_GETSUITCOL); + width = GetStringBoundingBox(buffer2).width; + btn = w->widget[widgno]; + cl = w->click_state & (1<widget[widgno]; + cl = w->click_state & (1<widget[widgno]; + cl = w->click_state & (1<window_number); DrawPlayerFace(WP(w,facesel_d).face, p->player_color, 2, 16); } break; case WE_CLICK: + toadd = 0; + switch (e->we.click.widget) { - case 3: DeleteWindow(w); break; - case 4: /* ok click */ + case 2: /* Possible click on a fake widget */ + if (e->we.click.pt.x >= rand_lrtb[0] && e->we.click.pt.x <= rand_lrtb[1] && e->we.click.pt.y >= rand_lrtb[2] && e->we.click.pt.y <= rand_lrtb[3]) { + /* Randomize click */ + widgno = 0; + + WP(w,facesel_d).face = GetRandomFace(WP(w,facesel_d).face, 1); + } + else if (e->we.click.pt.x >= load_lrtb[0] && e->we.click.pt.x <= load_lrtb[1] && e->we.click.pt.y >= load_lrtb[2] && e->we.click.pt.y <= load_lrtb[3]) { + /* Load click */ + widgno = 1; + + if (_player_face == 0) {ShowErrorMessage(INVALID_STRING_ID, STR_FACE_LOAD_NOFAVE, 0, 0);} + else { + WP(w,facesel_d).face = _player_face; + ShowErrorMessage(INVALID_STRING_ID, STR_FACE_LOAD_DONE, 0, 0); + } + } + else if (e->we.click.pt.x >= save_lrtb[0] && e->we.click.pt.x <= save_lrtb[1] && e->we.click.pt.y >= save_lrtb[2] && e->we.click.pt.y <= save_lrtb[3]) { + /* Save click */ + widgno = 2; + + _player_face = WP(w,facesel_d).face; + ShowErrorMessage(INVALID_STRING_ID, STR_FACE_SAVE_DONE, 0, 0); + } + else if (e->we.click.pt.x >= code_lrtb[0] && e->we.click.pt.x <= code_lrtb[1] && e->we.click.pt.y >= code_lrtb[2] && e->we.click.pt.y <= code_lrtb[3]) { + /* Face Number Code click */ + widgno = 3; + + WP(w,facesel_d).data_2 = 0; // Face Number Code query dialog ID + snprintf(buffer, 512, "%u", WP(w,facesel_d).face); + ShowQueryString(BindCString(buffer), STR_FACE_FACECODE_CAPTION, 10+1, 0, w->window_class, w->window_number, CS_NUMERAL); + } + else {break;} + + WP(w,facesel_d).data_1 |= (1<flags4 |= 5 << WF_TIMEOUT_SHL; // Setup unclick (fake widget) + SetWindowDirty(w); + break; + case 3: /* Cancel click */ + DeleteWindow(w); + break; + case 4: /* OK click */ DoCommandP(0, 0, WP(w,facesel_d).face, NULL, CMD_SET_PLAYER_FACE); DeleteWindow(w); break; - case 5: /* male click */ - case 6: /* female click */ - WP(w,facesel_d).gender = e->we.click.widget - 5; + case 5: /* Hair */ + case 15: + toadd = 1; + case 14: /* (back) */ + if (!toadd) {toadd--;} + + FC_ADDHAIR(toadd); + switch (FC_GETHAIR) { // Reset? + case 5: if (FC_GENDER_ETH_COUNT > 0) {FC_SETHAIR(0);} break; + case 9: FC_SETHAIR(0); break; + } SetWindowDirty(w); break; - case 7: - WP(w,facesel_d).face = (WP(w,facesel_d).gender << 31) + GB(InteractiveRandom(), 0, 31); + case 6: /* Glasses */ + case 17: + toadd = 1; + case 16: /* (back) */ + if (!toadd) { + toadd = (FC_GETGLASSES == 0 ? 2 : toadd-1); + } + + FC_ADDGLASSES(toadd); + if (FC_GETGLASSES == 3) { /* Reset? */ FC_SETGLASSES(0);} SetWindowDirty(w); break; + case 7: /* Eyes */ + case 19: + toadd = 1; + case 18: /* (back) */ + if (!toadd) {toadd--;} + + FC_ADDEYES(toadd); + switch (FC_GETEYES) { // Reset? + case 11: if (FC_GENDER_ETH_COUNT == 2) {FC_SETEYES(0);} break; + case 12: if (FC_GENDER_ETH_COUNT == 0) {FC_SETEYES(0);} break; + } + SetWindowDirty(w); + break; + case 8: /* Eye Colour */ + case 21: + toadd = 1; + case 20: /* (back) */ + if (!toadd) {toadd--;} + + FC_ADDEYECOL(toadd); + if (FC_GETEYECOL == 13) { /* Reset? */ FC_SETEYECOL(0);} + SetWindowDirty(w); + break; + case 9: /* Mouth & Nose */ + case 23: + toadd = 1; + case 22: /* (back) */ + if (!toadd) { toadd = (FC_GETMOUTHNOSE % 16 ? toadd-1 : toadd-2); } + + FC_ADDMOUTHNOSE(toadd); + thisval = FC_GETMOUTHNOSE; + switch (thisval) { // Reset? + case 127: if (FC_GENDER_ETH_COUNT == 0) {FC_SETMOUTHNOSE(thisval=0);} break; + case 42: if (FC_GENDER_ETH_COUNT == 1) {FC_SETMOUTHNOSE(thisval=0);} break; + case 60: if (FC_GENDER_ETH_COUNT == 2) {FC_SETMOUTHNOSE(thisval=0);} break; + case 73: if (FC_GENDER_ETH_COUNT == 3) {FC_SETMOUTHNOSE(thisval=0);} break; + } + switch (FC_GENDER_ETH_COUNT) { // Skip some? + case 0: if (((FC_GETMOUTHNOSE+1) % 16) == 0) {FC_ADDMOUTHNOSE(1);} + break; + case 1: if (((FC_GETMOUTHNOSE+1) % 16) == 11) {FC_ADDMOUTHNOSE(6);} + break; + case 2: if (((FC_GETMOUTHNOSE+1) % 16) == 13) {FC_ADDMOUTHNOSE(4);} + break; + case 3: if (((FC_GETMOUTHNOSE+1) % 16) == 10) {FC_ADDMOUTHNOSE(7);} + break; + } + SetWindowDirty(w); + break; + case 10: /* Chin */ + case 25: + toadd = 1; + case 24: /* (back) */ + if (!toadd) {toadd--;} + + FC_ADDCHIN(toadd); + if ((FC_GETCHIN == 2) && FC_GENDER_ETH_COUNT > 0) { /* Reset? */ FC_SETCHIN(0);} + SetWindowDirty(w); + break; + case 11: /* Suit & Collar */ + case 27: + toadd = 1; + case 26: /* (back) */ + if (!toadd) {toadd--;} + + FC_ADDSUITCOL(toadd); + if (!((FC_GETSUITCOL+1) % 4)) { // Skip 1? + FC_ADDSUITCOL(toadd); + // ^ Will reset at 15+1, no need for manual reset + } + SetWindowDirty(w); + break; + case 12: /* Tie / Earring */ + case 29: + toadd = 1; + case 28: /* (back) */ + if (!toadd) {toadd--;} + + FC_ADDTIEEAR(toadd); + switch (FC_GETTIEEAR) { // Reset? + case 6: if (FC_GENDER_ETH_COUNT==0 || FC_GENDER_ETH_COUNT==2) {FC_SETTIEEAR(0);} break; + case 4: if (FC_GENDER_ETH_COUNT==1 || FC_GENDER_ETH_COUNT==3) {FC_SETTIEEAR(0);} break; + } + SetWindowDirty(w); + break; + case 13: /* Gender & Ethnicity */ + case 31: + toadd = 1; + case 30: /* (back) */ + if (!toadd) {toadd--;} + + toadd = (toadd > 0 ? (gender_eth_next[FC_GENDER_ETH_VAL]) : (gender_eth_next[gender_eth_next[gender_eth_next[FC_GENDER_ETH_VAL]]])); + + FC_SETGENDERETH(toadd); + SetWindowDirty(w); + break; + + // TODO: - make attribute 'numbers' inc/decrement contiguously (havent't decided whether we want to do this) + // Error: Save different face as default, leave error window open. Close face window, open face window, click Load. Window isn't set dirty first time. :-S } - break; + break; + + case WE_RCLICK: + switch (e->we.click.widget) { + case 2: /* Possible click on a fake widget */ + if (e->we.click.pt.x >= rand_lrtb[0] && e->we.click.pt.x <= rand_lrtb[1] && e->we.click.pt.y >= rand_lrtb[2] && e->we.click.pt.y <= rand_lrtb[3]) { + /* Randomize right-click */ + GuiShowTooltips(STR_FACE_RANDOMIZE_TIP); + } + else if (e->we.click.pt.x >= load_lrtb[0] && e->we.click.pt.x <= load_lrtb[1] && e->we.click.pt.y >= load_lrtb[2] && e->we.click.pt.y <= load_lrtb[3]) { + /* Load right-click */ + GuiShowTooltips(STR_FACE_LOAD_TIP); + } + else if (e->we.click.pt.x >= save_lrtb[0] && e->we.click.pt.x <= save_lrtb[1] && e->we.click.pt.y >= save_lrtb[2] && e->we.click.pt.y <= save_lrtb[3]) { + /* Save right-click */ + GuiShowTooltips(STR_FACE_SAVE_TIP); + } + else if (e->we.click.pt.x >= code_lrtb[0] && e->we.click.pt.x <= code_lrtb[1] && e->we.click.pt.y >= code_lrtb[2] && e->we.click.pt.y <= code_lrtb[3]) { + /* Face Number Code right-click */ + GuiShowTooltips(STR_FACE_FACECODE_TIP); + } + else {break;} + + SetWindowDirty(w); + break; + } + break; + + case WE_TIMEOUT: + /* Unclick all fake widgets */ + WP(w,facesel_d).data_1 = 0; + SetWindowDirty(w); + break; + + case WE_ON_EDIT_TEXT: + if (WP(w,facesel_d).data_2 == 0) { // Face Number Code + /* Must not be higher than 4,294,967,295 */ + errno = 0; + facecode = strtoul(e->we.edittext.str, NULL, 10); + if (errno == ERANGE) {ShowErrorMessage(INVALID_STRING_ID, STR_FACE_FACECODE_ERR, 0, 0);} + else {WP(w,facesel_d).face = facecode; ShowErrorMessage(INVALID_STRING_ID, STR_FACE_FACECODE_SET, 0, 0); SetWindowDirty(w);} + } + break; + } } +uint32 GetRandomFace(uint32 face, int retain) { + int type; + + if (retain) {face &= 3<<30;} else {face = GB(Random(), 0, 2)<<30;} + + switch (face >> 30) { + case 0: type=FC_CM; break; + case 1: type=FC_BM; break; + case 2: type=FC_CF; break; + case 3: type=FC_BF; break; + } + // shift left by LSB of characteristic's bitgroup + face |= (RandomRange(face_limits[type][FC_HAIR]+1) << 17); + face |= (RandomRange(face_limits[type][FC_GLASSES]+1) << 28); + face |= (RandomRange(face_limits[type][FC_EYES]+1) << 6); + face |= (RandomRange(face_limits[type][FC_EYECOL]+1) << 0); + face |= (RandomRange(face_limits[type][FC_MOUTHNOSE]+1) << 10); // X + face |= (RandomRange(face_limits[type][FC_CHIN]+1) << 4); + face |= (RandomRange(face_limits[type][FC_SUITCOL]+1) << 21); // X + face |= (RandomRange(face_limits[type][FC_TIEEAR]+1) << 25); + /* Note that although some characteristics (marked X above) have some values that */ + /* are a little more likely to be chosen than others, because of 'repetitions' */ + /* (eg. the black female mouth/nose #8, #24, #40, etc. have 7 repetitions each), */ + /* this randomness is 'good enough' and any better randomness would involve */ + /* significant extra code. */ + + return face; +} + static const Widget _select_player_face_widgets[] = { -{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, -{ WWT_CAPTION, RESIZE_NONE, 14, 11, 189, 0, 13, STR_7043_FACE_SELECTION, STR_018C_WINDOW_TITLE_DRAG_THIS}, -{ WWT_IMGBTN, RESIZE_NONE, 14, 0, 189, 14, 136, 0x0, STR_NULL}, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 0, 94, 137, 148, STR_012E_CANCEL, STR_7047_CANCEL_NEW_FACE_SELECTION}, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 95, 189, 137, 148, STR_012F_OK, STR_7048_ACCEPT_NEW_FACE_SELECTION}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 95, 187, 25, 36, STR_7044_MALE, STR_7049_SELECT_MALE_FACES}, -{ WWT_TEXTBTN, RESIZE_NONE, 14, 95, 187, 37, 48, STR_7045_FEMALE, STR_704A_SELECT_FEMALE_FACES}, -{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 95, 187, 79, 90, STR_7046_NEW_FACE, STR_704B_GENERATE_RANDOM_NEW_FACE}, +{ WWT_CLOSEBOX, RESIZE_NONE, 14, 0, 10, 0, 13, STR_00C5, STR_018B_CLOSE_WINDOW}, +{ WWT_CAPTION, RESIZE_NONE, 14, 11, 235, 0, 13, STR_7043_FACE_SELECTION, STR_018C_WINDOW_TITLE_DRAG_THIS}, +{ WWT_IMGBTN, RESIZE_NONE, 14, 0, 235, 14, 150, 0x0, STR_NULL}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 0, 117, 151, 162, STR_012E_CANCEL, STR_7047_CANCEL_NEW_FACE_SELECTION}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 118, 235, 151, 162, STR_012F_OK, STR_7048_ACCEPT_NEW_FACE_SELECTION}, +/* The top 5 widgets are MANDATORY. This leaves room for 27 more widgets, because OpenTTD */ +/* can only properly handle 32 widgets per window, for some weird reason. */ +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 104, 224, 16, 27, STR_EMPTY, STR_FACE_HAIR_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 104, 224, 28, 39, STR_EMPTY, STR_FACE_GLASSES_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 104, 224, 40, 51, STR_EMPTY, STR_FACE_EYES_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 104, 224, 52, 63, STR_EMPTY, STR_FACE_EYECOLOUR_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 104, 224, 64, 75, STR_EMPTY, STR_FACE_MOUTHNOSE_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 104, 224, 76, 87, STR_EMPTY, STR_FACE_CHIN_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 104, 224, 88, 99, STR_EMPTY, STR_FACE_SUITCOLLAR_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 104, 224, 100, 111, STR_EMPTY, STR_FACE_TIEEARRING_TIP}, +{ WWT_PUSHTXTBTN, RESIZE_NONE, 14, 104, 224, 112, 123, STR_EMPTY, STR_FACE_GENDERETH_TIP}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 95, 103, 16, 27, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 225, 233, 16, 27, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 95, 103, 28, 39, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 225, 233, 28, 39, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 95, 103, 40, 51, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 225, 233, 40, 51, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 95, 103, 52, 63, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 225, 233, 52, 63, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 95, 103, 64, 75, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 225, 233, 64, 75, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 95, 103, 76, 87, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 225, 233, 76, 87, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 95, 103, 88, 99, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 225, 233, 88, 99, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 95, 103, 100, 111, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 225, 233, 100, 111, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 95, 103, 112, 123, SPR_ARROW_LEFT, STR_HSCROLL_BAR_SCROLLS_LIST}, +{ WWT_PUSHIMGBTN, RESIZE_NONE, 14, 225, 233, 112, 123, SPR_ARROW_RIGHT, STR_HSCROLL_BAR_SCROLLS_LIST}, { WIDGETS_END}, }; static const WindowDesc _select_player_face_desc = { - -1,-1, 190, 149, + -1,-1, 236, 163, WC_PLAYER_FACE,0, WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, _select_player_face_widgets, @@ -710,7 +1221,6 @@ if (wf != NULL) { wf->caption_color = w->window_number; WP(wf,facesel_d).face = GetPlayer(wf->window_number)->face; - WP(wf,facesel_d).gender = 0; } } break; Index: players.c =================================================================== --- players.c (revision 6502) +++ players.c (working copy) @@ -1,7 +1,7 @@ /* $Id$ */ /** @file players.c - * @todo Cleanup the messy DrawPlayerFace function asap + * */ #include "stdafx.h" #include "openttd.h" @@ -39,156 +39,175 @@ static const SpriteID cheeks_table[4] = { - 0x325, 0x326, - 0x390, 0x3B0, + 805, 806, 912, 944 }; -static const SpriteID mouth_table[3] = { - 0x34C, 0x34D, 0x34F +static const SpriteID nose_table[3] = { + 844, 845, 847 }; void DrawPlayerFace(uint32 face, int color, int x, int y) { byte flag = 0; - if ( (int32)face < 0) - flag |= 1; - if ((((((face >> 7) ^ face) >> 7) ^ face) & 0x8080000) == 0x8000000) - flag |= 2; + flag |= ((1 && GB(face, 31, 1)) << 0); // 0 for male, 1 for female + flag |= ((1 && GB(face, 30, 1)) << 1); // 0 for Western-Caucasian, 1 for black - /* draw the gradient */ + /* Draw the background gradient */ DrawSprite(GENERAL_SPRITE_COLOR(color) + SPRITE_PALETTE(SPR_GRADIENT), x, y); - /* draw the cheeks */ - DrawSprite(cheeks_table[flag&3], x, y); + /* Draw the cheeks, ears, and scalp */ + DrawSprite(cheeks_table[flag & 3], x, y); - /* draw the chin */ - /* FIXME: real code uses -2 in zoomlevel 1 */ + /* Draw the chin */ { - uint val = GB(face, 4, 2); - if (!(flag & 2)) { - DrawSprite(0x327 + (flag&1?0:val), x, y); - } else { - DrawSprite((flag&1?0x3B1:0x391) + (val>>1), x, y); + uint data = GB(face, 4, 2); + + if (!(flag & 2)) { /* Western-Caucasian */ + DrawSprite(807 + (flag&1 ? 0 : data), x, y); } + else { /* Black */ + DrawSprite((flag&1 ? 945 : 913) + (data > 1 ? 1 : data), x, y); + } } - /* draw the eyes */ + + /* Draw the eyes */ { - uint val1 = GB(face, 6, 4); - uint val2 = GB(face, 20, 3); - uint32 high = 0x314 << PALETTE_SPRITE_START; + uint data = GB(face, 6, 4); + uint remap = GB(face, 0, 4); + uint32 highword; - if (val2 >= 6) { - high = 0x30F << PALETTE_SPRITE_START; - if (val2 != 6) - high = 0x30D << PALETTE_SPRITE_START; - } + if (remap >= 4) {remap++;} // Avoid red + if (remap >= 12) {remap++;} // Avoid orange + // Avoid white + remap = (remap > 14 ? 14 : remap); + highword = (775+remap); + highword <<= PALETTE_SPRITE_START; - if (!(flag & 2)) { - if (!(flag & 1)) { - DrawSprite(high+((val1 * 12 >> 4) + SPRITE_PALETTE(0x32B)), x, y); - } else { - DrawSprite(high+(val1 + SPRITE_PALETTE(0x337)), x, y); + if (!(flag & 2)) { /* Western-Caucasian */ + if (!(flag & 1)) { /* Male */ + DrawSprite(highword + SPRITE_PALETTE(811) + (data > 11 ? 11 : data), x, y); + } else { /* Female */ + DrawSprite(highword + SPRITE_PALETTE(823) + (data > 15 ? 15 : data), x, y); } - } else { - if (!(flag & 1)) { - DrawSprite(high+((val1 * 11 >> 4) + SPRITE_PALETTE(0x39A)), x, y); - } else { - DrawSprite(high+(val1 + SPRITE_PALETTE(0x3B8)), x, y); + } else { /* Black */ + if (!(flag & 1)) { /* Male */ + DrawSprite(highword + SPRITE_PALETTE(922) + (data > 10 ? 10 : data), x, y); + } else { /* Female */ + DrawSprite(highword + SPRITE_PALETTE(952) + (data > 15 ? 15 : data), x, y); } } } - /* draw the mouth */ + /* Draw the mouth and nose */ { - uint val = GB(face, 10, 6); - uint val2; + uint data = GB(face, 10, 7); + uint data2; - if (!(flag&1)) { - val2 = ((val&0xF) * 15 >> 4); - - if (val2 < 3) { - DrawSprite((flag&2 ? 0x397 : 0x367) + val2, x, y); - /* skip the rest */ - goto skip_mouth; + /* Mouth */ + data2 = GB(data, 0, 4); + if (!(flag & 1)) { /* Male */ + if (!(flag & 2)) { /* Western-Caucasian */ + if (data2 > 11) { /* Moustache */ + data2 -= 12; + data2 = (data2 > 2 ? 2 : data2); + DrawSprite(871 + data2, x, y); + /* Skip the rest */ + goto skip_nose; + } + DrawSprite(859 + data2, x, y); } - - val2 -= 3; - if (flag & 2) { - if (val2 > 8) val2 = 0; - val2 += 0x3A5 - 0x35B; + else { /* Black */ + if (data2 > 8) { /* Moustache */ + data2 -= 9; + data2 = (data2 > 2 ? 2 : data2); + DrawSprite(919 + data2, x, y); + /* Skip the rest */ + goto skip_nose; + } + DrawSprite(933 + data2, x, y); } - DrawSprite(val2 + 0x35B, x, y); - } else if (!(flag&2)) { - DrawSprite(((val&0xF) * 10 >> 4) + 0x351, x, y); - } else { - DrawSprite(((val&0xF) * 9 >> 4) + 0x3C8, x, y); } + else { /* Female */ + if (!(flag & 2)) { /* Western-Caucasian */ + DrawSprite(849 + (data2 > 9 ? 9 : data2), x, y); + } + else { /* Black */ + DrawSprite(968 + (data2 > 8 ? 8 : data2), x, y); + } + } - val >>= 3; - - if (!(flag&2)) { - if (!(flag&1)) { - DrawSprite(0x349 + val, x, y); - } else { - DrawSprite( mouth_table[(val*3>>3)], x, y); + /* Nose */ + data2 = GB(data, 4, 3); + if (!(flag & 2)) { /* Western-Caucasian */ + if (!(flag & 1)) { /* Male */ + DrawSprite(841 + (data2 > 7 ? 7 : data2), x, y); } - } else { - if (!(flag&1)) { - DrawSprite(0x393 + (val&3), x, y); - } else { - DrawSprite(0x3B3 + (val*5>>3), x, y); + else { /* Female */ + DrawSprite(nose_table[(data2 > 2 ? 2 : data2)], x, y); } } + else { /* Black */ + if (!(flag & 1)) { /* Male */ + DrawSprite(915 + (data2 > 3 ? 3 : data2), x, y); + } + else { /* Female */ + DrawSprite(947 + (data2 > 4 ? 4 : data2), x, y); + } + } - skip_mouth:; + skip_nose:; } + /* Draw the hair */ + { + uint data = GB(face, 17, 4); - /* draw the hair */ - { - uint val = GB(face, 16, 4); - if (flag & 2) { - if (flag & 1) { - DrawSprite(0x3D9 + (val * 5 >> 4), x, y); - } else { - DrawSprite(0x3D4 + (val * 5 >> 4), x, y); + if (!(flag & 2)) { /* Western-Caucasian */ + if (!(flag & 1)) { /* Male */ + DrawSprite(898 + (data > 8 ? 8 : data), x, y); + } else { /* Female */ + DrawSprite(907 + (data > 4 ? 4 : data), x, y); } - } else { - if (flag & 1) { - DrawSprite(0x38B + (val * 5 >> 4), x, y); - } else { - DrawSprite(0x382 + (val * 9 >> 4), x, y); + } else { /* Black */ + if (!(flag & 1)) { /* Male */ + DrawSprite(980 + (data > 4 ? 4 : data), x, y); + } else { /* Female */ + DrawSprite(985 + (data > 4 ? 4 : data), x, y); } } } - /* draw the tie */ + /* Draw the suit, collar, and tie (or earring) */ { - uint val = GB(face, 20, 8); + uint data = GB(face, 21, 7); - if (!(flag&1)) { - DrawSprite(0x36B + (GB(val, 0, 2) * 3 >> 2), x, y); - DrawSprite(0x36E + (GB(val, 2, 2) * 4 >> 2), x, y); - DrawSprite(0x372 + (GB(val, 4, 4) * 6 >> 4), x, y); - } else { - DrawSprite(0x378 + (GB(val, 0, 2) * 3 >> 2), x, y); - DrawSprite(0x37B + (GB(val, 2, 2) * 4 >> 2), x, y); - - val >>= 4; - if (val < 3) DrawSprite((flag & 2 ? 0x3D1 : 0x37F) + val, x, y); + if (!(flag & 1)) { /* Male... suit, collar, tie */ + DrawSprite(875 + (GB(data, 0, 2) > 2 ? 2 : GB(data, 0, 2)), x, y); + DrawSprite(878 + (GB(data, 2, 2) > 3 ? 3 : GB(data, 2, 2)), x, y); + DrawSprite(882 + (GB(data, 4, 3) > 5 ? 5 : GB(data, 4, 3)), x, y); + } else { /* Female... suit, collar, earring */ + DrawSprite(888 + (GB(data, 0, 2) > 2 ? 2 : GB(data, 0, 2)), x, y); + DrawSprite(891 + (GB(data, 2, 2) > 3 ? 3 : GB(data, 2, 2)), x, y); + if (!(flag & 2)) { /* Western-Caucasian */ + if (GB(data, 4, 3) < 3) {DrawSprite(895 + GB(data, 4, 3), x, y);} + } + else { /* Black */ + if (GB(data, 4, 3) < 3) {DrawSprite(977 + GB(data, 4, 3), x, y);} + } } } - /* draw the glasses */ + /* Draw the glasses */ { - uint val = GB(face, 28, 3); + uint data = GB(face, 28, 2); - if (flag & 2) { - if (val <= 1) DrawSprite(0x3AE + val, x, y); - } else { - if (val <= 1) DrawSprite(0x347 + val, x, y); + if (!(flag & 2)) { /* Western-Caucasian */ + if (data <= 1) DrawSprite(839 + data, x, y); } + else { /* Black */ + if (data <= 1) DrawSprite(942 + data, x, y); + } } } @@ -514,7 +533,7 @@ p->avail_railtypes = GetPlayerRailtypes(p->index); p->inaugurated_year = _cur_year; - p->face = Random(); + p->face = GetRandomFace(0, 0); /* Engine renewal settings */ p->engine_renew_list = NULL; Index: settings.c =================================================================== --- settings.c (revision 6502) +++ settings.c (working copy) @@ -1184,6 +1184,7 @@ SDTG_STR("screenshot_format",SLE_STRB, S, 0, _screenshot_format_name,NULL, STR_NULL, NULL), SDTG_STR("savegame_format", SLE_STRB, S, 0, _savegame_format, NULL, STR_NULL, NULL), SDTG_BOOL("rightclick_emulate", S, 0, _rightclick_emulate, false, STR_NULL, NULL), + SDTG_VAR("player_face", SLE_UINT32, S, 0, _player_face, 0,0, 4294836225,0,STR_NULL, NULL), SDTG_END() }; Index: variables.h =================================================================== --- variables.h (revision 6502) +++ variables.h (working copy) @@ -414,6 +414,7 @@ /* misc */ VARDEF char _screenshot_name[128]; VARDEF byte _vehicle_design_names; +VARDEF uint32 _player_face; /* Forking stuff */ VARDEF bool _dedicated_forks; Index: window.h =================================================================== --- window.h (revision 6502) +++ window.h (working copy) @@ -416,7 +416,8 @@ typedef struct { uint32 face; - byte gender; + int16 data_1; + int16 data_2; } facesel_d; assert_compile(WINDOW_CUSTOM_SIZE >= sizeof(facesel_d));