git-svn-id: http://svn.berlios.de/svnroot/repos/oolite-linux/trunk@891 127b21dd-08f5-0310-b4b7-95ae10353056
1416 lines
41 KiB
Objective-C
1416 lines
41 KiB
Objective-C
/*
|
|
|
|
OOJavaScriptEngine.h
|
|
|
|
JavaScript support for Oolite
|
|
Copyright (C) 2007 David Taylor and Jens Ayton.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
#import "OOJavaScriptEngine.h"
|
|
#import "OOJSScript.h"
|
|
#import "OOCollectionExtractors.h"
|
|
#import "Universe.h"
|
|
#import "PlanetEntity.h"
|
|
#import "NSStringOOExtensions.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
|
|
extern NSString * const kOOLogDebugMessage;
|
|
|
|
|
|
static OOJavaScriptEngine *sSharedEngine = nil;
|
|
static JSObject *xglob, *universeObj, *systemObj, *playerObj, *missionObj;
|
|
|
|
|
|
extern OOJSScript *currentOOJSScript;
|
|
|
|
|
|
OOINLINE jsval BOOLToJSVal(BOOL b) INLINE_CONST_FUNC;
|
|
OOINLINE jsval BOOLToJSVal(BOOL b)
|
|
{
|
|
return BOOLEAN_TO_JSVAL(b != NO);
|
|
}
|
|
|
|
|
|
// For _bool scripting methods which always return @"YES" or @"NO" and nothing else.
|
|
OOINLINE jsval BooleanStringToJSVal(NSString *string) INLINE_PURE_FUNC;
|
|
OOINLINE jsval BooleanStringToJSVal(NSString *string)
|
|
{
|
|
return BOOLEAN_TO_JSVAL([string isEqualToString:@"YES"]);
|
|
}
|
|
|
|
|
|
#define JSValToNSString(cx, val) [NSString stringWithJavaScriptValue:val inContext:cx]
|
|
|
|
|
|
static void ReportJSError(JSContext *cx, const char *message, JSErrorReport *report);
|
|
|
|
|
|
//===========================================================================
|
|
// MissionVars class
|
|
//===========================================================================
|
|
|
|
static JSBool MissionVarsGetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp);
|
|
static JSBool MissionVarsSetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp);
|
|
|
|
|
|
static JSClass MissionVars_class =
|
|
{
|
|
"MissionVariables",
|
|
JSCLASS_HAS_PRIVATE,
|
|
|
|
JS_PropertyStub,
|
|
JS_PropertyStub,
|
|
MissionVarsGetProperty,
|
|
MissionVarsSetProperty,
|
|
JS_EnumerateStub,
|
|
JS_ResolveStub,
|
|
JS_ConvertStub,
|
|
JS_FinalizeStub
|
|
};
|
|
|
|
|
|
static JSBool MissionVarsGetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
NSDictionary *mission_variables = [[PlayerEntity sharedPlayer] mission_variables];
|
|
|
|
if (JSVAL_IS_STRING(name))
|
|
{
|
|
NSString *key = [@"mission_" stringByAppendingString:[NSString stringWithJavaScriptValue:name inContext:cx]];
|
|
NSString *value = [mission_variables objectForKey:key];
|
|
|
|
if (value == nil)
|
|
{
|
|
*vp = JSVAL_VOID;
|
|
}
|
|
else
|
|
{
|
|
/* The point of this code is to try and tell the JS interpreter to treat numeric strings
|
|
as numbers where possible so that standard arithmetic works as you'd expect rather than
|
|
1+1 == "11". So a JSVAL_DOUBLE is returned if possible, otherwise a JSVAL_STRING is returned.
|
|
*/
|
|
|
|
BOOL isNumber = NO;
|
|
double dVal;
|
|
|
|
dVal = [value doubleValue];
|
|
if (dVal != 0) isNumber = YES;
|
|
else
|
|
{
|
|
NSCharacterSet *notZeroSet = [[NSCharacterSet characterSetWithCharactersInString:@"-0. "] invertedSet];
|
|
if ([value rangeOfCharacterFromSet:notZeroSet].location == NSNotFound) isNumber = YES;
|
|
}
|
|
if (isNumber)
|
|
{
|
|
jsdouble ds = [value doubleValue];
|
|
JSBool ok = JS_NewDoubleValue(cx, ds, vp);
|
|
if (!ok) *vp = JSVAL_VOID;
|
|
}
|
|
else *vp = [value javaScriptValueInContext:cx];
|
|
}
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool MissionVarsSetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
NSDictionary *mission_variables = [[PlayerEntity sharedPlayer] mission_variables];
|
|
|
|
if (JSVAL_IS_STRING(name))
|
|
{
|
|
NSString *key = [@"mission_" stringByAppendingString:[NSString stringWithJavaScriptValue:name inContext:cx]];
|
|
NSString *value = [NSString stringWithJavaScriptValue:*vp inContext:cx];
|
|
[mission_variables setValue:value forKey:key];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
//===========================================================================
|
|
// Global object class
|
|
//===========================================================================
|
|
|
|
static JSBool GlobalGetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp);
|
|
|
|
|
|
static JSClass global_class =
|
|
{
|
|
"Oolite",
|
|
0,
|
|
|
|
JS_PropertyStub,
|
|
JS_PropertyStub,
|
|
JS_PropertyStub,
|
|
JS_PropertyStub,
|
|
JS_EnumerateStub,
|
|
JS_ResolveStub,
|
|
JS_ConvertStub,
|
|
JS_FinalizeStub
|
|
};
|
|
|
|
|
|
enum global_propertyIDs
|
|
{
|
|
GLOBAL_GALAXY_NUMBER,
|
|
GLOBAL_PLANET_NUMBER,
|
|
GLOBAL_MISSION_VARS,
|
|
GLOBAL_GUI_SCREEN
|
|
};
|
|
|
|
|
|
// TODO: most of these should be properties of Player class.
|
|
static JSPropertySpec Global_props[] =
|
|
{
|
|
// JS name ID flags
|
|
{ "galaxyNumber", GLOBAL_GALAXY_NUMBER, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY, GlobalGetProperty },
|
|
{ "planetNumber", GLOBAL_PLANET_NUMBER, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY, GlobalGetProperty },
|
|
{ "missionVariables", GLOBAL_MISSION_VARS, JSPROP_PERMANENT | JSPROP_ENUMERATE, GlobalGetProperty },
|
|
{ "guiScreen", GLOBAL_GUI_SCREEN, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY, GlobalGetProperty },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
static JSBool GlobalLog(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool GlobalLogWithClass(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
|
|
|
|
static JSFunctionSpec Global_funcs[] =
|
|
{
|
|
{ "Log", GlobalLog, 1, 0 },
|
|
{ "LogWithClass", GlobalLogWithClass, 2, 0 },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
static JSBool GlobalLog(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
NSString *logString = [NSString concatenationOfStringsFromJavaScriptValues:argv count:argc separator:@", " inContext:cx];
|
|
OOLog(kOOLogDebugMessage, logString);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool GlobalLogWithClass(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
NSString *logString = [NSString concatenationOfStringsFromJavaScriptValues:argv + 1 count:argc - 1 separator:@", " inContext:cx];
|
|
OOLog([NSString stringWithJavaScriptValue:argv[0] inContext:cx], logString);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool GlobalGetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
if (!JSVAL_IS_INT(name)) return JS_TRUE;
|
|
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
id<OOJavaScriptConversion> result = nil;
|
|
|
|
switch (JSVAL_TO_INT(name))
|
|
{
|
|
case GLOBAL_GALAXY_NUMBER:
|
|
result = [playerEntity galaxy_number];
|
|
break;
|
|
|
|
case GLOBAL_PLANET_NUMBER:
|
|
result = [playerEntity planet_number];
|
|
break;
|
|
|
|
case GLOBAL_GUI_SCREEN:
|
|
result = [playerEntity gui_screen_string];
|
|
break;
|
|
|
|
case GLOBAL_MISSION_VARS: {
|
|
JSObject *mv = JS_DefineObject(cx, xglob, "missionVariables", &MissionVars_class, 0x00, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
|
|
*vp = OBJECT_TO_JSVAL(mv);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (result != nil) *vp = [result javaScriptValueInContext:cx];
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
// Player proxy
|
|
//===========================================================================
|
|
|
|
static JSBool PlayerGetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp);
|
|
static JSBool PlayerSetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp);
|
|
|
|
|
|
static JSClass Player_class =
|
|
{
|
|
"Player",
|
|
JSCLASS_HAS_PRIVATE,
|
|
|
|
JS_PropertyStub,
|
|
JS_PropertyStub,
|
|
PlayerGetProperty,
|
|
PlayerSetProperty,
|
|
JS_EnumerateStub,
|
|
JS_ResolveStub,
|
|
JS_ConvertStub,
|
|
JS_FinalizeStub
|
|
};
|
|
|
|
|
|
enum Player_propertyIDs
|
|
{
|
|
PE_SHIP_DESCRIPTION,
|
|
PE_COMMANDER_NAME,
|
|
PE_SCORE,
|
|
PE_CREDITS,
|
|
PE_LEGAL_STATUS,
|
|
PE_FUEL_LEVEL,
|
|
PE_FUEL_LEAK_RATE,
|
|
PE_ALERT_CONDITION,
|
|
PE_STATUS_STRING,
|
|
PE_DOCKED_AT_MAIN_STATION,
|
|
PE_DOCKED_STATION_NAME,
|
|
|
|
// Special handling -- these correspond to ALERT_FLAG_FOO (PlayerEntity.h). 0x10 is shifted left by the low nybble to get the alert mask.
|
|
PE_DOCKED = 0xA0,
|
|
PE_ALERT_MASS_LOCKED = 0xA1,
|
|
PE_ALERT_TEMPERATURE = 0xA2,
|
|
PE_ALERT_ALTITUTE = 0xA3,
|
|
PE_ALERT_ENERGY = 0xA4,
|
|
PE_ALERT_HOSTILES = 0xA5
|
|
};
|
|
|
|
|
|
static JSPropertySpec Player_props[] =
|
|
{
|
|
// JS name ID flags
|
|
{ "shipDescription", PE_SHIP_DESCRIPTION, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "name", PE_COMMANDER_NAME, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "score", PE_SCORE, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "credits", PE_CREDITS, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "legalStatus", PE_LEGAL_STATUS, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "fuel", PE_FUEL_LEVEL, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "fuelLeakRate", PE_FUEL_LEAK_RATE, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "alertCondition", PE_ALERT_CONDITION, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "docked", PE_DOCKED, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "alertTemperature", PE_ALERT_TEMPERATURE, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "alertMassLocked", PE_ALERT_MASS_LOCKED, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "alertAltitude", PE_ALERT_ALTITUTE, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "alertEnergy", PE_ALERT_ENERGY, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "alertHostiles", PE_ALERT_HOSTILES, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "status", PE_STATUS_STRING, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "dockedAtMainStation", PE_DOCKED_AT_MAIN_STATION, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "stationName", PE_DOCKED_STATION_NAME, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
static JSBool PlayerAwardEquipment(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool PlayerRemoveEquipment(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool PlayerHasEquipment(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool PlayerLaunch(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool PlayerCall(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool PlayerAwardCargo(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool PlayerRemoveAllCargo(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool PlayerUseSpecialCargo(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
|
|
|
|
static JSFunctionSpec Player_funcs[] =
|
|
{
|
|
// JS name Function min args
|
|
{ "awardEquipment", PlayerAwardEquipment, 1 },
|
|
{ "removeEquipment", PlayerRemoveEquipment, 1 },
|
|
{ "hasEquipment", PlayerHasEquipment, 1 },
|
|
{ "launch", PlayerLaunch, 0 },
|
|
{ "call", PlayerCall, 1 },
|
|
{ "awardCargo", PlayerAwardCargo, 2 },
|
|
{ "removeAllCargo", PlayerRemoveAllCargo, 0 },
|
|
{ "useSpecialCargo", PlayerUseSpecialCargo, 1 },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
static JSBool PlayerAwardCargo(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
NSString *amount_type = [NSString stringWithFormat:@"%@ %@", JSValToNSString(cx, argv[1]), JSValToNSString(cx, argv[0])];
|
|
[playerEntity awardCargo:amount_type];
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool PlayerRemoveAllCargo(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
[playerEntity removeAllCargo];
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool PlayerUseSpecialCargo(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
if (argc == 1) {
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
[playerEntity useSpecialCargo:JSValToNSString(cx, argv[0])];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSBool PlayerAwardEquipment(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
if (argc > 0 && JSVAL_IS_STRING(argv[0])) {
|
|
JSString *jskey = JS_ValueToString(cx, argv[0]);
|
|
[playerEntity awardEquipment: [NSString stringWithCString:JS_GetStringBytes(jskey)]];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool PlayerRemoveEquipment(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
if (argc > 0 && JSVAL_IS_STRING(argv[0])) {
|
|
JSString *jskey = JS_ValueToString(cx, argv[0]);
|
|
[playerEntity removeEquipment: [NSString stringWithCString:JS_GetStringBytes(jskey)]];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool PlayerHasEquipment(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
if (argc > 0 && JSVAL_IS_STRING(argv[0]))
|
|
{
|
|
NSString *key = [NSString stringWithJavaScriptValue:argv[0] inContext:cx];
|
|
*rval = BOOLToJSVal([playerEntity has_extra_equipment:key]);
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool PlayerLaunch(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
[playerEntity launchFromStation];
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool PlayerCall(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = nil;
|
|
NSString *selectorString = nil;
|
|
SEL selector = NULL;
|
|
NSString *paramString = nil;
|
|
BOOL haveParameter = NO;
|
|
|
|
player = [PlayerEntity sharedPlayer];
|
|
selectorString = [NSString stringWithJavaScriptValue:argv[0] inContext:cx];
|
|
|
|
// Join all parameters together with spaces.
|
|
if (1 < argc && [selectorString hasSuffix:@":"])
|
|
{
|
|
haveParameter = YES;
|
|
paramString = [NSString concatenationOfStringsFromJavaScriptValues:argv + 1 count:argc - 1 separator:@" " inContext:cx];
|
|
}
|
|
|
|
selector = NSSelectorFromString(selectorString);
|
|
if ([player respondsToSelector:selector])
|
|
{
|
|
OOLog(@"script.trace.javaScript.call", @"Player.call: selector = %@, paramters = \"%@\"", selectorString, paramString);
|
|
OOLogIndentIf(@"script.trace.javaScript.call");
|
|
|
|
if (haveParameter) [player performSelector:selector withObject:paramString];
|
|
else [player performSelector:selector];
|
|
|
|
OOLogOutdentIf(@"script.trace.javaScript.call");
|
|
return JS_TRUE;
|
|
}
|
|
else
|
|
{
|
|
OOLog(@"script.javaScript.call.badSelector", @"**** Error in script %@: Player does not respond to call(%@).", [currentOOJSScript displayName], selectorString);
|
|
return JS_FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
static JSBool PlayerGetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
|
|
if (!JSVAL_IS_INT(name)) return JS_TRUE;
|
|
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
id<OOJavaScriptConversion> result = nil;
|
|
|
|
uint8_t ID = JSVAL_TO_INT(name);
|
|
switch (ID)
|
|
{
|
|
case PE_SHIP_DESCRIPTION:
|
|
result = [playerEntity commanderShip_string];
|
|
break;
|
|
|
|
case PE_COMMANDER_NAME:
|
|
result = [playerEntity commanderName_string];
|
|
break;
|
|
|
|
case PE_SCORE:
|
|
result = [playerEntity score_number];
|
|
break;
|
|
|
|
case PE_LEGAL_STATUS:
|
|
result = [playerEntity legalStatus_number];
|
|
break;
|
|
|
|
case PE_CREDITS:
|
|
result = [playerEntity credits_number];
|
|
break;
|
|
|
|
case PE_FUEL_LEVEL:
|
|
result = [playerEntity fuel_level_number];
|
|
break;
|
|
|
|
case PE_FUEL_LEAK_RATE:
|
|
result = [playerEntity fuel_leak_rate_number];
|
|
break;
|
|
|
|
case PE_ALERT_CONDITION:
|
|
*vp = INT_TO_JSVAL([playerEntity alert_condition]);
|
|
break;
|
|
|
|
case PE_DOCKED_AT_MAIN_STATION:
|
|
*vp = BooleanStringToJSVal([playerEntity dockedAtMainStation_bool]);
|
|
break;
|
|
|
|
case PE_DOCKED_STATION_NAME:
|
|
result = [playerEntity dockedStationName_string];
|
|
break;
|
|
|
|
case PE_STATUS_STRING:
|
|
result = [playerEntity status_string];
|
|
break;
|
|
|
|
default:
|
|
if ((ID & 0xF0) == 0xA0)
|
|
{
|
|
unsigned flags = [playerEntity alert_flags];
|
|
unsigned mask = 0x10 << ((unsigned)ID & 0xF);
|
|
*vp = BOOLToJSVal((flags & mask) != 0);
|
|
}
|
|
}
|
|
|
|
if (result != nil) *vp = [result javaScriptValueInContext:cx];
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool PlayerSetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
if (!JSVAL_IS_INT(name)) return JS_TRUE;
|
|
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
NSString *value = [NSString stringWithJavaScriptValue:*vp inContext:cx];
|
|
|
|
switch (JSVAL_TO_INT(name))
|
|
{
|
|
case PE_SCORE:
|
|
[playerEntity setKills:[value intValue]];
|
|
break;
|
|
|
|
case PE_LEGAL_STATUS:
|
|
[playerEntity setLegalStatus:value];
|
|
break;
|
|
|
|
case PE_CREDITS:
|
|
[playerEntity setCredits:[value intValue]];
|
|
break;
|
|
|
|
case PE_FUEL_LEVEL:
|
|
[playerEntity setCredits:(int)([value doubleValue] * 10.0)];
|
|
break;
|
|
|
|
case PE_FUEL_LEAK_RATE:
|
|
[playerEntity setFuelLeak:value];
|
|
break;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
// Universe (solar system) proxy
|
|
//===========================================================================
|
|
|
|
static JSBool SystemGetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp);
|
|
static JSBool SystemSetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp);
|
|
|
|
static JSClass System_class =
|
|
{
|
|
"Universe",
|
|
JSCLASS_HAS_PRIVATE,
|
|
|
|
JS_PropertyStub,
|
|
JS_PropertyStub,
|
|
SystemGetProperty,
|
|
SystemSetProperty,
|
|
JS_EnumerateStub,
|
|
JS_ResolveStub,
|
|
JS_ConvertStub,
|
|
JS_FinalizeStub
|
|
};
|
|
|
|
|
|
enum System_propertyIDs
|
|
{
|
|
SYS_ID,
|
|
SYS_NAME,
|
|
SYS_DESCRIPTION,
|
|
SYS_GOING_NOVA,
|
|
SYS_GONE_NOVA,
|
|
SYS_GOVT_STR,
|
|
SYS_GOVT_ID,
|
|
SYS_ECONOMY_STR,
|
|
SYS_ECONOMY_ID,
|
|
SYS_TECH_LVL,
|
|
SYS_POPULATION,
|
|
SYS_PRODUCTIVITY,
|
|
SYS_INHABITANTS
|
|
};
|
|
|
|
|
|
static JSPropertySpec System_props[] =
|
|
{
|
|
// JS name ID flags
|
|
{ "ID", SYS_ID, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "name", SYS_NAME, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "description", SYS_DESCRIPTION, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "inhabitantsDescription", SYS_INHABITANTS, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "goingNova", SYS_GOING_NOVA, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "goneNova", SYS_GONE_NOVA, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "government", SYS_GOVT_ID, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "governmentDescription", SYS_GOVT_STR, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "economy", SYS_ECONOMY_ID, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "economyDescription", SYS_ECONOMY_STR, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "techLevel", SYS_TECH_LVL, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "population", SYS_POPULATION, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "productivity", SYS_PRODUCTIVITY, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
static JSBool SystemAddPlanet(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemAddMoon(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemSendAllShipsAway(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemSetSunNova(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemCountShipsWithRole(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemAddShips(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemAddSystemShips(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemAddShipsAt(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemAddShipsAtPrecisely(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemAddShipsWithinRadius(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemSpawn(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemSpawnShip(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
|
|
|
|
static JSFunctionSpec System_funcs[] =
|
|
{
|
|
// JS name Function min args
|
|
{ "addPlanet", SystemAddPlanet, 1 },
|
|
{ "addMoon", SystemAddMoon, 1 },
|
|
{ "sendAllShipsAway", SystemSendAllShipsAway, 1 },
|
|
{ "setSunNova", SystemSetSunNova, 1 },
|
|
{ "countShipsWithRole", SystemCountShipsWithRole, 1, 0 },
|
|
{ "legacy_addShips", SystemAddShips, 2, 0 },
|
|
{ "legacy_addSystemShips", SystemAddSystemShips, 3, 0 },
|
|
{ "legacy_addShipsAt", SystemAddShipsAt, 6, 0 },
|
|
{ "legacy_addShipsAtPrecisely", SystemAddShipsAtPrecisely, 6, 0 },
|
|
{ "legacy_addShipsWithinRadius", SystemAddShipsWithinRadius, 7, 0 },
|
|
{ "legacy_spawn", SystemSpawn, 2, 0 },
|
|
{ "legacy_spawnShip", SystemSpawnShip, 1, 0 },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
static JSBool SystemAddPlanet(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
if (argc > 0 && JSVAL_IS_STRING(argv[0])) {
|
|
NSString *key = JSValToNSString(cx, argv[0]);
|
|
[playerEntity addPlanet:key];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemAddMoon(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
if (argc > 0 && JSVAL_IS_STRING(argv[0])) {
|
|
NSString *key = JSValToNSString(cx, argv[0]);
|
|
[playerEntity addMoon:key];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemSendAllShipsAway(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
[playerEntity sendAllShipsAway];
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemSetSunNova(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
if (argc > 0) {
|
|
NSString *key = JSValToNSString(cx, argv[0]);
|
|
[playerEntity setSunNovaIn:key];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static Random_Seed currentSystem;
|
|
static NSDictionary *planetinfo = nil;
|
|
|
|
static JSBool SystemGetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
if (!JSVAL_IS_INT(name)) return JS_TRUE;
|
|
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
id<OOJavaScriptConversion> result = nil;
|
|
|
|
if (!equal_seeds(currentSystem, playerEntity->system_seed))
|
|
{
|
|
currentSystem = playerEntity->system_seed;
|
|
|
|
[planetinfo release];
|
|
planetinfo = [[[Universe sharedUniverse] generateSystemData:currentSystem] retain];
|
|
}
|
|
|
|
switch (JSVAL_TO_INT(name))
|
|
{
|
|
case SYS_ID:
|
|
result = [playerEntity planet_number];
|
|
break;
|
|
|
|
case SYS_NAME:
|
|
if ([[Universe sharedUniverse] sun] != nil)
|
|
{
|
|
result = [planetinfo objectForKey:KEY_NAME];
|
|
if (result == nil) result = @"None"; // TODO: should this return JSVAL_VOID instead? Other cases below. -- ahruman
|
|
}
|
|
else
|
|
{
|
|
// Witchspace. (Hmm, does a system that's gone nova have a sun? If not, -[PlayerEntity planet_number] is broken, too.
|
|
result = @"Interstellar space";
|
|
}
|
|
break;
|
|
|
|
case SYS_DESCRIPTION:
|
|
result = [planetinfo objectForKey:KEY_DESCRIPTION];
|
|
if (result == nil) result = @"None";
|
|
break;
|
|
|
|
case SYS_INHABITANTS:
|
|
result = [planetinfo objectForKey:KEY_INHABITANTS];
|
|
if (result == nil) result = @"None";
|
|
break;
|
|
|
|
case SYS_GOING_NOVA:
|
|
*vp = BooleanStringToJSVal([playerEntity sunWillGoNova_bool]);
|
|
break;
|
|
|
|
case SYS_GONE_NOVA:
|
|
*vp = BooleanStringToJSVal([playerEntity sunGoneNova_bool]);
|
|
break;
|
|
|
|
case SYS_GOVT_ID:
|
|
result = [playerEntity systemGovernment_number];
|
|
break;
|
|
|
|
case SYS_GOVT_STR:
|
|
result = [playerEntity systemGovernment_string];
|
|
break;
|
|
|
|
case SYS_ECONOMY_ID:
|
|
result = [playerEntity systemEconomy_number];
|
|
break;
|
|
|
|
case SYS_ECONOMY_STR:
|
|
result = [playerEntity systemEconomy_string];
|
|
break;
|
|
|
|
case SYS_TECH_LVL:
|
|
result = [playerEntity systemTechLevel_number];
|
|
break;
|
|
|
|
case SYS_POPULATION:
|
|
result = [playerEntity systemPopulation_number];
|
|
break;
|
|
|
|
case SYS_PRODUCTIVITY:
|
|
result = [playerEntity systemProductivity_number];
|
|
break;
|
|
}
|
|
|
|
if (result != nil) *vp = [result javaScriptValueInContext:cx];
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemSetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
if (!JSVAL_IS_INT(name)) return JS_TRUE;
|
|
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
if (!equal_seeds(currentSystem, playerEntity->system_seed))
|
|
{
|
|
currentSystem = playerEntity->system_seed;
|
|
if (planetinfo) [planetinfo release];
|
|
|
|
planetinfo = [[[Universe sharedUniverse] generateSystemData:currentSystem] retain];
|
|
}
|
|
int gn = [[playerEntity galaxy_number] intValue];
|
|
int pn = [[playerEntity planet_number] intValue];
|
|
Universe *universe = [Universe sharedUniverse];
|
|
|
|
switch (JSVAL_TO_INT(name))
|
|
{
|
|
case SYS_NAME:
|
|
[universe setSystemDataForGalaxy:gn planet:pn key:KEY_NAME value:JSValToNSString(cx, *vp)];
|
|
break;
|
|
|
|
case SYS_DESCRIPTION:
|
|
[universe setSystemDataForGalaxy:gn planet:pn key:KEY_DESCRIPTION value:JSValToNSString(cx, *vp)];
|
|
break;
|
|
|
|
case SYS_INHABITANTS:
|
|
[universe setSystemDataForGalaxy:gn planet:pn key:KEY_INHABITANTS value:JSValToNSString(cx, *vp)];
|
|
break;
|
|
|
|
case SYS_GOING_NOVA:
|
|
*vp = BOOLToJSVal([[universe sun] willGoNova]);
|
|
break;
|
|
|
|
case SYS_GONE_NOVA:
|
|
*vp = BOOLToJSVal([[universe sun] goneNova]);
|
|
break;
|
|
|
|
case SYS_GOVT_ID:
|
|
[universe setSystemDataForGalaxy:gn planet:pn key:KEY_GOVERNMENT value:[NSNumber numberWithInt:[JSValToNSString(cx, *vp) intValue]]];
|
|
break;
|
|
|
|
case SYS_ECONOMY_ID:
|
|
[universe setSystemDataForGalaxy:gn planet:pn key:KEY_ECONOMY value:[NSNumber numberWithInt:[JSValToNSString(cx, *vp) intValue]]];
|
|
break;
|
|
|
|
case SYS_TECH_LVL:
|
|
[universe setSystemDataForGalaxy:gn planet:pn key:KEY_TECHLEVEL value:[NSNumber numberWithInt:[JSValToNSString(cx, *vp) intValue]]];
|
|
break;
|
|
|
|
case SYS_POPULATION:
|
|
[universe setSystemDataForGalaxy:gn planet:pn key:KEY_POPULATION value:[NSNumber numberWithInt:[JSValToNSString(cx, *vp) intValue]]];
|
|
break;
|
|
|
|
case SYS_PRODUCTIVITY:
|
|
[universe setSystemDataForGalaxy:gn planet:pn key:KEY_PRODUCTIVITY value:[NSNumber numberWithInt:[JSValToNSString(cx, *vp) intValue]]];
|
|
break;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemCountShipsWithRole(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
if (argc == 1)
|
|
{
|
|
NSString *role = JSValToNSString(cx, argv[0]);
|
|
int num = [[Universe sharedUniverse] countShipsWithRole:role];
|
|
*rval = INT_TO_JSVAL(num);
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemAddShips(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
if (argc == 2)
|
|
{
|
|
NSString *role = JSValToNSString(cx, argv[0]);
|
|
int num = JSVAL_TO_INT(argv[1]);
|
|
|
|
while (num--)
|
|
[[Universe sharedUniverse] witchspaceShipWithRole:role];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemAddSystemShips(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
if (argc == 3)
|
|
{
|
|
jsdouble posn;
|
|
NSString *role = JSValToNSString(cx, argv[0]);
|
|
int num = JSVAL_TO_INT(argv[1]);
|
|
JS_ValueToNumber(cx, argv[2], &posn);
|
|
while (num--)
|
|
[[Universe sharedUniverse] addShipWithRole:role nearRouteOneAt:posn];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemAddShipsAt(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
if (argc == 6)
|
|
{
|
|
jsdouble x, y, z;
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
NSString *role = JSValToNSString(cx, argv[0]);
|
|
int num = JSVAL_TO_INT(argv[1]);
|
|
NSString *coordScheme = JSValToNSString(cx, argv[2]);
|
|
JS_ValueToNumber(cx, argv[3], &x);
|
|
JS_ValueToNumber(cx, argv[4], &y);
|
|
JS_ValueToNumber(cx, argv[5], &z);
|
|
NSString *arg = [NSString stringWithFormat:@"%@ %d %@ %f %f %f", role, num, coordScheme, x, y, z];
|
|
[playerEntity addShipsAt:arg];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemAddShipsAtPrecisely(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
if (argc == 6)
|
|
{
|
|
jsdouble x, y, z;
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
NSString *role = JSValToNSString(cx, argv[0]);
|
|
int num = JSVAL_TO_INT(argv[1]);
|
|
NSString *coordScheme = JSValToNSString(cx, argv[2]);
|
|
JS_ValueToNumber(cx, argv[3], &x);
|
|
JS_ValueToNumber(cx, argv[4], &y);
|
|
JS_ValueToNumber(cx, argv[5], &z);
|
|
NSString *arg = [NSString stringWithFormat:@"%@ %d %@ %f %f %f", role, num, coordScheme, x, y, z];
|
|
[playerEntity addShipsAtPrecisely:arg];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemAddShipsWithinRadius(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
if (argc == 7)
|
|
{
|
|
jsdouble x, y, z;
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
NSString *role = JSValToNSString(cx, argv[0]);
|
|
int num = JSVAL_TO_INT(argv[1]);
|
|
NSString *coordScheme = JSValToNSString(cx, argv[2]);
|
|
JS_ValueToNumber(cx, argv[3], &x);
|
|
JS_ValueToNumber(cx, argv[4], &y);
|
|
JS_ValueToNumber(cx, argv[5], &z);
|
|
int rad = JSVAL_TO_INT(argv[6]);
|
|
NSString *arg = [NSString stringWithFormat:@"%@ %d %@ %f %f %f %d", role, num, coordScheme, x, y, z, rad];
|
|
[playerEntity addShipsAt:arg];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemSpawn(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
if (argc == 2)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
NSString *role = JSValToNSString(cx, argv[0]);
|
|
int num = JSVAL_TO_INT(argv[1]);
|
|
NSString *arg = [NSString stringWithFormat:@"%@ %d", role, num];
|
|
[playerEntity spawn:arg];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemSpawnShip(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
if (argc == 1)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
[playerEntity spawnShip:JSValToNSString(cx, argv[0])];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
// Mission class
|
|
//===========================================================================
|
|
|
|
static JSBool MissionGetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp);
|
|
static JSBool MissionSetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp);
|
|
|
|
|
|
static JSClass Mission_class =
|
|
{
|
|
"Mission",
|
|
JSCLASS_HAS_PRIVATE,
|
|
|
|
JS_PropertyStub,
|
|
JS_PropertyStub,
|
|
MissionGetProperty,
|
|
MissionSetProperty,
|
|
JS_EnumerateStub,
|
|
JS_ResolveStub,
|
|
JS_ConvertStub,
|
|
JS_FinalizeStub
|
|
};
|
|
|
|
|
|
enum Mission_propertyIDs
|
|
{
|
|
MISSION_TEXT, MISSION_MUSIC, MISSION_IMAGE, MISSION_CHOICES, MISSION_CHOICE, MISSION_INSTRUCTIONS
|
|
};
|
|
|
|
static JSPropertySpec Mission_props[] =
|
|
{
|
|
{ "missionScreenTextKey", MISSION_TEXT, JSPROP_ENUMERATE },
|
|
{ "musicFileName", MISSION_MUSIC, JSPROP_ENUMERATE },
|
|
{ "imageFileName", MISSION_IMAGE, JSPROP_ENUMERATE },
|
|
{ "choicesKey", MISSION_CHOICES, JSPROP_ENUMERATE },
|
|
{ "choice", MISSION_CHOICE, JSPROP_ENUMERATE },
|
|
{ "instructionsKey", MISSION_INSTRUCTIONS, JSPROP_ENUMERATE },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
static JSBool MissionShowMissionScreen(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool MissionShowShipModel(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool MissionResetMissionChoice(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool MissionMarkSystem(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool MissionUnmarkSystem(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
|
|
|
|
static JSFunctionSpec Mission_funcs[] =
|
|
{
|
|
{ "showMissionScreen", MissionShowMissionScreen, 0, 0 },
|
|
{ "showShipModel", MissionShowShipModel, 1, 0 },
|
|
{ "resetMissionChoice", MissionResetMissionChoice, 0, 0 },
|
|
{ "markSystem", MissionMarkSystem, 1, 0 },
|
|
{ "unmarkSystem", MissionUnmarkSystem, 1, 0 },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
static JSBool MissionGetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
if (!JSVAL_IS_INT(name)) return JS_TRUE;
|
|
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
id<OOJavaScriptConversion> result = nil;
|
|
|
|
switch (JSVAL_TO_INT(name))
|
|
{
|
|
case MISSION_CHOICE:
|
|
result = [playerEntity missionChoice_string];
|
|
if (result == nil) result = @"None";
|
|
break;
|
|
}
|
|
|
|
if (result != nil) *vp = [result javaScriptValueInContext:cx];
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool MissionSetProperty(JSContext *cx, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
if (!JSVAL_IS_INT(name)) return JS_TRUE;
|
|
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
|
|
switch (JSVAL_TO_INT(name)) {
|
|
case MISSION_TEXT: {
|
|
if (JSVAL_IS_STRING(*vp)) {
|
|
JSString *jskey = JS_ValueToString(cx, *vp);
|
|
[playerEntity addMissionText: [NSString stringWithCString:JS_GetStringBytes(jskey)]];
|
|
}
|
|
break;
|
|
}
|
|
case MISSION_MUSIC: {
|
|
if (JSVAL_IS_STRING(*vp)) {
|
|
JSString *jskey = JS_ValueToString(cx, *vp);
|
|
[playerEntity setMissionMusic: [NSString stringWithCString:JS_GetStringBytes(jskey)]];
|
|
}
|
|
break;
|
|
}
|
|
case MISSION_IMAGE: {
|
|
if (JSVAL_IS_STRING(*vp)) {
|
|
NSString *str = JSValToNSString(cx, *vp);
|
|
if ([str length] == 0)
|
|
str = @"none";
|
|
[playerEntity setMissionImage:str];
|
|
}
|
|
break;
|
|
}
|
|
case MISSION_CHOICES: {
|
|
if (JSVAL_IS_STRING(*vp)) {
|
|
JSString *jskey = JS_ValueToString(cx, *vp);
|
|
[playerEntity setMissionChoices: [NSString stringWithCString:JS_GetStringBytes(jskey)]];
|
|
}
|
|
break;
|
|
}
|
|
case MISSION_INSTRUCTIONS: {
|
|
if (JSVAL_IS_STRING(*vp)) {
|
|
JSString *jskey = JS_ValueToString(cx, *vp);
|
|
NSString *ins = [NSString stringWithCString:JS_GetStringBytes(jskey)];
|
|
if ([ins length])
|
|
[playerEntity setMissionDescription:ins forMission:[currentOOJSScript name]];
|
|
else
|
|
[playerEntity clearMissionDescriptionForMission:[currentOOJSScript name]];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool MissionShowMissionScreen(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
[playerEntity setGuiToMissionScreen];
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool MissionShowShipModel(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
if (argc > 0 && JSVAL_IS_STRING(argv[0])) {
|
|
JSString *jskey = JS_ValueToString(cx, argv[0]);
|
|
[playerEntity showShipModel: [NSString stringWithCString:JS_GetStringBytes(jskey)]];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool MissionResetMissionChoice(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
[playerEntity resetMissionChoice];
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool MissionMarkSystem(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
NSString *params = [NSString concatenationOfStringsFromJavaScriptValues:argv count:argc separator:@" " inContext:cx];
|
|
|
|
[playerEntity addMissionDestination:params];
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool MissionUnmarkSystem(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *playerEntity = [PlayerEntity sharedPlayer];
|
|
NSString *params = [NSString concatenationOfStringsFromJavaScriptValues:argv count:argc separator:@" " inContext:cx];
|
|
|
|
[playerEntity removeMissionDestination:params];
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static void ReportJSError(JSContext *cx, const char *message, JSErrorReport *report)
|
|
{
|
|
NSString *severity = nil;
|
|
NSString *messageText = nil;
|
|
NSString *lineBuf = nil;
|
|
NSString *messageClass = nil;
|
|
|
|
// Type of problem: error, warning or exception? (Strict flag wilfully ignored.)
|
|
if (report->flags & JSREPORT_EXCEPTION) severity = @"exception";
|
|
else if (report->flags & JSREPORT_WARNING) severity = @"warning";
|
|
else severity = @"error";
|
|
|
|
// The error message itself
|
|
messageText = [NSString stringWithUTF16String:report->ucmessage];
|
|
|
|
// Get offending line, if present, and trim trailing line breaks
|
|
lineBuf = [NSString stringWithUTF16String:report->uclinebuf];
|
|
while ([lineBuf hasSuffix:@"\n"] || [lineBuf hasSuffix:@"\r"]) lineBuf = [lineBuf substringToIndex:[lineBuf length] - 1];
|
|
|
|
// Log message class
|
|
messageClass = [NSString stringWithFormat:@"script.javaScript.%@.%u", severity, report->errorNumber];
|
|
|
|
// First line: problem description
|
|
OOLog(messageClass, @"***** JavaScript %@: %@", severity, messageText);
|
|
|
|
// Second line: where error occured, and line if provided. (The line is only provided for compile-time errors, not run-time errors.)
|
|
if ([lineBuf length] != 0)
|
|
{
|
|
OOLog(messageClass, @" %s, line %d: %@", report->filename, report->lineno, lineBuf);
|
|
}
|
|
else
|
|
{
|
|
OOLog(messageClass, @" %s, line %d.", report->filename, report->lineno);
|
|
}
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
// JavaScript engine initialisation and shutdown
|
|
//===========================================================================
|
|
|
|
@implementation OOJavaScriptEngine
|
|
|
|
+ (OOJavaScriptEngine *)sharedEngine
|
|
{
|
|
if (sSharedEngine == nil) [[self alloc] init];
|
|
|
|
return sSharedEngine;
|
|
}
|
|
|
|
|
|
- (id) init
|
|
{
|
|
assert(sSharedEngine == nil);
|
|
|
|
self = [super init];
|
|
|
|
assert(sizeof(jschar) == sizeof(unichar));
|
|
|
|
/*set up global JS variables, including global and custom objects */
|
|
|
|
/* initialize the JS run time, and return result in rt */
|
|
rt = JS_NewRuntime(8L * 1024L * 1024L);
|
|
|
|
/* if rt does not have a value, end the program here */
|
|
if (!rt)
|
|
{
|
|
OOLog(@"script.javaScript.init.error", @"FATAL ERROR: failed to create JavaScript %@.", @"runtime");
|
|
exit(1);
|
|
}
|
|
|
|
/* create a context and associate it with the JS run time */
|
|
cx = JS_NewContext(rt, 8192);
|
|
|
|
/* if cx does not have a value, end the program here */
|
|
if (!cx)
|
|
{
|
|
OOLog(@"script.javaScript.init.error", @"FATAL ERROR: failed to create JavaScript %@.", @"context");
|
|
exit(1);
|
|
}
|
|
|
|
JS_SetErrorReporter(cx, ReportJSError);
|
|
|
|
/* create the global object here */
|
|
glob = JS_NewObject(cx, &global_class, NULL, NULL);
|
|
xglob = glob;
|
|
|
|
/* initialize the built-in JS objects and the global object */
|
|
builtins = JS_InitStandardClasses(cx, glob);
|
|
JS_DefineProperties(cx, glob, Global_props);
|
|
JS_DefineFunctions(cx, glob, Global_funcs);
|
|
|
|
universeObj = JS_DefineObject(cx, glob, "universe", &System_class, NULL, JSPROP_ENUMERATE);
|
|
//JS_DefineProperties(cx, universeObj, System_props);
|
|
JS_DefineFunctions(cx, universeObj, System_funcs);
|
|
|
|
systemObj = JS_DefineObject(cx, glob, "system", &System_class, NULL, JSPROP_ENUMERATE);
|
|
JS_DefineProperties(cx, systemObj, System_props);
|
|
JS_DefineFunctions(cx, systemObj, System_funcs);
|
|
|
|
playerObj = JS_DefineObject(cx, glob, "player", &Player_class, NULL, JSPROP_ENUMERATE);
|
|
JS_DefineProperties(cx, playerObj, Player_props);
|
|
JS_DefineFunctions(cx, playerObj, Player_funcs);
|
|
|
|
missionObj = JS_DefineObject(cx, glob, "mission", &Mission_class, NULL, JSPROP_ENUMERATE);
|
|
JS_DefineProperties(cx, missionObj, Mission_props);
|
|
JS_DefineFunctions(cx, missionObj, Mission_funcs);
|
|
|
|
OOLog(@"script.javaScript.init.success", @"Set up JavaScript context.");
|
|
|
|
sSharedEngine = self;
|
|
return self;
|
|
}
|
|
|
|
|
|
- (void) dealloc
|
|
{
|
|
sSharedEngine = nil;
|
|
|
|
JS_DestroyContext(cx);
|
|
JS_DestroyRuntime(rt);
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
|
|
- (JSContext *) context
|
|
{
|
|
return cx;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation NSString (OOJavaScriptExtensions)
|
|
|
|
// Convert a JSString to an NSString.
|
|
+ (id)stringWithJavaScriptString:(JSString *)string
|
|
{
|
|
jschar *chars = NULL;
|
|
size_t length;
|
|
|
|
chars = JS_GetStringChars(string);
|
|
length = JS_GetStringLength(string);
|
|
|
|
return [NSString stringWithCharacters:chars length:length];
|
|
}
|
|
|
|
|
|
+ (id)stringWithJavaScriptValue:(jsval)value inContext:(JSContext *)context
|
|
{
|
|
JSString *string = NULL;
|
|
|
|
string = JS_ValueToString(context, value); // Calls the value's convert method if needed.
|
|
return [NSString stringWithJavaScriptString:string];
|
|
}
|
|
|
|
|
|
- (jsval)javaScriptValueInContext:(JSContext *)context
|
|
{
|
|
size_t length;
|
|
unichar *buffer = NULL;
|
|
JSString *string = NULL;
|
|
|
|
length = [self length];
|
|
buffer = malloc(length * sizeof *buffer);
|
|
if (buffer == NULL) return JSVAL_VOID;
|
|
|
|
[self getCharacters:buffer];
|
|
|
|
string = JS_NewUCStringCopyN(context, buffer, length);
|
|
free(buffer);
|
|
|
|
return STRING_TO_JSVAL(string);
|
|
}
|
|
|
|
|
|
+ (id)concatenationOfStringsFromJavaScriptValues:(jsval *)values count:(size_t)count separator:(NSString *)separator inContext:(JSContext *)context
|
|
{
|
|
size_t i;
|
|
NSMutableString *result = nil;
|
|
NSString *element = nil;
|
|
|
|
if (count < 1) return nil;
|
|
if (values == NULL) return NULL;
|
|
|
|
for (i = 0; i != count; ++i)
|
|
{
|
|
element = [NSString stringWithJavaScriptValue:values[i] inContext:context];
|
|
if (result == nil) result = [element mutableCopy];
|
|
else
|
|
{
|
|
if (separator != nil) [result appendString:separator];
|
|
[result appendString:element];
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation NSNumber (OOJavaScriptExtensions)
|
|
|
|
- (jsval)javaScriptValueInContext:(JSContext *)context
|
|
{
|
|
jsval result;
|
|
BOOL isFloat = NO;
|
|
const char *type;
|
|
long long longLongValue;
|
|
|
|
if (self == [NSNumber numberWithBool:YES])
|
|
{
|
|
/* Under OS X, at least, numberWithBool: returns one of two singletons.
|
|
There is no other way to reliably identify a boolean NSNumber.
|
|
Fun, eh? */
|
|
result = JSVAL_TRUE;
|
|
}
|
|
else if (self == [NSNumber numberWithBool:NO])
|
|
{
|
|
result = JSVAL_FALSE;
|
|
}
|
|
else
|
|
{
|
|
longLongValue = [self longLongValue];
|
|
if (longLongValue < (long long)JSVAL_INT_MIN || (long long)JSVAL_INT_MAX < longLongValue)
|
|
{
|
|
// values outside JSVAL_INT range are returned as doubles.
|
|
isFloat = YES;
|
|
}
|
|
else
|
|
{
|
|
// Check value type.
|
|
type = [self objCType];
|
|
if (type[0] == 'f' || type[0] == 'd') isFloat = YES;
|
|
}
|
|
|
|
if (isFloat)
|
|
{
|
|
if (!JS_NewDoubleValue(context, [self doubleValue], &result)) result = JSVAL_VOID;
|
|
}
|
|
else
|
|
{
|
|
result = INT_TO_JSVAL(longLongValue);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
NSString *JSPropertyAsString(JSContext *context, JSObject *object, const char *name)
|
|
{
|
|
JSBool OK;
|
|
jsval returnValue;
|
|
NSString *result;
|
|
|
|
if (context == NULL || object == NULL || name == NULL) return nil;
|
|
|
|
OK = JS_GetProperty(context, object, name, &returnValue);
|
|
if (OK && !JSVAL_IS_VOID(returnValue))
|
|
{
|
|
result = [NSString stringWithJavaScriptValue:returnValue inContext:context];
|
|
}
|
|
|
|
return result;
|
|
}
|