#define NBACTIONS 12
#define NBLANGUAGES 7
#define MAXAUTOBUTTONS 84 //12*7
#define BUTT_WIDTH 56
#define BUTT_HEIGHT 12
#define MAXTEXTWIDTH 200 //Some bullshit width to be passed to GetTextHeight. Should be bigger than the biggest possible width
int defaultTheme;
GUI* guiMain; //The gui that has the action buttons
struct AutoButtonData {
DynamicSprite* s[3]; //one for each state
eLanguage lan;
Action action;
String text;
int widenFactor; //how much the text should be stretched horizontally (not all the same depending on the language and the action)
int width; //what should be the width of the buttons (they don't have the same width depending on the language and the action)
};
AutoButtonData buttons[MAXAUTOBUTTONS];
#define MAXTHEMES 3
struct Constants {
int width;
int height;
};
Constants constants;
struct Theme {
//all values below initialized in game_start or if you call SetTheme
int settings[THEMESIZE];
//You can add any field you want. For example String themeName;
};
Theme themes[MAXTHEMES];
//int nbThemes;
//Unfortunately the Verbs module does not provide the bare actions, only with their prepositions and complement, like "Look at %s"
String TranslateAction(Action a, eLanguage lan) {
switch(lan) {
case eLangEN:
switch(a) {
case eGA_WalkTo: return "Go to";
case eGA_LookAt: return "Look At";
case eGA_TalkTo: return "Talk To";
case eGA_GiveTo: return "Give";
case eGA_PickUp: return "Pick Up";
case eGA_Use: return "Use";
case eGA_Open: return "Open";
case eGA_Close: return "Close";
case eGA_Push: return "Push";
case eGA_Pull: return "Pull";
default :
return "ERROR";
break;
}
break;
case eLangDE :
switch(a) {
case eGA_WalkTo: return "Gehe zu";
case eGA_LookAt: return "Schau";
case eGA_TalkTo: return "Rede";
case eGA_GiveTo: return "Gebe";
case eGA_PickUp: return "Nehme";
case eGA_Use: return "Nutze";
case eGA_Open: return "Öffne";
case eGA_Close: return "Schließe";
case eGA_Push: return "Drücke";
case eGA_Pull: return "Ziehe";
default :
return "ERROR";
break;
}
break;
case eLangES :
switch(a) {
case eGA_WalkTo: return "Ir a";
case eGA_LookAt: return "Mirar";
case eGA_TalkTo: return "Hablar";
case eGA_GiveTo: return "Dar";
case eGA_PickUp: return "Coger";
case eGA_Use: return "Usar";
case eGA_Open: return "Abrir";
case eGA_Close: return "Cerrar";
case eGA_Push: return "Empujar";
case eGA_Pull: return "Tirar";
default :
return "ERROR";
break;
}
break;
case eLangFR :
switch(a) {
case eGA_WalkTo: return "Aller";
case eGA_LookAt: return "Regarder";
case eGA_TalkTo: return "Parler";
case eGA_GiveTo: return "Donner";
case eGA_PickUp: return "Prendre";
case eGA_Use: return "Utiliser";
case eGA_Open: return "Ouvrir";
case eGA_Close: return "Fermer";
case eGA_Push: return "Pousser";
case eGA_Pull: return "Tirer";
default :
return "ERROR";
break;
}
break;
case eLangIT :
switch(a) {
case eGA_WalkTo: return "Vai a";
case eGA_LookAt: return "Esamina";
case eGA_TalkTo: return "Parla";
case eGA_GiveTo: return "Dai";
case eGA_PickUp: return "Raccogli";
case eGA_Use: return "Usa";
case eGA_Open: return "Apri";
case eGA_Close: return "Ferma";
case eGA_Push: return "Premi";
case eGA_Pull: return "Tira";
default :
return "ERROR";
break;
}
break;
case eLangPT :
switch(a) {
case eGA_WalkTo: return "Ir para";
case eGA_LookAt: return "Olhar";
case eGA_TalkTo: return "Falar";
case eGA_GiveTo: return "Dar";
case eGA_PickUp: return "Apanhar";
case eGA_Use: return "Usar";
case eGA_Open: return "Abrir";
case eGA_Close: return "Fechar";
case eGA_Push: return "Empurrar";
case eGA_Pull: return "Puxar";
default :
return "ERROR";
break;
}
break;
case eLangNL :
switch(a) {
case eGA_WalkTo: return "Ga naar";
case eGA_LookAt: return "Bekijk";
case eGA_TalkTo: return "Praat";
case eGA_GiveTo: return "Geef";
case eGA_PickUp: return "Pak";
case eGA_Use: return "Gebruik";
case eGA_Open: return "Open";
case eGA_Close: return "Sluit";
case eGA_Push: return "Duw";
case eGA_Pull: return "Trek";
default :
return "ERROR";
break;
}
break;
default :
return "ERROR2";
break;
}
}
DynamicSprite* DrawString_Widened(int font, String text, int color, int widenFactor )
{
int kerning = 0;
int wordSpacing = 2; //arbitrary number of pixels for the 'space' character, to make the text more compact
int maxWidth = GetTextWidth(text, font)*3;
int height = GetTextHeight(text, font, MAXTEXTWIDTH);
//The width is a bit hard to calculate so we'll start by drawing onto a temporary surface
DynamicSprite* temp_s = DynamicSprite.Create(maxWidth, height, true);
DrawingSurface* temp_ds = temp_s.GetDrawingSurface();
temp_ds.Clear();
int offset = 0;
temp_ds.DrawingColor = color;
//we draw each letter one by one
for (int i=0; i<text.Length; i++) {
if (text.Chars[i] == ' ') {
offset+=wordSpacing;
} else {
String c = String.Format("%c", text.Chars[i]);
int letterWidth = GetTextWidth(c, font);
//the easiest way to widen a letter is to draw it several times
for (int j=0; j < widenFactor; j++) {
temp_ds.DrawString(offset+j, 0, font, c);
}
offset+=(letterWidth+widenFactor);
//if it's not the last letter we add a white space after the letter
if (i<text.Length-1)
offset+=kerning;
}
}
//now that we know the width we copy the temp sprite to a final sprite
DynamicSprite* s = DynamicSprite.Create(offset, height, true);
DrawingSurface* ds = s.GetDrawingSurface();
ds.DrawSurface(temp_ds);
temp_ds.Release();
return s;
}
//Draws 'graphic' 9 times onto ds, in an "outlined" manner (i.e. with offsets of -1 to +1)
void DrawOutline(DrawingSurface*ds, int x, int y, int graphic)
{
ds.DrawImage(x-1, y-1, graphic);
ds.DrawImage(x-0, y-1, graphic);
ds.DrawImage(x+1, y-1, graphic);
ds.DrawImage(x-1, y-0, graphic);
ds.DrawImage(x-0, y-0, graphic);
ds.DrawImage(x+1, y-0, graphic);
ds.DrawImage(x-1, y+1, graphic);
ds.DrawImage(x-0, y+1, graphic);
ds.DrawImage(x+1, y+1, graphic);
}
//See more at https://www.adventuregamestudio.co.uk/forums/index.php?topic=42449.0
int[] GetRGBFromColor(int color)
{
int rgb[] = new int[3];
bool highBit = true; //or false, you decide
if (color > 65535) color -= 65536;
rgb[0] = ((color >> 11) & 31) << 3;
rgb[1] = ((color >> 6) & 31) << 3;
rgb[2] = (color & 31) << 3;
if (highBit)
{
rgb[0] = rgb[0] | 7;
rgb[1] = rgb[1] | 3;
rgb[2] = rgb[2] | 7;
}
return rgb;
}
//from top to bottom, every color of 'ds' that has color 'color' will be replaced by a gradient
void noloopcheck ApplyGradient(DrawingSurface* ds, int color, int color_gradient_top, int color_gradient_bottom)
{
int rgb_top[] = GetRGBFromColor(color_gradient_top);
int rgb_bottom[] = GetRGBFromColor(color_gradient_bottom);
float height_f = IntToFloat(ds.Height);
float r_top = IntToFloat(rgb_top[0]); float r_bottom = IntToFloat(rgb_bottom[0]); float step_r = (r_bottom-r_top)/height_f;
float g_top = IntToFloat(rgb_top[1]); float g_bottom = IntToFloat(rgb_bottom[1]); float step_g = (g_bottom-g_top)/height_f;
float b_top = IntToFloat(rgb_top[2]); float b_bottom = IntToFloat(rgb_bottom[2]); float step_b = (b_bottom-b_top)/height_f;
float r = r_top; float g = g_top; float b = b_top; //start values
for (int j=0; j< ds.Height; j++) {
ds.DrawingColor = Game.GetColorFromRGB(FloatToInt(r), FloatToInt(g), FloatToInt(b));
for (int i=0; i<ds.Width; i++) {
if (ds.GetPixel(i, j)==color)
ds.DrawPixel(i, j);
}
r+=step_r; g+=step_g; b+=step_b;
}
}
DynamicSprite* GenerateButton(String text, int width, AutoButtonStates state, int widenFactor, int themeSettings[])
{
int font = eFontTumbleText;
//int COLOR_BLACK = Game.GetColorFromRGB(5, 5, 5);
int COLOR_RED = Game.GetColorFromRGB(255, 0, 0);
int color = COLOR_RED; //we give it a default value for satefy
int color_gradient_top = COLOR_RED; //we give it a default value for satefy
int color_gradient_bottom = COLOR_RED; //we give it a default value for satefy
switch(state) {
case eAutoButton_off :
color = themeSettings[eAutoBSetting_color_off];
color_gradient_top = themeSettings[eAutoBSetting_color_off_gradienttop];
color_gradient_bottom = themeSettings[eAutoBSetting_color_off_gradientbottom];
break;
case eAutoButton_on :
color = themeSettings[eAutoBSetting_color_on];
color_gradient_top = themeSettings[eAutoBSetting_color_on_gradienttop];
color_gradient_bottom = themeSettings[eAutoBSetting_color_on_gradientbottom];
break;
case eAutoButton_hover :
color = themeSettings[eAutoBSetting_color_hover];
color_gradient_top = themeSettings[eAutoBSetting_color_hover_gradienttop];
color_gradient_bottom = themeSettings[eAutoBSetting_color_hover_gradientbottom];
break;
default:
AbortGame("Unknown state");
}
//int width = constants.width;
int height = constants.height;
DynamicSprite* text_s = null;
DynamicSprite* s = DynamicSprite.Create(width, height, true);
DrawingSurface* ds = s.GetDrawingSurface();
//ds.DrawingColor = COLOR_TRANSPARENT;
ds.Clear();
//int textWidth = GetTextWidth(text, font); //we actually need the widened width
int textHeight = GetTextHeight(text, font, MAXTEXTWIDTH);
//(optional) colored rectangular background
//ds.DrawingColor = Game.GetColorFromRGB(100, 100, 100);
//ds.DrawRectangle(0, 0, width, height);
//shadow
text_s = DrawString_Widened(font, text, themeSettings[eAutoBSetting_color_shadow], widenFactor );
int textWidth = text_s.Width;
int offset_x = (width - textWidth)/2; if (offset_x < 0) offset_x = 0;
int offset_y = (height - textHeight)/2; if (offset_y < 0) offset_y = 0;
DrawOutline(ds, offset_x+widenFactor, offset_y, text_s.Graphic);
//outline (the cheapest way to do an outline is to draw the text 9 times with and offset of -1 or +1 all around
text_s = DrawString_Widened(font, text, themeSettings[eAutoBSetting_color_outline], widenFactor );
DrawOutline(ds, offset_x, offset_y, text_s.Graphic);
//widened text. It returns a temporary sprite that we immediately copy onto our drawing surface
text_s = DrawString_Widened(font, text, color, widenFactor );
ds.DrawImage(offset_x, offset_y, text_s.Graphic);
//gradient
ApplyGradient(ds, color, color_gradient_top, color_gradient_bottom);
//finish up
text_s.Delete();
ds.Release();
return s;
}
//this is purely built-in
//how much the text should be stretched horizontally (not all the same depending on the language and the action)
int GetWidenFactor(eLanguage lan, Action a)
{
switch(lan) {
case eLangDE :
if (a==eGA_Close) //In German, "SchlieBe"'s font is narrower then the other buttons fonts
return 1;
else
return 2;
break;
case eLangEN : return 2; break;
case eLangES : return 1; break;
case eLangFR : return 1; break;
case eLangIT : return 1; break;
case eLangNL : return 1; break;
case eLangPT : return 1; break;
default : AbortGame("Unknown language");
}
}
//Utility : Returns the button bound to a given action. Unfortunately this function is not provided by Verbs
Button* GetActionButton(Action a)
{
//we scan every control
for (int i=0; i<guiMain.ControlCount; i++) {
GUIControl* c = guiMain.Controls[i];
Button* b = c.AsButton;
if (b!=null) {
if (Verbs.GetButtonAction(b) == a)
return b;
}
}
//AbortGame("Couldn't find the button bound to action '%d'", a);
return null; //Not all actions have a button. E.g. "walk to"
//If it was implemented in Verbs we would just use this :
//return Verbs.GetActionButton(a);
}
int GetButtonWidth(Action a) {
/*
//Somehow the automated code below doesn't work. buttons return goofy values, like b.Width == 200. I have no idea why.
Button* b = GetActionButton(a);
if (b!= null) {
//Display("Action %d return button.ID=%d, which has width %d x height %d. It belongs to GUI %d. Name is '%s'", a, b.ID, b.Width, b.Height, b.OwningGUI.ID, b.Text);
return b.Width;
} else {
return constants.width; //this action doesn't seem to have a button. We roll back to the default buttons width
}
*/
//Manual version. TODO : find why code above does not work.
switch(a) {
case eGA_LookAt : return 56; //larger buttons in the central column
case eGA_TalkTo : return 56; //larger buttons in the central column
case eGA_PickUp : return 56; //larger buttons in the central column
default: return 50; //narrower buttons in th eleft and right column
}
}
static int AutoButtons::GenerateAll(int themeSettings[])
{
int NBSPRITES = NBACTIONS*NBLANGUAGES;
//safety
if (NBSPRITES > MAXAUTOBUTTONS)
AbortGame("Did something change?");
//Some languages have shorter words so we can make the text wider up to a factor of 3
int widenFactor = 1;
for (int i=0; i < NBLANGUAGES; i++) {
for (int a = 0; a<NBACTIONS; a++) {
widenFactor = GetWidenFactor(i, a);
int width = GetButtonWidth(a);
buttons[i*NBACTIONS+a].widenFactor = widenFactor;
buttons[i*NBACTIONS+a].width = width;
String text = TranslateAction(a, i);
buttons[i*NBACTIONS+a].lan = i;
buttons[i*NBACTIONS+a].action = a;
buttons[i*NBACTIONS+a].s[eAutoButton_off] = GenerateButton(text, width, eAutoButton_off, widenFactor, themeSettings);
buttons[i*NBACTIONS+a].s[eAutoButton_on] = GenerateButton(text, width, eAutoButton_on, widenFactor, themeSettings);
buttons[i*NBACTIONS+a].s[eAutoButton_hover] = GenerateButton(text, width, eAutoButton_hover, widenFactor, themeSettings);
buttons[i*NBACTIONS+a].text = text;
}
}
}
static DynamicSprite* AutoButtons::GetSprite(eLanguage lan, Action action, AutoButtonStates state)
{
return buttons[lan*NBACTIONS+action].s[state];
}
static int AutoButtons::GetSpriteID(eLanguage lan, Action action, AutoButtonStates state)
{
return buttons[lan*NBACTIONS+action].s[state].Graphic;
}
float min(float a, float b) { if (a < b) return a; return b; }
float max(float a, float b) { if (a > b) return a; return b; }
float bound(float a, float roof, float floor) { return min(roof, max(a, floor)); }
//this function returns a darker or brighter version of color 'color'.
// 'factor' between 0.0 and 2.0.
// - If factor is 0.0, the function returns black (because the color has been darkened to the max).
// - If it's 1.0, the color doesn't change
// - If factor is 2.0, the function probably returns white, or at least 'color" is now twice brighter
int ChangeBrightness(int color, float factor)
{
int rgb[] = GetRGBFromColor(color);
float r = IntToFloat(rgb[0]); float g = IntToFloat(rgb[1]); float b = IntToFloat(rgb[2]);
rgb = null;
r = bound(r*factor, 255.0, 0.0); g = bound(g*factor, 255.0, 0.0); b = bound(b*factor, 255.0, 0.0);
return Game.GetColorFromRGB(FloatToInt(r), FloatToInt(g),FloatToInt(b));
}
int[] GetTheme_BuiltIn(AutoButtonThemes theme)
{
//useful values
int COLOR_RED = Game.GetColorFromRGB(255, 99, 99);
int COLOR_BROWN = Game.GetColorFromRGB(50, 0, 0);
int COLOR_YELLOW = Game.GetColorFromRGB(255, 249, 72);
int COLOR_GRAYISHRED = Game.GetColorFromRGB(116, 63, 63);
int COLOR_DARKGRAYISHRED = Game.GetColorFromRGB(52, 32, 32);
int COLOR_BLACK = Game.GetColorFromRGB(5, 5, 5);
int themeSettings[] = new int[THEMESIZE];
switch (theme) {
case eAutoButtonTheme_Red :
themeSettings[eAutoBSetting_color_off] = COLOR_RED;
themeSettings[eAutoBSetting_color_on] = COLOR_YELLOW;
themeSettings[eAutoBSetting_color_hover] = COLOR_BROWN;
themeSettings[eAutoBSetting_color_outline] = COLOR_GRAYISHRED;
themeSettings[eAutoBSetting_color_shadow] = COLOR_BLACK;
/*
//MANUAL
themeSettings[eAutoBSetting_color_off_gradienttop] = Game.GetColorFromRGB(255, 175, 175);
themeSettings[eAutoBSetting_color_off_gradientbottom] = Game.GetColorFromRGB(200, 0, 0);
themeSettings[eAutoBSetting_color_on_gradienttop] = Game.GetColorFromRGB(255, 249, 125);
themeSettings[eAutoBSetting_color_on_gradientbottom] = Game.GetColorFromRGB(200, 200, 25);
themeSettings[eAutoBSetting_color_hover_gradienttop] = Game.GetColorFromRGB(100, 50, 50);
themeSettings[eAutoBSetting_color_hover_gradientbottom] = Game.GetColorFromRGB(25, 0, 0);
*/
//AUTOMATED
themeSettings[eAutoBSetting_color_off_gradienttop] = ChangeBrightness(themeSettings[eAutoBSetting_color_off], 1.5);
themeSettings[eAutoBSetting_color_off_gradientbottom] = ChangeBrightness(themeSettings[eAutoBSetting_color_off], 0.5);
themeSettings[eAutoBSetting_color_on_gradienttop] = ChangeBrightness(themeSettings[eAutoBSetting_color_on], 1.5);
themeSettings[eAutoBSetting_color_on_gradientbottom] = ChangeBrightness(themeSettings[eAutoBSetting_color_on], 0.5);
themeSettings[eAutoBSetting_color_hover_gradienttop] = ChangeBrightness(themeSettings[eAutoBSetting_color_hover], 1.5);
themeSettings[eAutoBSetting_color_hover_gradientbottom] = ChangeBrightness(themeSettings[eAutoBSetting_color_hover], 0.5);
break;
case eAutoButtonTheme_Blue :
AbortGame("NOT IMPLEMENTED (suit yourself)");
break;
}
return themeSettings;
}
static int[] AutoButtons::GetTheme(AutoButtonThemes theme)
{
int themeSettings[] = new int[THEMESIZE];
if (theme < 0 || theme >= MAXTHEMES)
AbortGame("Not a valid theme number : %d", theme);
for (int i=0; i< THEMESIZE; i++) {
themeSettings[i] = themes[theme].settings[i];
}
return themeSettings;
}
//Overwrite an existing theme from your game, without requiring to temper with the module's script (if needed)
static void AutoButtons::SetTheme(AutoButtonThemes theme, int themeSettings[])
{
if (theme < 0 || theme >= MAXTHEMES)
AbortGame("Not a valid theme number : %d", theme);
for (int i=0; i< THEMESIZE; i++) {
themes[theme].settings[i] = themeSettings[i];
}
}
void game_start()
{
//depends on your game
guiMain = gMain; //this is a dirty hack. we should rely only on "Verbs" methods but it doesn't offer GetActionButton.
//nbThemes = 0;
defaultTheme = eAutoButtonTheme_Red;
//Init buttons constant width. This is normally ignored and replaced with buttons[].width because not all buttons have the same width
constants.height = BUTT_HEIGHT;
constants.width = BUTT_WIDTH;
//Init built-in themes
int themeSettings[] = GetTheme_BuiltIn(eAutoButtonTheme_Red);
AutoButtons.SetTheme(eAutoButtonTheme_Red, themeSettings);
//themeSettings[] = GetTheme_BuiltIn(eAutoButtonTheme_Blue);
//AutoButtons.SetTheme(eAutoButtonTheme_Blue, themeSettings);
//final init
themeSettings = AutoButtons.GetTheme(defaultTheme);
AutoButtons.GenerateAll(themeSettings);
}