diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index e5d7456b..5651a911 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -626,6 +626,11 @@ Forth: extensions: - .4th +Game Maker Language: + type: programming + lexer: JavaScript + primary_extension: .gml + GAS: type: programming group: Assembly diff --git a/lib/linguist/samples.json b/lib/linguist/samples.json index 2fd22b9e..a46586cd 100644 --- a/lib/linguist/samples.json +++ b/lib/linguist/samples.json @@ -132,6 +132,9 @@ ".forth", ".fth" ], + "Game Maker Language": [ + ".gml" + ], "GAS": [ ".s" ], @@ -575,8 +578,8 @@ ".gemrc" ] }, - "tokens_total": 456690, - "languages_total": 569, + "tokens_total": 470000, + "languages_total": 582, "tokens": { "ABAP": { "*/**": 1, @@ -18134,6 +18137,1541 @@ "/cell": 2, "cell": 2 }, + "Game Maker Language": { + "//draws": 1, + "the": 62, + "sprite": 12, + "draw": 3, + "true": 73, + ";": 1282, + "if": 397, + "(": 1501, + "facing": 17, + "RIGHT": 10, + ")": 1502, + "image_xscale": 17, + "-": 212, + "else": 151, + "blinkToggle": 1, + "{": 300, + "state": 50, + "CLIMBING": 5, + "or": 78, + "sprite_index": 14, + "sPExit": 1, + "sDamselExit": 1, + "sTunnelExit": 1, + "and": 155, + "global.hasJetpack": 4, + "not": 63, + "whipping": 5, + "draw_sprite_ext": 10, + "x": 76, + "y": 85, + "image_yscale": 14, + "image_angle": 14, + "image_blend": 2, + "image_alpha": 10, + "//draw_sprite": 1, + "draw_sprite": 9, + "sJetpackBack": 1, + "false": 85, + "}": 307, + "sJetpackRight": 1, + "sJetpackLeft": 1, + "+": 206, + "redColor": 2, + "make_color_rgb": 1, + "holdArrow": 4, + "ARROW_NORM": 2, + "sArrowRight": 1, + "ARROW_BOMB": 2, + "holdArrowToggle": 2, + "sBombArrowRight": 2, + "LEFT": 7, + "sArrowLeft": 1, + "sBombArrowLeft": 2, + "hangCountMax": 2, + "//////////////////////////////////////": 2, + "kLeft": 12, + "checkLeft": 1, + "kLeftPushedSteps": 3, + "kLeftPressed": 2, + "checkLeftPressed": 1, + "kLeftReleased": 3, + "checkLeftReleased": 1, + "kRight": 12, + "checkRight": 1, + "kRightPushedSteps": 3, + "kRightPressed": 2, + "checkRightPressed": 1, + "kRightReleased": 3, + "checkRightReleased": 1, + "kUp": 5, + "checkUp": 1, + "kDown": 5, + "checkDown": 1, + "//key": 1, + "canRun": 1, + "kRun": 2, + "kJump": 6, + "checkJump": 1, + "kJumpPressed": 11, + "checkJumpPressed": 1, + "kJumpReleased": 5, + "checkJumpReleased": 1, + "cantJump": 3, + "global.isTunnelMan": 1, + "sTunnelAttackL": 1, + "holdItem": 1, + "kAttack": 2, + "checkAttack": 2, + "kAttackPressed": 2, + "checkAttackPressed": 1, + "kAttackReleased": 2, + "checkAttackReleased": 1, + "kItemPressed": 2, + "checkItemPressed": 1, + "xPrev": 1, + "yPrev": 1, + "stunned": 3, + "dead": 3, + "//////////////////////////////////////////": 2, + "colSolidLeft": 4, + "colSolidRight": 3, + "colLeft": 6, + "colRight": 6, + "colTop": 4, + "colBot": 11, + "colLadder": 3, + "colPlatBot": 6, + "colPlat": 5, + "colWaterTop": 3, + "colIceBot": 2, + "runKey": 4, + "isCollisionMoveableSolidLeft": 1, + "isCollisionMoveableSolidRight": 1, + "isCollisionLeft": 2, + "isCollisionRight": 2, + "isCollisionTop": 1, + "isCollisionBottom": 1, + "isCollisionLadder": 1, + "isCollisionPlatformBottom": 1, + "isCollisionPlatform": 1, + "isCollisionWaterTop": 1, + "collision_point": 30, + "oIce": 1, + "checkRun": 1, + "runHeld": 3, + "HANGING": 10, + "approximatelyZero": 4, + "xVel": 24, + "xAcc": 12, + "platformCharacterIs": 23, + "ON_GROUND": 18, + "DUCKING": 4, + "pushTimer": 3, + "//if": 5, + "SS_IsSoundPlaying": 2, + "global.sndPush": 4, + "playSound": 3, + "runAcc": 2, + "abs": 9, + "alarm": 13, + "[": 99, + "]": 103, + "<": 39, + "floor": 11, + "/": 5, + "/xVel": 1, + "instance_exists": 8, + "oCape": 2, + "oCape.open": 6, + "kJumped": 7, + "ladderTimer": 4, + "ladder": 5, + "oLadder": 4, + "ladder.x": 3, + "oLadderTop": 2, + "yAcc": 26, + "climbAcc": 2, + "FALLING": 8, + "STANDING": 2, + "departLadderXVel": 2, + "departLadderYVel": 1, + "JUMPING": 6, + "jumpButtonReleased": 7, + "jumpTime": 8, + "IN_AIR": 5, + "gravityIntensity": 2, + "yVel": 20, + "RUNNING": 3, + "jumps": 3, + "//playSound": 1, + "global.sndLand": 1, + "grav": 22, + "global.hasGloves": 3, + "hangCount": 14, + "*": 18, + "yVel*0.3": 1, + "oWeb": 2, + "obj": 14, + "instance_place": 3, + "obj.life": 1, + "initialJumpAcc": 6, + "xVel/2": 3, + "gravNorm": 7, + "global.hasCape": 1, + "jetpackFuel": 2, + "fallTimer": 2, + "global.hasJordans": 1, + "yAccLimit": 2, + "global.hasSpringShoes": 1, + "global.sndJump": 1, + "jumpTimeTotal": 2, + "//let": 1, + "character": 20, + "continue": 4, + "to": 62, + "jump": 1, + "jumpTime/jumpTimeTotal": 1, + "looking": 2, + "UP": 1, + "LOOKING_UP": 4, + "oSolid": 14, + "move_snap": 6, + "oTree": 4, + "oArrow": 5, + "instance_nearest": 1, + "obj.stuck": 1, + "//the": 2, + "can": 1, + "t": 23, + "want": 1, + "use": 4, + "because": 2, + "is": 9, + "too": 2, + "high": 1, + "yPrevHigh": 1, + "//": 11, + "we": 5, + "ll": 1, + "move": 2, + "correct": 1, + "distance": 1, + "but": 2, + "need": 1, + "shorten": 1, + "out": 4, + "a": 55, + "little": 1, + "ratio": 1, + "xVelInteger": 2, + "/dist*0.9": 1, + "//can": 1, + "be": 4, + "changed": 1, + "moveTo": 2, + "round": 6, + "xVelInteger*ratio": 1, + "yVelInteger*ratio": 1, + "slopeChangeInY": 1, + "maxDownSlope": 1, + "floating": 1, + "just": 1, + "above": 1, + "slope": 1, + "so": 2, + "down": 1, + "upYPrev": 1, + "for": 26, + "<=upYPrev+maxDownSlope;y+=1)>": 1, + "hit": 1, + "solid": 1, + "below": 1, + "upYPrev=": 1, + "I": 1, + "know": 1, + "that": 2, + "this": 2, + "doesn": 1, + "seem": 1, + "make": 1, + "sense": 1, + "of": 25, + "name": 9, + "variable": 1, + "it": 6, + "all": 3, + "works": 1, + "correctly": 1, + "after": 1, + "break": 58, + "loop": 1, + "y=": 1, + "figures": 1, + "what": 1, + "index": 11, + "should": 25, + "characterSprite": 1, + "sets": 1, + "previous": 2, + "previously": 1, + "statePrevPrev": 1, + "statePrev": 2, + "calculates": 1, + "image_speed": 9, + "based": 1, + "on": 4, + "s": 6, + "velocity": 1, + "runAnimSpeed": 1, + "0": 21, + "1": 32, + "sqrt": 1, + "sqr": 2, + "climbAnimSpeed": 1, + "<=>": 3, + "4": 2, + "setCollisionBounds": 3, + "8": 9, + "5": 5, + "DUCKTOHANG": 1, + "image_index": 1, + "limit": 5, + "at": 23, + "animation": 1, + "always": 1, + "looks": 1, + "good": 1, + "var": 79, + "i": 95, + "playerObject": 1, + "playerID": 1, + "player": 36, + "otherPlayerID": 1, + "otherPlayer": 1, + "sameVersion": 1, + "buffer": 1, + "plugins": 4, + "pluginsRequired": 2, + "usePlugins": 1, + "tcp_eof": 3, + "global.serverSocket": 10, + "gotServerHello": 2, + "show_message": 7, + "instance_destroy": 7, + "exit": 10, + "room": 1, + "DownloadRoom": 1, + "keyboard_check": 1, + "vk_escape": 1, + "downloadingMap": 2, + "while": 15, + "tcp_receive": 3, + "min": 4, + "downloadMapBytes": 2, + "buffer_size": 2, + "downloadMapBuffer": 6, + "write_buffer": 2, + "write_buffer_to_file": 1, + "downloadMapName": 3, + "buffer_destroy": 8, + "roomchange": 2, + "do": 1, + "switch": 9, + "read_ubyte": 10, + "case": 50, + "HELLO": 1, + "global.joinedServerName": 2, + "receivestring": 4, + "advertisedMapMd5": 1, + "receiveCompleteMessage": 1, + "global.tempBuffer": 3, + "string_pos": 20, + "Server": 3, + "sent": 7, + "illegal": 2, + "map": 47, + "This": 2, + "server": 10, + "requires": 1, + "following": 2, + "play": 2, + "#": 3, + "suggests": 1, + "optional": 1, + "Error": 2, + "ocurred": 1, + "loading": 1, + "plugins.": 1, + "Maps/": 2, + ".png": 2, + "The": 6, + "version": 4, + "Enter": 1, + "Password": 1, + "Incorrect": 1, + "Password.": 1, + "Incompatible": 1, + "protocol": 3, + "version.": 1, + "Name": 1, + "Exploit": 1, + "Invalid": 2, + "plugin": 6, + "packet": 3, + "ID": 2, + "There": 1, + "are": 1, + "many": 1, + "connections": 1, + "from": 5, + "your": 1, + "IP": 1, + "You": 1, + "have": 2, + "been": 1, + "kicked": 1, + "server.": 1, + ".": 12, + "#Server": 1, + "went": 1, + "invalid": 1, + "internal": 1, + "#Exiting.": 1, + "full.": 1, + "noone": 7, + "ERROR": 1, + "when": 1, + "reading": 1, + "no": 1, + "such": 1, + "unexpected": 1, + "data.": 1, + "until": 1, + "downloadHandle": 3, + "url": 62, + "tmpfile": 3, + "window_oldshowborder": 2, + "window_oldfullscreen": 2, + "timeLeft": 1, + "counter": 1, + "AudioControlPlaySong": 1, + "window_get_showborder": 1, + "window_get_fullscreen": 1, + "window_set_fullscreen": 2, + "window_set_showborder": 1, + "global.updaterBetaChannel": 3, + "UPDATE_SOURCE_BETA": 1, + "UPDATE_SOURCE": 1, + "temp_directory": 1, + "httpGet": 1, + "httpRequestStatus": 1, + "download": 1, + "isn": 1, + "extract": 1, + "downloaded": 1, + "file": 2, + "now.": 1, + "extractzip": 1, + "working_directory": 6, + "execute_program": 1, + "game_end": 1, + "victim": 10, + "killer": 11, + "assistant": 16, + "damageSource": 18, + "argument0": 28, + "argument1": 10, + "argument2": 3, + "argument3": 1, + "//*************************************": 6, + "//*": 3, + "Scoring": 1, + "Kill": 1, + "log": 1, + "recordKillInLog": 1, + "victim.stats": 1, + "DEATHS": 1, + "WEAPON_KNIFE": 1, + "||": 16, + "WEAPON_BACKSTAB": 1, + "killer.stats": 8, + "STABS": 2, + "killer.roundStats": 8, + "POINTS": 10, + "victim.object.currentWeapon.object_index": 1, + "Medigun": 2, + "victim.object.currentWeapon.uberReady": 1, + "BONUS": 2, + "KILLS": 2, + "victim.object.intel": 1, + "DEFENSES": 2, + "recordEventInLog": 1, + "killer.team": 1, + "killer.name": 2, + "global.myself": 4, + "assistant.stats": 2, + "ASSISTS": 2, + "assistant.roundStats": 2, + ".5": 2, + "//SPEC": 1, + "instance_create": 20, + "victim.object.x": 3, + "victim.object.y": 3, + "Spectator": 1, + "Gibbing": 2, + "xoffset": 5, + "yoffset": 5, + "xsize": 3, + "ysize": 3, + "view_xview": 3, + "view_yview": 3, + "view_wview": 2, + "view_hview": 2, + "randomize": 1, + "with": 47, + "victim.object": 2, + "WEAPON_ROCKETLAUNCHER": 1, + "WEAPON_MINEGUN": 1, + "FRAG_BOX": 2, + "WEAPON_REFLECTED_STICKY": 1, + "WEAPON_REFLECTED_ROCKET": 1, + "FINISHED_OFF_GIB": 2, + "GENERATOR_EXPLOSION": 2, + "player.class": 15, + "CLASS_QUOTE": 3, + "global.gibLevel": 14, + "distance_to_point": 3, + "xsize/2": 2, + "ysize/2": 2, + "hasReward": 4, + "repeat": 7, + "createGib": 14, + "PumpkinGib": 1, + "hspeed": 14, + "vspeed": 13, + "random": 21, + "choose": 8, + "Gib": 1, + "player.team": 8, + "TEAM_BLUE": 6, + "BlueClump": 1, + "TEAM_RED": 8, + "RedClump": 1, + "blood": 2, + "BloodDrop": 1, + "blood.hspeed": 1, + "blood.vspeed": 1, + "blood.sprite_index": 1, + "PumpkinJuiceS": 1, + "//All": 1, + "Classes": 1, + "gib": 1, + "head": 1, + "hands": 2, + "feet": 1, + "Headgib": 1, + "//Medic": 1, + "has": 2, + "specially": 1, + "colored": 1, + "CLASS_MEDIC": 2, + "Hand": 3, + "Feet": 1, + "//Class": 1, + "specific": 1, + "gibs": 1, + "CLASS_PYRO": 2, + "Accesory": 5, + "CLASS_SOLDIER": 2, + "CLASS_ENGINEER": 3, + "CLASS_SNIPER": 3, + "playsound": 2, + "deadbody": 2, + "DeathSnd1": 1, + "DeathSnd2": 1, + "DeadGuy": 1, + "deadbody.sprite_index": 2, + "haxxyStatue": 1, + "deadbody.image_index": 2, + "CHARACTER_ANIMATION_DEAD": 1, + "deadbody.hspeed": 1, + "deadbody.vspeed": 1, + "deadbody.image_xscale": 1, + "global.gg_birthday": 1, + "myHat": 2, + "PartyHat": 1, + "myHat.image_index": 2, + "victim.team": 2, + "global.xmas": 1, + "XmasHat": 1, + "Deathcam": 1, + "global.killCam": 3, + "KILL_BOX": 1, + "FINISHED_OFF": 5, + "DeathCam": 1, + "DeathCam.killedby": 1, + "DeathCam.name": 1, + "DeathCam.oldxview": 1, + "DeathCam.oldyview": 1, + "DeathCam.lastDamageSource": 1, + "DeathCam.team": 1, + "global.myself.team": 3, + "xr": 19, + "yr": 19, + "cloakAlpha": 1, + "team": 13, + "canCloak": 1, + "cloakAlpha/2": 1, + "invisible": 1, + "stabbing": 2, + "power": 1, + "currentWeapon.stab.alpha": 1, + "&&": 6, + "global.showHealthBar": 3, + "draw_set_alpha": 3, + "draw_healthbar": 1, + "hp*100/maxHp": 1, + "c_black": 1, + "c_red": 3, + "c_green": 1, + "mouse_x": 1, + "mouse_y": 1, + "<25)>": 1, + "cloak": 2, + "global": 8, + "myself": 2, + "draw_set_halign": 1, + "fa_center": 1, + "draw_set_valign": 1, + "fa_bottom": 1, + "team=": 1, + "draw_set_color": 2, + "c_blue": 2, + "draw_text": 4, + "35": 1, + "showTeammateStats": 1, + "weapons": 3, + "50": 3, + "Superburst": 1, + "string": 13, + "currentWeapon": 2, + "uberCharge": 1, + "20": 1, + "Shotgun": 1, + "Nuts": 1, + "N": 1, + "Bolts": 1, + "nutsNBolts": 1, + "Minegun": 1, + "Lobbed": 1, + "Mines": 1, + "lobbed": 1, + "ubercolour": 6, + "overlaySprite": 6, + "zoomed": 1, + "SniperCrouchRedS": 1, + "SniperCrouchBlueS": 1, + "sniperCrouchOverlay": 1, + "overlay": 1, + "omnomnomnom": 2, + "draw_sprite_ext_overlay": 7, + "omnomnomnomSprite": 2, + "omnomnomnomOverlay": 2, + "omnomnomnomindex": 4, + "c_white": 13, + "ubered": 7, + "7": 4, + "taunting": 2, + "tauntsprite": 2, + "tauntOverlay": 2, + "tauntindex": 2, + "humiliated": 1, + "humiliationPoses": 1, + "animationImage": 9, + "humiliationOffset": 1, + "animationOffset": 6, + "burnDuration": 2, + "burnIntensity": 2, + "numFlames": 1, + "maxIntensity": 1, + "FlameS": 1, + "flameArray_x": 1, + "flameArray_y": 1, + "maxDuration": 1, + "demon": 4, + "demonX": 5, + "median": 2, + "demonY": 4, + "demonOffset": 4, + "demonDir": 2, + "dir": 3, + "demonFrame": 5, + "sprite_get_number": 1, + "*player.team": 2, + "dir*1": 2, + "#define": 26, + "__http_init": 3, + "global.__HttpClient": 4, + "object_add": 1, + "object_set_persistent": 1, + "__http_split": 3, + "text": 19, + "delimeter": 7, + "list": 36, + "count": 4, + "ds_list_create": 5, + "ds_list_add": 23, + "string_copy": 32, + "string_length": 25, + "return": 56, + "__http_parse_url": 4, + "ds_map_create": 4, + "ds_map_add": 15, + "colonPos": 22, + "string_char_at": 13, + "slashPos": 13, + "real": 14, + "queryPos": 12, + "ds_map_destroy": 6, + "__http_resolve_url": 2, + "baseUrl": 3, + "refUrl": 18, + "urlParts": 15, + "refUrlParts": 5, + "canParseRefUrl": 3, + "result": 11, + "ds_map_find_value": 22, + "__http_resolve_path": 3, + "ds_map_replace": 3, + "ds_map_exists": 11, + "ds_map_delete": 1, + "path": 10, + "query": 4, + "relUrl": 1, + "__http_construct_url": 2, + "basePath": 4, + "refPath": 7, + "parts": 29, + "refParts": 5, + "lastPart": 3, + "ds_list_find_value": 9, + "ds_list_size": 11, + "ds_list_delete": 5, + "ds_list_destroy": 4, + "part": 6, + "ds_list_replace": 3, + "__http_parse_hex": 2, + "hexString": 4, + "hexValues": 3, + "digit": 4, + "__http_prepare_request": 4, + "client": 33, + "headers": 11, + "parsed": 18, + "show_error": 2, + "destroyed": 3, + "CR": 10, + "chr": 3, + "LF": 5, + "CRLF": 17, + "socket": 40, + "tcp_connect": 1, + "errored": 19, + "error": 18, + "linebuf": 33, + "line": 19, + "statusCode": 6, + "reasonPhrase": 2, + "responseBody": 19, + "buffer_create": 7, + "responseBodySize": 5, + "responseBodyProgress": 5, + "responseHeaders": 9, + "requestUrl": 2, + "requestHeaders": 2, + "write_string": 9, + "key": 17, + "ds_map_find_first": 1, + "is_string": 2, + "ds_map_find_next": 1, + "socket_send": 1, + "__http_parse_header": 3, + "ord": 16, + "headerValue": 9, + "string_lower": 3, + "headerName": 4, + "__http_client_step": 2, + "socket_has_error": 1, + "socket_error": 1, + "__http_client_destroy": 20, + "available": 7, + "tcp_receive_available": 1, + "bytesRead": 6, + "c": 20, + "read_string": 9, + "Reached": 2, + "end": 11, + "HTTP": 1, + "defines": 1, + "sequence": 2, + "as": 1, + "marker": 1, + "elements": 1, + "except": 2, + "entity": 1, + "body": 2, + "see": 1, + "appendix": 1, + "19": 1, + "3": 1, + "tolerant": 1, + "applications": 1, + "Strip": 1, + "trailing": 1, + "First": 1, + "status": 2, + "code": 2, + "first": 3, + "Response": 1, + "message": 1, + "Status": 1, + "Line": 1, + "consisting": 1, + "followed": 1, + "by": 5, + "numeric": 1, + "its": 1, + "associated": 1, + "textual": 1, + "phrase": 1, + "each": 18, + "element": 8, + "separated": 1, + "SP": 1, + "characters": 3, + "No": 3, + "allowed": 1, + "in": 21, + "final": 1, + "httpVer": 2, + "spacePos": 11, + "space": 4, + "response": 5, + "second": 2, + "Other": 1, + "Blank": 1, + "write": 1, + "remainder": 1, + "write_buffer_part": 3, + "Header": 1, + "Receiving": 1, + "transfer": 6, + "encoding": 2, + "chunked": 4, + "Chunked": 1, + "let": 1, + "decode": 36, + "actualResponseBody": 8, + "actualResponseSize": 1, + "actualResponseBodySize": 3, + "Parse": 1, + "chunks": 1, + "chunk": 12, + "size": 7, + "extension": 3, + "data": 4, + "HEX": 1, + "buffer_bytes_left": 6, + "chunkSize": 11, + "Read": 1, + "byte": 2, + "We": 1, + "found": 21, + "semicolon": 1, + "beginning": 1, + "skip": 1, + "stuff": 2, + "header": 2, + "Doesn": 1, + "did": 1, + "empty": 13, + "something": 1, + "up": 6, + "Parsing": 1, + "failed": 56, + "hex": 2, + "was": 1, + "hexadecimal": 1, + "Is": 1, + "bigger": 2, + "than": 1, + "remaining": 1, + "2": 2, + "responseHaders": 1, + "location": 4, + "resolved": 5, + "socket_destroy": 4, + "http_new_get": 1, + "variable_global_exists": 2, + "http_new_get_ex": 1, + "http_step": 1, + "client.errored": 3, + "client.state": 3, + "http_status_code": 1, + "client.statusCode": 1, + "http_reason_phrase": 1, + "client.error": 1, + "client.reasonPhrase": 1, + "http_response_body": 1, + "client.responseBody": 1, + "http_response_body_size": 1, + "client.responseBodySize": 1, + "http_response_body_progress": 1, + "client.responseBodyProgress": 1, + "http_response_headers": 1, + "client.responseHeaders": 1, + "http_destroy": 1, + "RoomChangeObserver": 1, + "set_little_endian_global": 1, + "file_exists": 5, + "file_delete": 3, + "backupFilename": 5, + "file_find_first": 1, + "file_find_next": 1, + "file_find_close": 1, + "customMapRotationFile": 7, + "restart": 4, + "//import": 1, + "wav": 1, + "files": 1, + "music": 1, + "global.MenuMusic": 3, + "sound_add": 3, + "global.IngameMusic": 3, + "global.FaucetMusic": 3, + "sound_volume": 3, + "global.sendBuffer": 19, + "global.HudCheck": 1, + "global.map_rotation": 19, + "global.CustomMapCollisionSprite": 1, + "window_set_region_scale": 1, + "ini_open": 2, + "global.playerName": 7, + "ini_read_string": 12, + "string_count": 2, + "MAX_PLAYERNAME_LENGTH": 2, + "global.fullscreen": 3, + "ini_read_real": 65, + "global.useLobbyServer": 2, + "global.hostingPort": 2, + "global.music": 2, + "MUSIC_BOTH": 1, + "global.playerLimit": 4, + "//thy": 1, + "playerlimit": 1, + "shalt": 1, + "exceed": 1, + "global.dedicatedMode": 7, + "ini_write_real": 60, + "global.multiClientLimit": 2, + "global.particles": 2, + "PARTICLES_NORMAL": 1, + "global.monitorSync": 3, + "set_synchronization": 2, + "global.medicRadar": 2, + "global.showHealer": 2, + "global.showHealing": 2, + "global.showTeammateStats": 2, + "global.serverPluginsPrompt": 2, + "global.restartPrompt": 2, + "//user": 1, + "HUD": 1, + "settings": 1, + "global.timerPos": 2, + "global.killLogPos": 2, + "global.kothHudPos": 2, + "global.clientPassword": 1, + "global.shuffleRotation": 2, + "global.timeLimitMins": 2, + "max": 2, + "global.serverPassword": 2, + "global.mapRotationFile": 1, + "global.serverName": 2, + "global.welcomeMessage": 2, + "global.caplimit": 3, + "global.caplimitBkup": 1, + "global.autobalance": 2, + "global.Server_RespawntimeSec": 4, + "global.rewardKey": 1, + "unhex": 1, + "global.rewardId": 1, + "global.mapdownloadLimitBps": 2, + "isBetaVersion": 1, + "global.attemptPortForward": 2, + "global.serverPluginList": 3, + "global.serverPluginsRequired": 2, + "CrosshairFilename": 5, + "CrosshairRemoveBG": 4, + "global.queueJumping": 2, + "global.backgroundHash": 2, + "global.backgroundTitle": 2, + "global.backgroundURL": 2, + "global.backgroundShowVersion": 2, + "readClasslimitsFromIni": 1, + "global.currentMapArea": 1, + "global.totalMapAreas": 1, + "global.setupTimer": 1, + "global.serverPluginsInUse": 1, + "global.pluginPacketBuffers": 1, + "global.pluginPacketPlayers": 1, + "ini_write_string": 10, + "ini_key_delete": 1, + "global.classlimits": 10, + "CLASS_SCOUT": 1, + "CLASS_HEAVY": 2, + "CLASS_DEMOMAN": 1, + "CLASS_SPY": 1, + "//screw": 1, + "will": 1, + "start": 1, + "//map_truefort": 1, + "maps": 37, + "//map_2dfort": 1, + "//map_conflict": 1, + "//map_classicwell": 1, + "//map_waterway": 1, + "//map_orange": 1, + "//map_dirtbowl": 1, + "//map_egypt": 1, + "//arena_montane": 1, + "//arena_lumberyard": 1, + "//gen_destroy": 1, + "//koth_valley": 1, + "//koth_corinth": 1, + "//koth_harvest": 1, + "//dkoth_atalia": 1, + "//dkoth_sixties": 1, + "//Server": 1, + "respawn": 1, + "time": 1, + "calculator.": 1, + "Converts": 1, + "frame.": 1, + "read": 1, + "multiply": 1, + "hehe": 1, + "global.Server_Respawntime": 3, + "global.mapchanging": 1, + "ini_close": 2, + "global.protocolUuid": 2, + "parseUuid": 2, + "PROTOCOL_UUID": 1, + "global.gg2lobbyId": 2, + "GG2_LOBBY_UUID": 1, + "initRewards": 1, + "IPRaw": 3, + "portRaw": 3, + "doubleCheck": 8, + "global.launchMap": 5, + "parameter_count": 1, + "parameter_string": 8, + "global.serverPort": 1, + "global.serverIP": 1, + "global.isHost": 1, + "Client": 1, + "global.customMapdesginated": 2, + "fileHandle": 6, + "mapname": 9, + "file_text_open_read": 1, + "file_text_eof": 1, + "file_text_read_string": 1, + "starts": 1, + "tab": 2, + "string_delete": 1, + "delete": 1, + "comment": 1, + "starting": 1, + "file_text_readln": 1, + "file_text_close": 1, + "load": 1, + "ini": 1, + "Maps": 9, + "section": 1, + "//Set": 1, + "rotation": 1, + "sort_list": 7, + "*maps": 1, + "ds_list_sort": 1, + "mod": 1, + "global.gg2Font": 2, + "font_add_sprite": 2, + "gg2FontS": 1, + "global.countFont": 1, + "countFontS": 1, + "draw_set_font": 1, + "cursor_sprite": 1, + "CrosshairS": 5, + "directory_exists": 2, + "directory_create": 2, + "AudioControl": 1, + "SSControl": 1, + "message_background": 1, + "popupBackgroundB": 1, + "message_button": 1, + "popupButtonS": 1, + "message_text_font": 1, + "message_button_font": 1, + "message_input_font": 1, + "//Key": 1, + "Mapping": 1, + "global.jump": 1, + "global.down": 1, + "global.left": 1, + "global.right": 1, + "global.attack": 1, + "MOUSE_LEFT": 1, + "global.special": 1, + "MOUSE_RIGHT": 1, + "global.taunt": 1, + "global.chat1": 1, + "global.chat2": 1, + "global.chat3": 1, + "global.medic": 1, + "global.drop": 1, + "global.changeTeam": 1, + "global.changeClass": 1, + "global.showScores": 1, + "vk_shift": 1, + "calculateMonthAndDay": 1, + "loadplugins": 1, + "registry_set_root": 1, + "HKLM": 1, + "global.NTKernelVersion": 1, + "registry_read_string_ext": 1, + "CurrentVersion": 1, + "SIC": 1, + "sprite_replace": 1, + "sprite_set_offset": 1, + "sprite_get_width": 1, + "/2": 2, + "sprite_get_height": 1, + "AudioControlToggleMute": 1, + "room_goto_fix": 2, + "Menu": 2, + "__jso_gmt_tuple": 1, + "//Position": 1, + "address": 1, + "table": 1, + "pos": 2, + "addr_table": 2, + "*argument_count": 1, + "//Build": 1, + "tuple": 1, + "ca": 1, + "isstr": 1, + "datastr": 1, + "argument_count": 1, + "//Check": 1, + "argument": 10, + "Unexpected": 18, + "position": 16, + "f": 5, + "JSON": 5, + "string.": 5, + "Cannot": 5, + "parse": 3, + "boolean": 3, + "value": 13, + "expecting": 9, + "digit.": 9, + "e": 4, + "E": 4, + "dot": 1, + "an": 24, + "integer": 6, + "Expected": 6, + "least": 6, + "arguments": 26, + "got": 6, + "find": 10, + "lookup.": 4, + "indices": 1, + "nested": 27, + "lists": 6, + "Index": 1, + "overflow": 4, + "Recursive": 1, + "abcdef": 1, + "number": 7, + "num": 1, + "assert_true": 1, + "_assert_error_popup": 2, + "string_repeat": 2, + "_assert_newline": 2, + "assert_false": 1, + "assert_equal": 1, + "//Safe": 1, + "equality": 1, + "check": 1, + "won": 1, + "support": 1, + "instead": 1, + "_assert_debug_value": 1, + "//String": 1, + "os_browser": 1, + "browser_not_a_browser": 1, + "string_replace_all": 1, + "//Numeric": 1, + "GMTuple": 1, + "jso_encode_string": 1, + "encode": 8, + "escape": 2, + "jso_encode_map": 4, + "one": 42, + "key1": 3, + "key2": 3, + "multi": 7, + "jso_encode_list": 3, + "three": 36, + "_jso_decode_string": 5, + "small": 1, + "quick": 2, + "brown": 2, + "fox": 2, + "over": 2, + "lazy": 2, + "dog.": 2, + "simple": 1, + "Waahoo": 1, + "negg": 1, + "mixed": 1, + "_jso_decode_boolean": 2, + "_jso_decode_real": 11, + "standard": 1, + "zero": 4, + "signed": 2, + "decimal": 1, + "digits": 1, + "positive": 7, + "negative": 7, + "exponent": 4, + "_jso_decode_integer": 3, + "_jso_decode_map": 14, + "didn": 14, + "include": 14, + "right": 14, + "prefix": 14, + "#1": 14, + "#2": 14, + "entry": 29, + "pi": 2, + "bool": 2, + "waahoo": 10, + "woohah": 8, + "mix": 4, + "_jso_decode_list": 14, + "woo": 2, + "Empty": 4, + "equal": 20, + "other.": 12, + "junk": 2, + "info": 1, + "taxi": 1, + "An": 4, + "filled": 4, + "map.": 2, + "A": 24, + "B": 18, + "C": 8, + "same": 6, + "content": 4, + "entered": 4, + "different": 12, + "orders": 4, + "D": 1, + "keys": 2, + "values": 4, + "six": 1, + "corresponding": 4, + "types": 4, + "other": 4, + "crash.": 4, + "list.": 2, + "Lists": 4, + "two": 16, + "entries": 2, + "also": 2, + "jso_map_check": 9, + "existing": 9, + "single": 11, + "jso_map_lookup": 3, + "wrong": 10, + "trap": 2, + "jso_map_lookup_type": 3, + "type": 8, + "four": 21, + "inexistent": 11, + "multiple": 20, + "jso_list_check": 8, + "jso_list_lookup": 3, + "jso_list_lookup_type": 3, + "inner": 1, + "indexing": 1, + "bad": 1, + "jso_cleanup_map": 1, + "one_map": 1, + "hashList": 5, + "pluginname": 9, + "pluginhash": 4, + "realhash": 1, + "handle": 1, + "filesize": 1, + "progress": 1, + "tempfile": 1, + "tempdir": 1, + "lastContact": 2, + "isCached": 2, + "isDebug": 2, + "split": 1, + "checkpluginname": 1, + "ds_list_find_index": 1, + ".zip": 3, + "ServerPluginsCache": 6, + "@": 5, + ".zip.tmp": 1, + ".tmp": 2, + "ServerPluginsDebug": 1, + "Warning": 2, + "being": 2, + "loaded": 2, + "ServerPluginsDebug.": 2, + "Make": 2, + "sure": 2, + "clients": 1, + "they": 1, + "may": 2, + "unable": 2, + "connect.": 2, + "you": 1, + "Downloading": 1, + "last_plugin.log": 2, + "plugin.gml": 1, + "playerId": 11, + "commandLimitRemaining": 4, + "variable_local_exists": 4, + "commandReceiveState": 1, + "commandReceiveExpectedBytes": 1, + "commandReceiveCommand": 1, + "player.socket": 12, + "player.commandReceiveExpectedBytes": 7, + "player.commandReceiveState": 7, + "player.commandReceiveCommand": 4, + "commandBytes": 2, + "commandBytesInvalidCommand": 1, + "commandBytesPrefixLength1": 1, + "commandBytesPrefixLength2": 1, + "default": 1, + "read_ushort": 2, + "PLAYER_LEAVE": 1, + "PLAYER_CHANGECLASS": 1, + "class": 8, + "getCharacterObject": 2, + "player.object": 12, + "SpawnRoom": 2, + "lastDamageDealer": 8, + "sendEventPlayerDeath": 4, + "BID_FAREWELL": 4, + "doEventPlayerDeath": 4, + "secondToLastDamageDealer": 2, + "lastDamageDealer.object": 2, + "lastDamageDealer.object.healer": 4, + "player.alarm": 4, + "<=0)>": 1, + "checkClasslimits": 2, + "ServerPlayerChangeclass": 2, + "sendBuffer": 1, + "PLAYER_CHANGETEAM": 1, + "newTeam": 7, + "balance": 5, + "redSuperiority": 6, + "calculate": 1, + "which": 1, + "Player": 1, + "TEAM_SPECTATOR": 1, + "newClass": 4, + "ServerPlayerChangeteam": 1, + "ServerBalanceTeams": 1, + "CHAT_BUBBLE": 2, + "bubbleImage": 5, + "global.aFirst": 1, + "write_ubyte": 20, + "setChatBubble": 1, + "BUILD_SENTRY": 2, + "collision_circle": 1, + "player.object.x": 3, + "player.object.y": 3, + "Sentry": 1, + "player.object.nutsNBolts": 1, + "player.sentry": 2, + "player.object.onCabinet": 1, + "write_ushort": 2, + "global.serializeBuffer": 3, + "player.object.x*5": 1, + "player.object.y*5": 1, + "write_byte": 1, + "player.object.image_xscale": 2, + "buildSentry": 1, + "DESTROY_SENTRY": 1, + "DROP_INTEL": 1, + "player.object.intel": 1, + "sendEventDropIntel": 1, + "doEventDropIntel": 1, + "OMNOMNOMNOM": 2, + "player.humiliated": 1, + "player.object.taunting": 1, + "player.object.omnomnomnom": 1, + "player.object.canEat": 1, + "omnomnomnomend": 2, + "xscale": 1, + "TOGGLE_ZOOM": 2, + "toggleZoom": 1, + "PLAYER_CHANGENAME": 2, + "nameLength": 4, + "socket_receivebuffer_size": 3, + "KICK": 2, + "KICK_NAME": 1, + "current_time": 2, + "lastNamechange": 2, + "INPUTSTATE": 1, + "keyState": 1, + "netAimDirection": 1, + "aimDirection": 1, + "netAimDirection*360/65536": 1, + "event_user": 1, + "REWARD_REQUEST": 1, + "player.rewardId": 1, + "player.challenge": 2, + "rewardCreateChallenge": 1, + "REWARD_CHALLENGE_CODE": 1, + "write_binstring": 1, + "REWARD_CHALLENGE_RESPONSE": 1, + "answer": 3, + "authbuffer": 1, + "read_binstring": 1, + "rewardAuthStart": 1, + "challenge": 1, + "rewardId": 1, + "PLUGIN_PACKET": 1, + "packetID": 3, + "buf": 5, + "success": 3, + "_PluginPacketPush": 1, + "KICK_BAD_PLUGIN_PACKET": 1, + "CLIENT_SETTINGS": 2, + "mirror": 4, + "player.queueJump": 1, + "global.levelType": 22, + "//global.currLevel": 1, + "global.currLevel": 22, + "global.hadDarkLevel": 4, + "global.startRoomX": 1, + "global.startRoomY": 1, + "global.endRoomX": 1, + "global.endRoomY": 1, + "oGame.levelGen": 2, + "j": 14, + "global.roomPath": 1, + "k": 5, + "global.lake": 3, + "isLevel": 1, + "999": 2, + "levelType": 2, + "16": 14, + "656": 3, + "oDark": 2, + "invincible": 2, + "sDark": 1, + "oTemple": 2, + "cityOfGold": 1, + "sTemple": 2, + "lake": 1, + "i*16": 8, + "j*16": 6, + "oLush": 2, + "obj.sprite_index": 4, + "sLush": 2, + "obj.invincible": 3, + "oBrick": 1, + "sBrick": 1, + "global.cityOfGold": 2, + "*16": 2, + "//instance_create": 2, + "oSpikes": 1, + "background_index": 1, + "bgTemple": 1, + "global.temp1": 1, + "global.gameStart": 3, + "scrLevelGen": 1, + "global.cemetary": 3, + "rand": 10, + "global.probCemetary": 1, + "oRoom": 1, + "scrRoomGen": 1, + "global.blackMarket": 3, + "scrRoomGenMarket": 1, + "scrRoomGen2": 1, + "global.yetiLair": 2, + "scrRoomGenYeti": 1, + "scrRoomGen3": 1, + "scrRoomGen4": 1, + "scrRoomGen5": 1, + "global.darkLevel": 4, + "global.noDarkLevel": 1, + "global.probDarkLevel": 1, + "oPlayer1.x": 2, + "oPlayer1.y": 2, + "oFlare": 1, + "global.genUdjatEye": 4, + "global.madeUdjatEye": 1, + "global.genMarketEntrance": 4, + "global.madeMarketEntrance": 1, + "////////////////////////////": 2, + "global.temp2": 1, + "isRoom": 3, + "scrEntityGen": 1, + "oEntrance": 1, + "global.customLevel": 1, + "oEntrance.x": 1, + "oEntrance.y": 1, + "global.snakePit": 1, + "global.alienCraft": 1, + "global.sacrificePit": 1, + "oPlayer1": 1, + "scrSetupWalls": 3, + "global.graphicsHigh": 1, + "tile_add": 4, + "bgExtrasLush": 1, + "*rand": 12, + "bgExtrasIce": 1, + "bgExtrasTemple": 1, + "bgExtras": 1, + "global.murderer": 1, + "global.thiefLevel": 1, + "isRealLevel": 1, + "oExit": 1, + "oShopkeeper": 1, + "obj.status": 1, + "oTreasure": 1, + "oWater": 1, + "sWaterTop": 1, + "sLavaTop": 1, + "scrCheckWaterTop": 1, + "global.temp3": 1 + }, "GAS": { ".cstring": 1, "LC0": 2, @@ -49609,6 +51147,7 @@ "Erlang": 2928, "fish": 636, "Forth": 1516, + "Game Maker Language": 13310, "GAS": 133, "GLSL": 3766, "Gnuplot": 1023, @@ -49760,6 +51299,7 @@ "Erlang": 5, "fish": 3, "Forth": 7, + "Game Maker Language": 13, "GAS": 1, "GLSL": 3, "Gnuplot": 6, @@ -49873,5 +51413,5 @@ "Xtend": 2, "YAML": 2 }, - "md5": "369eb61211321cd0a217de22110731ac" + "md5": "1b2f1436b9e8b1bff75b4f13f34c390e" } \ No newline at end of file diff --git a/samples/Game Maker Language/ClientBeginStep.gml b/samples/Game Maker Language/ClientBeginStep.gml new file mode 100644 index 00000000..64d14110 --- /dev/null +++ b/samples/Game Maker Language/ClientBeginStep.gml @@ -0,0 +1,642 @@ +/* + Originally from /Source/gg2/Scripts/Client/ClientBeginStep.gml in Gang Garrison 2 + + Copyright (C) 2008-2013 Faucet Software + http://www.ganggarrison.com + + 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 3 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, + see . + + Additional permission under GNU GPL version 3 section 7 + If you modify this Program, or any covered work, by linking or combining it with the Game Maker runtime library, + the 39dll library/extension, Hobbel's Download Manager DLL, or modified versions of these libraries, + the licensors of this Program grant you additional permission to convey the resulting work. +*/ + +// receive and interpret the server's message(s) +var i, playerObject, playerID, player, otherPlayerID, otherPlayer, sameVersion, buffer, plugins, pluginsRequired, usePlugins; + +if(tcp_eof(global.serverSocket)) { + if(gotServerHello) + show_message("You have been disconnected from the server."); + else + show_message("Unable to connect to the server."); + instance_destroy(); + exit; +} + +if(room == DownloadRoom and keyboard_check(vk_escape)) +{ + instance_destroy(); + exit; +} + +if(downloadingMap) +{ + while(tcp_receive(global.serverSocket, min(1024, downloadMapBytes-buffer_size(downloadMapBuffer)))) + { + write_buffer(downloadMapBuffer, global.serverSocket); + if(buffer_size(downloadMapBuffer) == downloadMapBytes) + { + write_buffer_to_file(downloadMapBuffer, "Maps/" + downloadMapName + ".png"); + downloadingMap = false; + buffer_destroy(downloadMapBuffer); + downloadMapBuffer = -1; + exit; + } + } + exit; +} + +roomchange = false; +do { + if(tcp_receive(global.serverSocket,1)) { + switch(read_ubyte(global.serverSocket)) { + case HELLO: + gotServerHello = true; + global.joinedServerName = receivestring(global.serverSocket, 1); + downloadMapName = receivestring(global.serverSocket, 1); + advertisedMapMd5 = receivestring(global.serverSocket, 1); + receiveCompleteMessage(global.serverSocket, 1, global.tempBuffer); + pluginsRequired = read_ubyte(global.tempBuffer); + plugins = receivestring(global.serverSocket, 1); + if(string_pos("/", downloadMapName) != 0 or string_pos("\", downloadMapName) != 0) + { + show_message("Server sent illegal map name: "+downloadMapName); + instance_destroy(); + exit; + } + + if (!noReloadPlugins && string_length(plugins)) + { + usePlugins = pluginsRequired || !global.serverPluginsPrompt; + if (global.serverPluginsPrompt) + { + var prompt; + if (pluginsRequired) + { + prompt = show_question( + "This server requires the following plugins to play on it: " + + string_replace_all(plugins, ",", "#") + + '#They are downloaded from the source: "' + + PLUGIN_SOURCE + + '"#The source states: "' + + PLUGIN_SOURCE_NOTICE + + '"#Do you wish to download them and continue connecting?' + ); + if (!prompt) + { + instance_destroy(); + exit; + } + } + else + { + prompt = show_question( + "This server suggests the following optional plugins to play on it: " + + string_replace_all(plugins, ",", "#") + + '#They are downloaded from the source: "' + + PLUGIN_SOURCE + + '"#The source states: "' + + PLUGIN_SOURCE_NOTICE + + '"#Do you wish to download them and use them?' + ); + if (prompt) + { + usePlugins = true; + } + } + } + if (usePlugins) + { + if (!loadserverplugins(plugins)) + { + show_message("Error ocurred loading server-sent plugins."); + instance_destroy(); + exit; + } + global.serverPluginsInUse = true; + } + } + noReloadPlugins = false; + + if(advertisedMapMd5 != "") + { + var download; + download = not file_exists("Maps/" + downloadMapName + ".png"); + if(!download and CustomMapGetMapMD5(downloadMapName) != advertisedMapMd5) + { + if(show_question("The server's copy of the map (" + downloadMapName + ") differs from ours.#Would you like to download this server's version of the map?")) + download = true; + else + { + instance_destroy(); + exit; + } + } + + if(download) + { + write_ubyte(global.serverSocket, DOWNLOAD_MAP); + socket_send(global.serverSocket); + receiveCompleteMessage(global.serverSocket,4,global.tempBuffer); + downloadMapBytes = read_uint(global.tempBuffer); + downloadMapBuffer = buffer_create(); + downloadingMap = true; + roomchange=true; + } + } + ClientPlayerJoin(global.serverSocket); + if(global.rewardKey != "" and global.rewardId != "") + { + var rewardId; + rewardId = string_copy(global.rewardId, 0, 255); + write_ubyte(global.serverSocket, REWARD_REQUEST); + write_ubyte(global.serverSocket, string_length(rewardId)); + write_string(global.serverSocket, rewardId); + } + if(global.queueJumping == true) + { + write_ubyte(global.serverSocket, CLIENT_SETTINGS); + write_ubyte(global.serverSocket, global.queueJumping); + } + socket_send(global.serverSocket); + break; + + case JOIN_UPDATE: + receiveCompleteMessage(global.serverSocket,2,global.tempBuffer); + global.playerID = read_ubyte(global.tempBuffer); + global.currentMapArea = read_ubyte(global.tempBuffer); + break; + + case FULL_UPDATE: + deserializeState(FULL_UPDATE); + break; + + case QUICK_UPDATE: + deserializeState(QUICK_UPDATE); + break; + + case CAPS_UPDATE: + deserializeState(CAPS_UPDATE); + break; + + case INPUTSTATE: + deserializeState(INPUTSTATE); + break; + + case PLAYER_JOIN: + player = instance_create(0,0,Player); + player.name = receivestring(global.serverSocket, 1); + + ds_list_add(global.players, player); + if(ds_list_size(global.players)-1 == global.playerID) { + global.myself = player; + instance_create(0,0,PlayerControl); + } + break; + + case PLAYER_LEAVE: + // Delete player from the game, adjust own ID accordingly + receiveCompleteMessage(global.serverSocket,1,global.tempBuffer); + playerID = read_ubyte(global.tempBuffer); + player = ds_list_find_value(global.players, playerID); + removePlayer(player); + if(playerID < global.playerID) { + global.playerID -= 1; + } + break; + + case PLAYER_DEATH: + var causeOfDeath, assistantPlayerID, assistantPlayer; + receiveCompleteMessage(global.serverSocket,4,global.tempBuffer); + playerID = read_ubyte(global.tempBuffer); + otherPlayerID = read_ubyte(global.tempBuffer); + assistantPlayerID = read_ubyte(global.tempBuffer); + causeOfDeath = read_ubyte(global.tempBuffer); + + player = ds_list_find_value(global.players, playerID); + + otherPlayer = noone; + if(otherPlayerID != 255) + otherPlayer = ds_list_find_value(global.players, otherPlayerID); + + assistantPlayer = noone; + if(assistantPlayerID != 255) + assistantPlayer = ds_list_find_value(global.players, assistantPlayerID); + + doEventPlayerDeath(player, otherPlayer, assistantPlayer, causeOfDeath); + break; + + case BALANCE: + receiveCompleteMessage(global.serverSocket,1,global.tempBuffer); + balanceplayer=read_ubyte(global.tempBuffer); + if balanceplayer == 255 { + if !instance_exists(Balancer) instance_create(x,y,Balancer); + with(Balancer) notice=0; + } else { + player = ds_list_find_value(global.players, balanceplayer); + if(player.object != -1) { + with(player.object) { + instance_destroy(); + } + player.object = -1; + } + if(player.team==TEAM_RED) { + player.team = TEAM_BLUE; + } else { + player.team = TEAM_RED; + } + if !instance_exists(Balancer) instance_create(x,y,Balancer); + Balancer.name=player.name; + with (Balancer) notice=1; + } + break; + + case PLAYER_CHANGETEAM: + receiveCompleteMessage(global.serverSocket,2,global.tempBuffer); + player = ds_list_find_value(global.players, read_ubyte(global.tempBuffer)); + if(player.object != -1) { + with(player.object) { + instance_destroy(); + } + player.object = -1; + } + player.team = read_ubyte(global.tempBuffer); + break; + + case PLAYER_CHANGECLASS: + receiveCompleteMessage(global.serverSocket,2,global.tempBuffer); + player = ds_list_find_value(global.players, read_ubyte(global.tempBuffer)); + if(player.object != -1) { + with(player.object) { + instance_destroy(); + } + player.object = -1; + } + player.class = read_ubyte(global.tempBuffer); + break; + + case PLAYER_CHANGENAME: + receiveCompleteMessage(global.serverSocket,1,global.tempBuffer); + player = ds_list_find_value(global.players, read_ubyte(global.tempBuffer)); + player.name = receivestring(global.serverSocket, 1); + if player=global.myself { + global.playerName=player.name + } + break; + + case PLAYER_SPAWN: + receiveCompleteMessage(global.serverSocket,3,global.tempBuffer); + player = ds_list_find_value(global.players, read_ubyte(global.tempBuffer)); + doEventSpawn(player, read_ubyte(global.tempBuffer), read_ubyte(global.tempBuffer)); + break; + + case CHAT_BUBBLE: + var bubbleImage; + receiveCompleteMessage(global.serverSocket,2,global.tempBuffer); + player = ds_list_find_value(global.players, read_ubyte(global.tempBuffer)); + setChatBubble(player, read_ubyte(global.tempBuffer)); + break; + + case BUILD_SENTRY: + receiveCompleteMessage(global.serverSocket,6,global.tempBuffer); + player = ds_list_find_value(global.players, read_ubyte(global.tempBuffer)); + buildSentry(player, read_ushort(global.tempBuffer)/5, read_ushort(global.tempBuffer)/5, read_byte(global.tempBuffer)); + break; + + case DESTROY_SENTRY: + receiveCompleteMessage(global.serverSocket,4,global.tempBuffer); + playerID = read_ubyte(global.tempBuffer); + otherPlayerID = read_ubyte(global.tempBuffer); + assistantPlayerID = read_ubyte(global.tempBuffer); + causeOfDeath = read_ubyte(global.tempBuffer); + + player = ds_list_find_value(global.players, playerID); + if(otherPlayerID == 255) { + doEventDestruction(player, noone, noone, causeOfDeath); + } else { + otherPlayer = ds_list_find_value(global.players, otherPlayerID); + if (assistantPlayerID == 255) { + doEventDestruction(player, otherPlayer, noone, causeOfDeath); + } else { + assistantPlayer = ds_list_find_value(global.players, assistantPlayerID); + doEventDestruction(player, otherPlayer, assistantPlayer, causeOfDeath); + } + } + break; + + case GRAB_INTEL: + receiveCompleteMessage(global.serverSocket,1,global.tempBuffer); + player = ds_list_find_value(global.players, read_ubyte(global.tempBuffer)); + doEventGrabIntel(player); + break; + + case SCORE_INTEL: + receiveCompleteMessage(global.serverSocket,1,global.tempBuffer); + player = ds_list_find_value(global.players, read_ubyte(global.tempBuffer)); + doEventScoreIntel(player); + break; + + case DROP_INTEL: + receiveCompleteMessage(global.serverSocket,1,global.tempBuffer); + player = ds_list_find_value(global.players, read_ubyte(global.tempBuffer)); + doEventDropIntel(player); + break; + + case RETURN_INTEL: + receiveCompleteMessage(global.serverSocket,1,global.tempBuffer); + doEventReturnIntel(read_ubyte(global.tempBuffer)); + break; + + case GENERATOR_DESTROY: + receiveCompleteMessage(global.serverSocket,1,global.tempBuffer); + team = read_ubyte(global.tempBuffer); + doEventGeneratorDestroy(team); + break; + + case UBER_CHARGED: + receiveCompleteMessage(global.serverSocket,1,global.tempBuffer); + player = ds_list_find_value(global.players, read_ubyte(global.tempBuffer)); + doEventUberReady(player); + break; + + case UBER: + receiveCompleteMessage(global.serverSocket,1,global.tempBuffer); + player = ds_list_find_value(global.players, read_ubyte(global.tempBuffer)); + doEventUber(player); + break; + + case OMNOMNOMNOM: + receiveCompleteMessage(global.serverSocket,1,global.tempBuffer); + player = ds_list_find_value(global.players, read_ubyte(global.tempBuffer)); + if(player.object != -1) { + with(player.object) { + omnomnomnom=true; + if(hp < 200) + { + canEat = false; + alarm[6] = eatCooldown; //10 second cooldown + } + if(player.team == TEAM_RED) { + omnomnomnomindex=0; + omnomnomnomend=31; + } else if(player.team==TEAM_BLUE) { + omnomnomnomindex=32; + omnomnomnomend=63; + } + xscale=image_xscale; + } + } + break; + + case TOGGLE_ZOOM: + receiveCompleteMessage(global.serverSocket,1,global.tempBuffer); + player = ds_list_find_value(global.players, read_ubyte(global.tempBuffer)); + if player.object != -1 { + toggleZoom(player.object); + } + break; + + case PASSWORD_REQUEST: + if(!usePreviousPwd) + global.clientPassword = get_string("Enter Password:", ""); + write_ubyte(global.serverSocket, string_length(global.clientPassword)); + write_string(global.serverSocket, global.clientPassword); + socket_send(global.serverSocket); + break; + + case PASSWORD_WRONG: + show_message("Incorrect Password."); + instance_destroy(); + exit; + + case INCOMPATIBLE_PROTOCOL: + show_message("Incompatible server protocol version."); + instance_destroy(); + exit; + + case KICK: + receiveCompleteMessage(global.serverSocket,1,global.tempBuffer); + reason = read_ubyte(global.tempBuffer); + if reason == KICK_NAME kickReason = "Name Exploit"; + else if reason == KICK_BAD_PLUGIN_PACKET kickReason = "Invalid plugin packet ID"; + else if reason == KICK_MULTI_CLIENT kickReason = "There are too many connections from your IP"; + else kickReason = ""; + show_message("You have been kicked from the server. "+kickReason+"."); + instance_destroy(); + exit; + + case ARENA_STARTROUND: + doEventArenaStartRound(); + break; + + case ARENA_ENDROUND: + with ArenaHUD clientArenaEndRound(); + break; + + case ARENA_RESTART: + doEventArenaRestart(); + break; + + case UNLOCKCP: + doEventUnlockCP(); + break; + + case MAP_END: + global.nextMap=receivestring(global.serverSocket, 1); + receiveCompleteMessage(global.serverSocket,2,global.tempBuffer); + global.winners=read_ubyte(global.tempBuffer); + global.currentMapArea=read_ubyte(global.tempBuffer); + global.mapchanging = true; + if !instance_exists(ScoreTableController) instance_create(0,0,ScoreTableController); + instance_create(0,0,WinBanner); + break; + + case CHANGE_MAP: + roomchange=true; + global.mapchanging = false; + global.currentMap = receivestring(global.serverSocket, 1); + global.currentMapMD5 = receivestring(global.serverSocket, 1); + if(global.currentMapMD5 == "") { // if this is an internal map (signified by the lack of an md5) + if(findInternalMapRoom(global.currentMap)) + room_goto_fix(findInternalMapRoom(global.currentMap)); + else + { + show_message("Error:#Server went to invalid internal map: " + global.currentMap + "#Exiting."); + instance_destroy(); + exit; + } + } else { // it's an external map + if(string_pos("/", global.currentMap) != 0 or string_pos("\", global.currentMap) != 0) + { + show_message("Server sent illegal map name: "+global.currentMap); + instance_destroy(); + exit; + } + if(!file_exists("Maps/" + global.currentMap + ".png") or CustomMapGetMapMD5(global.currentMap) != global.currentMapMD5) + { // Reconnect to the server to download the map + var oldReturnRoom; + oldReturnRoom = returnRoom; + returnRoom = DownloadRoom; + if (global.serverPluginsInUse) + noUnloadPlugins = true; + event_perform(ev_destroy,0); + ClientCreate(); + if (global.serverPluginsInUse) + noReloadPlugins = true; + returnRoom = oldReturnRoom; + usePreviousPwd = true; + exit; + } + room_goto_fix(CustomMapRoom); + } + + for(i=0; i. + + Additional permission under GNU GPL version 3 section 7 + If you modify this Program, or any covered work, by linking or combining it with the Game Maker runtime library, + the 39dll library/extension, Hobbel's Download Manager DLL, or modified versions of these libraries, + the licensors of this Program grant you additional permission to convey the resulting work. +*/ +// Downloading code. + +var downloadHandle, url, tmpfile, window_oldshowborder, window_oldfullscreen; +timeLeft = 0; +counter = 0; +AudioControlPlaySong(-1, false); +window_oldshowborder = window_get_showborder(); +window_oldfullscreen = window_get_fullscreen(); +window_set_fullscreen(false); +window_set_showborder(false); + +if(global.updaterBetaChannel) + url = UPDATE_SOURCE_BETA; +else + url = UPDATE_SOURCE; + +tmpfile = temp_directory + "\gg2update.zip"; + +downloadHandle = httpGet(url, -1); + +while(!httpRequestStatus(downloadHandle)) +{ // while download isn't finished + sleep(floor(1000/30)); // sleep for the equivalent of one frame + io_handle(); // this prevents GameMaker from appearing locked-up + httpRequestStep(downloadHandle); + + // check if the user cancelled the download with the esc key + if(keyboard_check(vk_escape)) + { + httpRequestDestroy(downloadHandle); + window_set_showborder(window_oldshowborder); + window_set_fullscreen(window_oldfullscreen); + room_goto_fix(Menu); + exit; + } + + if(counter == 0 || counter mod 60 == 0) + timer = random(359)+1; + draw_sprite(UpdaterBackgroundS,0,0,0); + draw_set_color(c_white); + draw_set_halign(fa_left); + draw_set_valign(fa_center); + minutes=floor(timer/60); + seconds=floor(timer-minutes*60); + draw_text(x,y-20,string(minutes) + " minutes " + string(seconds) + " seconds Remaining..."); + counter+=1; + var progress, size; + progress = httpRequestResponseBodyProgress(downloadHandle); + size = httpRequestResponseBodySize(downloadHandle); + if (size != -1) + { + progressBar = floor((progress/size) * 20); + offset = 3; + for(i=0;i. + + Additional permission under GNU GPL version 3 section 7 + If you modify this Program, or any covered work, by linking or combining it with the Game Maker runtime library, + the 39dll library/extension, Hobbel's Download Manager DLL, or modified versions of these libraries, + the licensors of this Program grant you additional permission to convey the resulting work. +*/ + +xoffset = view_xview[0]; +yoffset = view_yview[0]; +xsize = view_wview[0]; +ysize = view_hview[0]; + +if (distance_to_point(xoffset+xsize/2,yoffset+ysize/2) > 800) + exit; + +var xr, yr; +xr = round(x); +yr = round(y); + +image_alpha = cloakAlpha; + +if (global.myself.team == team and canCloak) + image_alpha = cloakAlpha/2 + 0.5; + +if (invisible) + exit; + +if(stabbing) + image_alpha -= power(currentWeapon.stab.alpha, 2); + +if team == global.myself.team && (player != global.myself || global.showHealthBar == 1){ + draw_set_alpha(1); + draw_healthbar(xr-10, yr-30, xr+10, yr-25,hp*100/maxHp,c_black,c_red,c_green,0,true,true); +} +if(distance_to_point(mouse_x, mouse_y)<25) { + if cloak && team!=global.myself.team exit; + draw_set_alpha(1); + draw_set_halign(fa_center); + draw_set_valign(fa_bottom); + if(team==TEAM_RED) { + draw_set_color(c_red); + } else { + draw_set_color(c_blue); + } + draw_text(xr, yr-35, player.name); + + if(team == global.myself.team && global.showTeammateStats) + { + if(weapons[0] == Medigun) + draw_text(xr,yr+50, "Superburst: " + string(currentWeapon.uberCharge/20) + "%"); + else if(weapons[0] == Shotgun) + draw_text(xr,yr+50, "Nuts 'N' Bolts: " + string(nutsNBolts)); + else if(weapons[0] == Minegun) + draw_text(xr,yr+50, "Lobbed Mines: " + string(currentWeapon.lobbed)); + } +} + +draw_set_alpha(1); +if team == TEAM_RED ubercolour = c_red; +if team == TEAM_BLUE ubercolour = c_blue; + +var sprite, overlaySprite; +if zoomed +{ + if (team == TEAM_RED) + sprite = SniperCrouchRedS; + else + sprite = SniperCrouchBlueS; + overlaySprite = sniperCrouchOverlay; +} +else +{ + sprite = sprite_index; + overlaySprite = overlay; +} + +if (omnomnomnom) +{ + draw_sprite_ext_overlay(omnomnomnomSprite,omnomnomnomOverlay,omnomnomnomindex,xr,yr,image_xscale,image_yscale,image_angle,c_white,1); + if (ubered) + draw_sprite_ext_overlay(omnomnomnomSprite,omnomnomnomOverlay,omnomnomnomindex,xr,yr,image_xscale,image_yscale,image_angle,ubercolour,0.7); +} +else if (taunting) +{ + draw_sprite_ext_overlay(tauntsprite,tauntOverlay,tauntindex,xr,yr,image_xscale,image_yscale,image_angle,c_white,1); + if (ubered) + draw_sprite_ext_overlay(tauntsprite,tauntOverlay,tauntindex,xr,yr,image_xscale,image_yscale,image_angle,ubercolour,0.7); +} +else if (player.humiliated) + draw_sprite_ext(humiliationPoses,floor(animationImage)+humiliationOffset,xr,yr,image_xscale,image_yscale,image_angle,c_white,image_alpha); +else if (!taunting) +{ + if (cloak) + { + if (!ubered) + draw_sprite_ext(sprite,floor(animationImage+animationOffset),xr,yr,image_xscale,image_yscale,image_angle,c_white,image_alpha); + else if (ubered) + { + draw_sprite_ext(sprite,floor(animationImage+animationOffset),xr,yr,image_xscale,image_yscale,image_angle,c_white,1); + draw_sprite_ext(sprite,floor(animationImage+animationOffset),xr,yr,image_xscale,image_yscale,image_angle,ubercolour,0.7); + } + } + else + { + if (!ubered) + draw_sprite_ext_overlay(sprite,overlaySprite,floor(animationImage+animationOffset),xr,yr,image_xscale,image_yscale,image_angle,c_white,image_alpha); + else if (ubered) + { + draw_sprite_ext_overlay(sprite,overlaySprite,floor(animationImage+animationOffset),xr,yr,image_xscale,image_yscale,image_angle,c_white,1); + draw_sprite_ext_overlay(sprite,overlaySprite,floor(animationImage+animationOffset),xr,yr,image_xscale,image_yscale,image_angle,ubercolour,0.7); + } + } +} +if (burnDuration > 0 or burnIntensity > 0) { + for(i = 0; i < numFlames * burnIntensity / maxIntensity; i += 1) + { + draw_sprite_ext(FlameS, alarm[5] + i + random(2), x + flameArray_x[i], y + flameArray_y[i], 1, 1, 0, c_white, burnDuration / maxDuration * 0.71 + 0.35); + } +} + +// Copied from Lorgan's itemserver "angels" with slight modifications +// All credit be upon him +if (demon != -1) +{ + demonX = median(x-40,demonX,x+40); + demonY = median(y-40,demonY,y); + demonOffset += demonDir; + if (abs(demonOffset) > 15) + demonDir *= -1; + + var dir; + if (demonX > x) + dir = -1; + else + dir = 1; + + if (demonFrame > sprite_get_number(demon)) + demonFrame = 0; + + if (stabbing || ubered) + draw_sprite_ext(demon,demonFrame+floor(animationImage)+7*player.team,demonX,demonY+demonOffset,dir*1,1,0,c_white,1); + else + draw_sprite_ext(demon,demonFrame+floor(animationImage)+7*player.team,demonX,demonY+demonOffset,dir*1,1,0,c_white,image_alpha); + + demonFrame += 1; +} diff --git a/samples/Game Maker Language/characterDrawEvent.gml b/samples/Game Maker Language/characterDrawEvent.gml new file mode 100644 index 00000000..6dcd8fcc --- /dev/null +++ b/samples/Game Maker Language/characterDrawEvent.gml @@ -0,0 +1,80 @@ +// Originally from /spelunky/Scripts/Platform Engine/characterDrawEvent.gml in the Spelunky Community Update Project + +/********************************************************************************** + Copyright (c) 2008, 2009 Derek Yu and Mossmouth, LLC + + This file is part of Spelunky. + + You can redistribute and/or modify Spelunky, including its source code, under + the terms of the Spelunky User License. + + Spelunky is distributed in the hope that it will be entertaining and useful, + but WITHOUT WARRANTY. Please see the Spelunky User License for more details. + + The Spelunky User License should be available in "Game Information", which + can be found in the Resource Explorer, or as an external file called COPYING. + If not, please obtain a new copy of Spelunky from + +***********************************************************************************/ + +/* +This event should be placed in the draw event of the platform character. +*/ +//draws the sprite +draw = true; +if (facing == RIGHT) image_xscale = -1; +else image_xscale = 1; + +if (blinkToggle != 1) +{ + if ((state == CLIMBING or (sprite_index == sPExit or sprite_index == sDamselExit or sprite_index == sTunnelExit)) and global.hasJetpack and not whipping) + { + draw_sprite_ext(sprite_index, -1, x, y, image_xscale, image_yscale, image_angle, image_blend, image_alpha); + //draw_sprite(sprite_index,-1,x,y); + draw_sprite(sJetpackBack,-1,x,y); + draw = false; + } + else if (global.hasJetpack and facing == RIGHT) draw_sprite(sJetpackRight,-1,x-4,y-1); + else if (global.hasJetpack) draw_sprite(sJetpackLeft,-1,x+4,y-1); + if (draw) + { + if (redColor > 0) draw_sprite_ext(sprite_index, -1, x, y, image_xscale, image_yscale, image_angle, make_color_rgb(200 + redColor,0,0), image_alpha); + else draw_sprite_ext(sprite_index, -1, x, y, image_xscale, image_yscale, image_angle, image_blend, image_alpha); + } + if (facing == RIGHT) + { + if (holdArrow == ARROW_NORM) + { + draw_sprite(sArrowRight, -1, x+4, y+1); + } + else if (holdArrow == ARROW_BOMB) + { + if (holdArrowToggle) draw_sprite(sBombArrowRight, 0, x+4, y+2); + else draw_sprite(sBombArrowRight, 1, x+4, y+2); + } + } + else if (facing == LEFT) + { + if (holdArrow == ARROW_NORM) + { + draw_sprite(sArrowLeft, -1, x-4, y+1); + } + else if (holdArrow == ARROW_BOMB) + { + if (holdArrowToggle) draw_sprite(sBombArrowLeft, 0, x-4, y+2); + else draw_sprite(sBombArrowLeft, 1, x-4, y+2); + } + } +} +/* +if canRun +{ + xOffset=80 + if player=1 + yOffset=120 + else + yOffset=143 + //draw the "flySpeed" bar, which shows how much speed the character has acquired while holding the "run" button + //draw_healthbar(view_xview[0]+224+xOffset,view_yview[0]+432+yOffset,view_xview[0]+400+xOffset,view_yview[0]+450+yOffset,flySpeed,make_color_rgb(0,64,128),c_blue,c_aqua,0,1,1) +} +*/ diff --git a/samples/Game Maker Language/characterStepEvent.gml b/samples/Game Maker Language/characterStepEvent.gml new file mode 100644 index 00000000..7416df80 --- /dev/null +++ b/samples/Game Maker Language/characterStepEvent.gml @@ -0,0 +1,1050 @@ +// Originally from /spelunky/Scripts/Platform Engine/characterStepEvent.gml in the Spelunky Community Update Project + +/********************************************************************************** + Copyright (c) 2008, 2009 Derek Yu and Mossmouth, LLC + + This file is part of Spelunky. + + You can redistribute and/or modify Spelunky, including its source code, under + the terms of the Spelunky User License. + + Spelunky is distributed in the hope that it will be entertaining and useful, + but WITHOUT WARRANTY. Please see the Spelunky User License for more details. + + The Spelunky User License should be available in "Game Information", which + can be found in the Resource Explorer, or as an external file called COPYING. + If not, please obtain a new copy of Spelunky from + +***********************************************************************************/ + +/* +This script should be placed in the step event for the platform character. +It updates the keys used by the character, moves all of the solids, moves the +character, sets the sprite index, and sets the animation speed for the sprite. +*/ +hangCountMax = 3; + +////////////////////////////////////// +// KEYS +////////////////////////////////////// + +kLeft = checkLeft(); + +if (kLeft) kLeftPushedSteps += 1; +else kLeftPushedSteps = 0; + +kLeftPressed = checkLeftPressed(); +kLeftReleased = checkLeftReleased(); + +kRight = checkRight(); + +if (kRight) kRightPushedSteps += 1; +else kRightPushedSteps = 0; + +kRightPressed = checkRightPressed(); +kRightReleased = checkRightReleased(); + +kUp = checkUp(); +kDown = checkDown(); + +//key "run" +if canRun + kRun = 0; +// kRun=runKey +else + kRun=0 + +kJump = checkJump(); +kJumpPressed = checkJumpPressed(); +kJumpReleased = checkJumpReleased(); + +if (cantJump > 0) +{ + kJump = 0; + kJumpPressed = 0; + kJumpReleased = 0; + cantJump -= 1; +} +else +{ + if (global.isTunnelMan and + sprite_index == sTunnelAttackL and + !holdItem) + { + kJump = 0; + kJumpPressed = 0; + kJumpReleased = 0; + cantJump -= 1; + } +} + +kAttack = checkAttack(); +kAttackPressed = checkAttackPressed(); +kAttackReleased = checkAttackReleased(); + +kItemPressed = checkItemPressed(); + +xPrev = x; +yPrev = y; + +if (stunned or dead) +{ + kLeft = false; + kLeftPressed = false; + kLeftReleased = false; + kRight = false; + kRightPressed = false; + kRightReleased = false; + kUp = false; + kDown = false; + kJump = false; + kJumpPressed = false; + kJumpReleased = false; + kAttack = false; + kAttackPressed = false; + kAttackReleased = false; + kItemPressed = false; +} + +////////////////////////////////////////// +// Collisions +////////////////////////////////////////// + +colSolidLeft = false; +colSolidRight = false; +colLeft = false; +colRight = false; +colTop = false; +colBot = false; +colLadder = false; +colPlatBot = false; +colPlat = false; +colWaterTop = false; +colIceBot = false; +runKey = false; +if (isCollisionMoveableSolidLeft(1)) colSolidLeft = true; +if (isCollisionMoveableSolidRight(1)) colSolidRight = true; +if (isCollisionLeft(1)) colLeft = true; +if (isCollisionRight(1)) colRight = true; +if (isCollisionTop(1)) colTop = true; +if (isCollisionBottom(1)) colBot = true; +if (isCollisionLadder()) colLadder = true; +if (isCollisionPlatformBottom(1)) colPlatBot = true; +if (isCollisionPlatform()) colPlat = true; +if (isCollisionWaterTop(1)) colWaterTop = true; +if (collision_point(x, y+8, oIce, 0, 0)) colIceBot = true; +if (checkRun()) +{ + runHeld = 100; + runKey = true; +} + +if (checkAttack() and not whipping) +{ + runHeld += 1; + runKey = true; +} + +if (not runKey or (not kLeft and not kRight)) runHeld = 0; + +// allows the character to run left and right +// if state!=DUCKING and state!=LOOKING_UP and state!=CLIMBING +if (state != CLIMBING and state != HANGING) +{ + if (kLeftReleased and approximatelyZero(xVel)) xAcc -= 0.5 + if (kRightReleased and approximatelyZero(xVel)) xAcc += 0.5 + + if (kLeft and not kRight) + { + if (colSolidLeft) + { + // xVel = 3; + if (platformCharacterIs(ON_GROUND) and state != DUCKING) + { + xAcc -= 1; + pushTimer += 10; + //if (not SS_IsSoundPlaying(global.sndPush)) playSound(global.sndPush); + } + } + else if (kLeftPushedSteps > 2) and (facing=LEFT or approximatelyZero(xVel)) + { + xAcc -= runAcc; + } + facing = LEFT; + //if (platformCharacterIs(ON_GROUND) and abs(xVel) > 0 and alarm[3] < 1) alarm[3] = floor(16/-xVel); + } + + if (kRight and not kLeft) + { + if (colSolidRight) + { + // xVel = 3; + if (platformCharacterIs(ON_GROUND) and state != DUCKING) + { + xAcc += 1; + pushTimer += 10; + //if (not SS_IsSoundPlaying(global.sndPush)) playSound(global.sndPush); + } + } + else if (kRightPushedSteps > 2 or colSolidLeft) and (facing=RIGHT or approximatelyZero(xVel)) + { + xAcc += runAcc; + } + facing = RIGHT; + //if (platformCharacterIs(ON_GROUND) and abs(xVel) > 0 and alarm[3] < 1) alarm[3] = floor(16/xVel); + } +} + +/****************************************** + + LADDERS + +*******************************************/ + +if (state == CLIMBING) +{ + if (instance_exists(oCape)) + { + oCape.open = false; + } + kJumped = false; + ladderTimer = 10; + ladder = collision_point(x, y, oLadder, 0, 0); + if (ladder) x = ladder.x + 8; + + if (kLeft) facing = LEFT; + else if (kRight) facing = RIGHT; + if (kUp) + { + if (collision_point(x, y-8, oLadder, 0, 0) or collision_point(x, y-8, oLadderTop, 0, 0)) + { + yAcc -= climbAcc; + if (alarm[2] < 1) alarm[2] = 8; + } + } + else if (kDown) + { + if (collision_point(x, y+8, oLadder, 0, 0) or collision_point(x, y+8, oLadderTop, 0, 0)) + { + yAcc += climbAcc; + if (alarm[2] < 1) alarm[2] = 8; + } + else + state = FALLING; + if (colBot) state = STANDING; + } + + if (kJumpPressed and not whipping) + { + if (kLeft) + xVel = -departLadderXVel; + else if (kRight) + xVel = departLadderXVel; + else + xVel = 0; + yAcc += departLadderYVel; + state = JUMPING; + jumpButtonReleased = 0; + jumpTime = 0; + ladderTimer = 5; + } +} +else +{ + if (ladderTimer > 0) ladderTimer -= 1; +} + +if (platformCharacterIs(IN_AIR) and state != HANGING) +{ + yAcc += gravityIntensity; +} + +// Player has landed +if ((colBot or colPlatBot) and platformCharacterIs(IN_AIR) and yVel >= 0) +{ + if (not colPlat or colBot) + { + yVel = 0; + yAcc = 0; + state = RUNNING; + jumps = 0; + } + //playSound(global.sndLand); +} +if ((colBot or colPlatBot) and not colPlat) yVel = 0; + +// Player has just walked off of the edge of a solid +if (colBot == 0 and (not colPlatBot or colPlat) and platformCharacterIs(ON_GROUND)) +{ + state = FALLING; + yAcc += grav; + kJumped = true; + if (global.hasGloves) hangCount = 5; +} + +if (colTop) +{ + if (dead or stunned) yVel = -yVel * 0.8; + else if (state == JUMPING) yVel = abs(yVel*0.3) +} + +if (colLeft and facing == LEFT) or (colRight and facing == RIGHT) +{ + if (dead or stunned) xVel = -xVel * 0.5; + else xVel = 0; +} + +/****************************************** + + JUMPING + +*******************************************/ + +if (kJumpReleased and platformCharacterIs(IN_AIR)) +{ + kJumped = true; +} +else if (platformCharacterIs(ON_GROUND)) +{ + oCape.open = false; + kJumped = false; +} + +if (kJumpPressed and collision_point(x, y, oWeb, 0, 0)) +{ + obj = instance_place(x, y, oWeb); + obj.life -= 1; + yAcc += initialJumpAcc * 2; + yVel -= 3; + xAcc += xVel/2; + + state = JUMPING; + jumpButtonReleased = 0; + jumpTime = 0; + + grav = gravNorm; +} +else if (kJumpPressed and colWaterTop) +{ + yAcc += initialJumpAcc * 2; + yVel -= 3; + xAcc += xVel/2; + + state = JUMPING; + jumpButtonReleased = 0; + jumpTime = 0; + + grav = gravNorm; +} +else if (global.hasCape and kJumpPressed and kJumped and platformCharacterIs(IN_AIR)) +{ + if (not oCape.open) oCape.open = true; + else oCape.open = false; +} +else if (global.hasJetpack and kJump and kJumped and platformCharacterIs(IN_AIR) and jetpackFuel > 0) +{ + yAcc += initialJumpAcc; + yVel = -1; + jetpackFuel -= 1; + if (alarm[10] < 1) alarm[10] = 3; + + state = JUMPING; + jumpButtonReleased = 0; + jumpTime = 0; + + grav = 0; +} +else if (platformCharacterIs(ON_GROUND) and kJumpPressed and fallTimer == 0) +{ + if (xVel > 3 or xVel < -3) + { + yAcc += initialJumpAcc * 2; + xAcc += xVel * 2; + } + else + { + yAcc += initialJumpAcc * 2; + xAcc += xVel/2; + } + + if (global.hasJordans) + { + yAcc *= 3; + yAccLimit = 12; + grav = 0.5; + } + else if (global.hasSpringShoes) yAcc *= 1.5; + else + { + yAccLimit = 6; + grav = gravNorm; + } + + playSound(global.sndJump); + + pushTimer = 0; + + // the "state" gets changed to JUMPING later on in the code + state = FALLING; + // "variable jumping" states + jumpButtonReleased = 0; + jumpTime = 0; +} + +if (jumpTime < jumpTimeTotal) jumpTime += 1; +//let the character continue to jump +if (kJump == 0) jumpButtonReleased = 1; +if (jumpButtonReleased) jumpTime = jumpTimeTotal; + +gravityIntensity = (jumpTime/jumpTimeTotal) * grav; + +if (kUp and platformCharacterIs(ON_GROUND) and not colLadder) +{ + looking = UP; + if (xVel == 0 and xAcc == 0) state = LOOKING_UP; +} +else looking = 0; + +if (not kUp and state == LOOKING_UP) +{ + state=STANDING +} + +/****************************************** + + HANGING + +*******************************************/ + +if (not colTop) +{ +if (global.hasGloves and yVel > 0) +{ + if (hangCount == 0 and y > 16 and !platformCharacterIs(ON_GROUND) and kRight and colRight and + (collision_point(x+9, y-5, oSolid, 0, 0) or collision_point(x+9, y-6, oSolid, 0, 0))) + { + state = HANGING; + move_snap(1, 8); + yVel = 0; + yAcc = 0; + grav = 0; + } + else if (hangCount == 0 and y > 16 and !platformCharacterIs(ON_GROUND) and kLeft and colLeft and + (collision_point(x-9, y-5, oSolid, 0, 0) or collision_point(x-9, y-6, oSolid, 0, 0))) + { + state = HANGING; + move_snap(1, 8); + yVel = 0; + yAcc = 0; + grav = 0; + } +} +else if (hangCount == 0 and y > 16 and !platformCharacterIs(ON_GROUND) and kRight and colRight and + (collision_point(x+9, y-5, oTree, 0, 0) or collision_point(x+9, y-6, oTree, 0, 0))) +{ + state = HANGING; + move_snap(1, 8); + yVel = 0; + yAcc = 0; + grav = 0; +} +else if (hangCount == 0 and y > 16 and !platformCharacterIs(ON_GROUND) and kLeft and colLeft and + (collision_point(x-9, y-5, oTree, 0, 0) or collision_point(x-9, y-6, oTree, 0, 0))) +{ + state = HANGING; + move_snap(1, 8); + yVel = 0; + yAcc = 0; + grav = 0; +} +else if (hangCount == 0 and y > 16 and !platformCharacterIs(ON_GROUND) and kRight and colRight and + (collision_point(x+9, y-5, oSolid, 0, 0) or collision_point(x+9, y-6, oSolid, 0, 0)) and + not collision_point(x+9, y-9, oSolid, 0, 0) and not collision_point(x, y+9, oSolid, 0, 0)) +{ + state = HANGING; + move_snap(1, 8); + yVel = 0; + yAcc = 0; + grav = 0; +} +else if (hangCount == 0 and y > 16 and !platformCharacterIs(ON_GROUND) and kLeft and colLeft and + (collision_point(x-9, y-5, oSolid, 0, 0) or collision_point(x-9, y-6, oSolid, 0, 0)) and + not collision_point(x-9, y-9, oSolid, 0, 0) and not collision_point(x, y+9, oSolid, 0, 0)) +{ + state = HANGING; + move_snap(1, 8); + yVel = 0; + yAcc = 0; + grav = 0; +} + +if (hangCount == 0 and y > 16 and !platformCharacterIs(ON_GROUND) and state == FALLING and + (collision_point(x, y-5, oArrow, 0, 0) or collision_point(x, y-6, oArrow, 0, 0)) and + not collision_point(x, y-9, oArrow, 0, 0) and not collision_point(x, y+9, oArrow, 0, 0)) +{ + obj = instance_nearest(x, y-5, oArrow); + if (obj.stuck) + { + state = HANGING; + // move_snap(1, 8); + yVel = 0; + yAcc = 0; + grav = 0; + } +} + +/* +if (hangCount == 0 and y > 16 and !platformCharacterIs(ON_GROUND) and state == FALLING and + (collision_point(x, y-5, oTreeBranch, 0, 0) or collision_point(x, y-6, oTreeBranch, 0, 0)) and + not collision_point(x, y-9, oTreeBranch, 0, 0) and not collision_point(x, y+9, oTreeBranch, 0, 0)) +{ + state = HANGING; + // move_snap(1, 8); + yVel = 0; + yAcc = 0; + grav = 0; +} +*/ + +} + +if (hangCount > 0) hangCount -= 1; + +if (state == HANGING) +{ + if (instance_exists(oCape)) oCape.open = false; + kJumped = false; + + if (kDown and kJumpPressed) + { + grav = gravNorm; + state = FALLING; + yAcc -= grav; + hangCount = 5; + if (global.hasGloves) hangCount = 10; + } + else if (kJumpPressed) + { + grav = gravNorm; + if ((facing == RIGHT and kLeft) or (facing == LEFT and kRight)) + { + state = FALLING; + yAcc -= grav; + } + else + { + state = JUMPING; + yAcc += initialJumpAcc * 2; + if (facing == RIGHT) x -= 2; + else x += 2; + } + hangCount = hangCountMax; + } + + if ((facing == LEFT and not isCollisionLeft(2)) or + (facing == RIGHT and not isCollisionRight(2))) + { + grav = gravNorm; + state = FALLING; + yAcc -= grav; + hangCount = 4; + } +} +else +{ + grav = gravNorm; +} + +// pressing down while standing +if (kDown and platformCharacterIs(ON_GROUND) and not whipping) +{ + if (colBot) + { + state = DUCKING; + } + else if colPlatBot + { + // climb down ladder if possible, else jump down + fallTimer = 0; + if (not colBot) + { + ladder = 0; + ladder = instance_place(x, y+16, oLadder); + if (instance_exists(ladder)) + { + if (abs(x-(ladder.x+8)) < 4) + { + x = ladder.x + 8; + + xVel = 0; + yVel = 0; + xAcc = 0; + yAcc = 0; + state = CLIMBING; + } + } + else + { + y += 1; + state = FALLING; + yAcc += grav; + } + } + else + { + //the character can't move down because there is a solid in the way + state = RUNNING; + } + } +} +if (not kDown and state == DUCKING) +{ + state = STANDING; + xVel = 0; + xAcc = 0; +} +if (xVel == 0 and xAcc == 0 and state == RUNNING) +{ + state = STANDING; +} +if (xAcc != 0 and state == STANDING) +{ + state = RUNNING; +} +if (yVel < 0 and platformCharacterIs(IN_AIR) and state != HANGING) +{ + state = JUMPING; +} +if (yVel > 0 and platformCharacterIs(IN_AIR) and state != HANGING) +{ + state = FALLING; + setCollisionBounds(-5, -6, 5, 8); +} +else setCollisionBounds(-5, -8, 5, 8); + +// CLIMB LADDER +colPointLadder = collision_point(x, y, oLadder, 0, 0) or collision_point(x, y, oLadderTop, 0, 0); + +if ((kUp and platformCharacterIs(IN_AIR) and collision_point(x, y-8, oLadder, 0, 0) and ladderTimer == 0) or + (kUp and colPointLadder and ladderTimer == 0) or + (kDown and colPointLadder and ladderTimer == 0 and platformCharacterIs(ON_GROUND) and collision_point(x, y+9, oLadderTop, 0, 0) and xVel == 0)) +{ + ladder = 0; + ladder = instance_place(x, y-8, oLadder); + if (instance_exists(ladder)) + { + if (abs(x-(ladder.x+8)) < 4) + { + x = ladder.x + 8; + if (not collision_point(x, y, oLadder, 0, 0) and + not collision_point(x, y, oLadderTop, 0, 0)) + { + y = ladder.y + 14; + } + + xVel = 0; + yVel = 0; + xAcc = 0; + yAcc = 0; + state = CLIMBING; + } + } +} + +/* +if (sprite_index == sDuckToHangL or sprite_index == sDamselDtHL) +{ + ladder = 0; + if (facing == LEFT and collision_rectangle(x-8, y, x, y+16, oLadder, 0, 0) and not collision_point(x-4, y+16, oSolid, 0, 0)) + { + ladder = instance_nearest(x-4, y+16, oLadder); + } + else if (facing == RIGHT and collision_rectangle(x, y, x+8, y+16, oLadder, 0, 0) and not collision_point(x+4, y+16, oSolid, 0, 0)) + { + ladder = instance_nearest(x+4, y+16, oLadder); + } + + if (ladder) + { + x = ladder.x + 8; + + xVel = 0; + yVel = 0; + xAcc = 0; + yAcc = 0; + state = CLIMBING; + } +} +*/ +/* +if (colLadder and state == CLIMBING and kJumpPressed and not whipping) +{ + if (kLeft) + xVel = -departLadderXVel; + else if (kRight) + xVel = departLadderXVel; + else + xVel = 0; + yAcc += departLadderYVel; + state = JUMPING; + jumpButtonReleased = 0; + jumpTime = 0; + ladderTimer = 5; +} +*/ + +// Calculate horizontal/vertical friction +if (state == CLIMBING) +{ + xFric = frictionClimbingX; + yFric = frictionClimbingY; +} +else +{ + if (runKey and platformCharacterIs(ON_GROUND) and runHeld >= 10) + { + if (kLeft) // run + { + xVel -= 0.1; + xVelLimit = 6; + xFric = frictionRunningFastX; + } + else if (kRight) + { + xVel += 0.1; + xVelLimit = 6; + xFric = frictionRunningFastX; + } + } + else if (state == DUCKING) + { + if (xVel<2 and xVel>-2) + { + xFric = 0.2 + xVelLimit = 3; + image_speed = 0.8; + } + else if (kLeft and global.downToRun) // run + { + xVel -= 0.1; + xVelLimit = 6; + xFric = frictionRunningFastX; + } + else if (kRight and global.downToRun) + { + xVel += 0.1; + xVelLimit = 6; + xFric = frictionRunningFastX; + } + else + { + xVel *= 0.8; + if (xVel < 0.5) xVel = 0; + xFric = 0.2 + xVelLimit = 3; + image_speed = 0.8; + } + } + else + { + //decrease the friction when the character is "flying" + if (platformCharacterIs(IN_AIR)) + { + if (dead or stunned) xFric = 1.0; + else xFric = 0.8; + } + else + { + xFric = frictionRunningX; + } + } + + // Stuck on web or underwater + if (collision_point(x, y, oWeb, 0, 0)) + { + xFric = 0.2; + yFric = 0.2; + fallTimer = 0; + } + else if (collision_point(x, y, oWater, -1, -1)) + { + if (instance_exists(oCape)) oCape.open = false; + + if (state == FALLING and yVel > 0) + { + yFric = 0.5; + } + else if (not collision_point(x, y-9, oWater, -1, -1)) + { + yFric = 1; + } + else + { + yFric = 0.9; + } + } + else + { + swimming = false; + yFric = 1; + } +} + +if (colIceBot and state != DUCKING and not global.hasSpikeShoes) +{ + xFric = 0.98; + yFric = 1; +} + +// RUNNING + +if (platformCharacterIs(ON_GROUND)) +{ + if (state == RUNNING and kLeft and colLeft) + { + pushTimer += 1; + } + else if (state == RUNNING and kRight and colRight) + { + pushTimer += 1; + } + else + { + pushTimer = 0; + } + + if (platformCharacterIs(ON_GROUND) and not kJump and not kDown and not runKey) + { + xVelLimit = 3; + } + + + // ledge flip + if (state == DUCKING and abs(xVel) < 3 and facing == LEFT and + collision_point(x, y+9, oSolid, 0, 0) and not collision_line(x-1, y+9, x-10, y+9, oSolid, 0, 0) and kLeft) + { + state = DUCKTOHANG; + + if (holdItem) + { + holdItem.held = false; + if (holdItem.type == "Gold Idol") holdItem.y -= 8; + scrDropItem(-1, -4); + } + + with oMonkey + { + // knock off monkeys that grabbed you + if (status == 7) + { + xVel = -1; + yVel = -4; + status = 1; + vineCounter = 20; + grabCounter = 60; + } + } + } + else if (state == DUCKING and abs(xVel) < 3 and facing == RIGHT and + collision_point(x, y+9, oSolid, 0, 0) and not collision_line(x+1, y+9, x+10, y+9, oSolid, 0, 0) and kRight) + { + state = DUCKTOHANG; + + if (holdItem) + { + // holdItem.held = false; + if (holdItem.type == "Gold Idol") holdItem.y -= 8; + scrDropItem(1, -4); + } + + with oMonkey + { + // knock off monkeys that grabbed you + if (status == 7) + { + xVel = 1; + yVel = -4; + status = 1; + vineCounter = 20; + grabCounter = 60; + } + } + } +} + +if (state == DUCKTOHANG) +{ + x = xPrev; + y = yPrev; + xVel = 0; + yVel = 0; + xAcc = 0; + yAcc = 0; + grav = 0; +} + +// PARACHUTE AND CAPE +if (instance_exists(oParachute)) +{ + yFric = 0.5; +} +if (instance_exists(oCape)) +{ + if (oCape.open) yFric = 0.5; +} + +if (pushTimer > 100) pushTimer = 100; + +// limits the acceleration if it is too extreme +if (xAcc > xAccLimit) xAcc = xAccLimit; +else if (xAcc < -xAccLimit) xAcc = -xAccLimit; +if (yAcc > yAccLimit) yAcc = yAccLimit; +else if (yAcc < -yAccLimit) yAcc = -yAccLimit; + +// applies the acceleration +xVel += xAcc; +if (dead or stunned) yVel += 0.6; +else yVel += yAcc; + +// nullifies the acceleration +xAcc = 0; +yAcc = 0; + +// applies the friction to the velocity, now that the velocity has been calculated +xVel *= xFric; +yVel *= yFric; + +// apply ball and chain +if (instance_exists(oBall)) +{ + if (distance_to_object(oBall) >= 24) + { + if (xVel > 0 and oBall.x < x and abs(oBall.x-x) > 24) xVel = 0; + if (xVel < 0 and oBall.x > x and abs(oBall.x-x) > 24) xVel = 0; + if (yVel > 0 and oBall.y < y and abs(oBall.y-y) > 24) + { + if (abs(oBall.x-x) < 1) + { + x = oBall.x; + } + else if (oBall.x < x and not kRight) + { + if (xVel > 0) xVel *= -0.25; + else if (xVel == 0) xVel -= 1; + } + else if (oBall.x > x and not kLeft) + { + if (xVel < 0) xVel *= -0.25; + else if (xVel == 0) xVel += 1; + } + yVel = 0; + fallTimer = 0; + } + if (yVel < 0 and oBall.y > y and abs(oBall.y-y) > 24) yVel = 0; + } +} + +// apply the limits since the velocity may be too extreme +if (not dead and not stunned) +{ + if (xVel > xVelLimit) xVel = xVelLimit; + else if (xVel < -xVelLimit) xVel = -xVelLimit; +} +if (yVel > yVelLimit) yVel = yVelLimit; +else if (yVel < -yVelLimit) yVel = -yVelLimit; + +// approximates the "active" variables +if approximatelyZero(xVel) xVel=0 +if approximatelyZero(yVel) yVel=0 +if approximatelyZero(xAcc) xAcc=0 +if approximatelyZero(yAcc) yAcc=0 + +// prepares the character to move up a hill +// we need to use the "slopeYPrev" variable later to know the "true" y previous value +// keep this condition the same +if maxSlope>0 and platformCharacterIs(ON_GROUND) and xVel!=0 +{ + slopeYPrev=y + for(y=y;y>=slopeYPrev-maxSlope;y-=1) + if colTop + break + slopeChangeInY=slopeYPrev-y +} +else + slopeChangeInY=0 + +// moves the character, and balances out the effects caused by other processes +// keep this condition the same +if maxSlope*abs(xVel)>0 and platformCharacterIs(ON_GROUND) +{ + // we need to check if we should dampen out the speed as the character runs on upward slopes + xPrev=x + yPrev=slopeYPrev // we don't want to use y, because y is too high + yPrevHigh=y // we'll use the higher previous variable later + moveTo(xVel,yVel+slopeChangeInY) + dist=point_distance(xPrev,yPrev,x,y)// overall distance that has been traveled + // we should have only ran at xVel + if dist>abs(xVelInteger) + { + // show_message(string(dist)+ " "+string(abs(xVelInteger))) + excess=dist-abs(xVelInteger) + if(xVelInteger<0) + excess*=-1 + // move back since the character moved too far + x=xPrev + y=yPrevHigh // we need the character to be high so the character can move down + // this time we'll move the correct distance, but we need to shorten out the xVel a little + // these lines can be changed for different types of slowing down when running up hills + ratio=abs(xVelInteger)/dist*0.9 //can be changed + moveTo( round(xVelInteger*ratio),round(yVelInteger*ratio+slopeChangeInY) ) + } +} +else +{ + // we simply move xVel and yVel while in the air or on a ladder + moveTo(xVel,yVel) +} +// move the character downhill if possible +// we need to multiply maxDownSlope by the absolute value of xVel since the character normally runs at an xVel larger than 1 +if not colBot and maxDownSlope>0 and xVelInteger!=0 and platformCharacterIs(ON_GROUND) +{ + //the character is floating just above the slope, so move the character down + upYPrev=y + for(y=y;y<=upYPrev+maxDownSlope;y+=1) + if colBot // we hit a solid below + { + upYPrev=y // I know that this doesn't seem to make sense, because of the name of the variable, but it all works out correctly after we break out of this loop + break + } + y=upYPrev +} + +//figures out what the sprite index of the character should be +characterSprite(); + +//sets the previous state and the previously previous state +statePrevPrev = statePrev; +statePrev = state; + +//calculates the image_speed based on the character's velocity +if (state == RUNNING or state == DUCKING or state == LOOKING_UP) +{ + if (state == RUNNING or state == LOOKING_UP) image_speed = abs(xVel) * runAnimSpeed + 0.1; +} + +if (state == CLIMBING) image_speed = sqrt(sqr(abs(xVel))+sqr(abs(yVel))) * climbAnimSpeed +if (xVel >= 4 or xVel <= -4) +{ + image_speed = 1; + if (platformCharacterIs(ON_GROUND)) setCollisionBounds(-8, -8, 8, 8); + else setCollisionBounds(-5, -8, 5, 8); +} +else setCollisionBounds(-5, -8, 5, 8); +if (whipping) image_speed = 1; +if (state == DUCKTOHANG) +{ + image_index = 0; + image_speed = 0.8; +} +//limit the image_speed at 1 so the animation always looks good +if (image_speed > 1) image_speed = 1; diff --git a/samples/Game Maker Language/doEventPlayerDeath.gml b/samples/Game Maker Language/doEventPlayerDeath.gml new file mode 100644 index 00000000..47ee7780 --- /dev/null +++ b/samples/Game Maker Language/doEventPlayerDeath.gml @@ -0,0 +1,251 @@ +/* + Originally from /Source/gg2/Scripts/Events/doEventPlayerDeath.gml in Gang Garrison 2 + + Copyright (C) 2008-2013 Faucet Software + http://www.ganggarrison.com + + 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 3 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, + see . + + Additional permission under GNU GPL version 3 section 7 + If you modify this Program, or any covered work, by linking or combining it with the Game Maker runtime library, + the 39dll library/extension, Hobbel's Download Manager DLL, or modified versions of these libraries, + the licensors of this Program grant you additional permission to convey the resulting work. +*/ + +/** + * Perform the "player death" event, i.e. change the appropriate scores, + * destroy the character object to much splattering and so on. + * + * argument0: The player whose character died + * argument1: The player who inflicted the fatal damage (or noone for unknown) + * argument2: The player who assisted the kill (or noone for no assist) + * argument3: The source of the fatal damage + */ +var victim, killer, assistant, damageSource; +victim = argument0; +killer = argument1; +assistant = argument2; +damageSource = argument3; + +if(!instance_exists(killer)) + killer = noone; + +if(!instance_exists(assistant)) + assistant = noone; + +//************************************* +//* Scoring and Kill log +//************************************* + + +recordKillInLog(victim, killer, assistant, damageSource); + +victim.stats[DEATHS] += 1; +if(killer) +{ + if(damageSource == WEAPON_KNIFE || damageSource == WEAPON_BACKSTAB) + { + killer.stats[STABS] += 1; + killer.roundStats[STABS] += 1; + killer.stats[POINTS] += 1; + killer.roundStats[POINTS] +=1; + } + + if (victim.object.currentWeapon.object_index == Medigun) + { + if (victim.object.currentWeapon.uberReady) + { + killer.stats[BONUS] += 1; + killer.roundStats[BONUS] += 1; + killer.stats[POINTS] += 1; + killer.roundStats[POINTS] += 1; + } + } + + if (killer != victim) + { + killer.stats[KILLS] += 1; + killer.roundStats[KILLS] += 1; + killer.stats[POINTS] += 1; + killer.roundStats[POINTS] += 1; + if(victim.object.intel) + { + killer.stats[DEFENSES] += 1; + killer.roundStats[DEFENSES] += 1; + killer.stats[POINTS] += 1; + killer.roundStats[POINTS] += 1; + recordEventInLog(4, killer.team, killer.name, global.myself == killer); + } + } +} + +if (assistant) +{ + assistant.stats[ASSISTS] += 1; + assistant.roundStats[ASSISTS] += 1; + assistant.stats[POINTS] += .5; + assistant.roundStats[POINTS] += .5; +} + +//SPEC +if (victim == global.myself) + instance_create(victim.object.x, victim.object.y, Spectator); + +//************************************* +//* Gibbing +//************************************* +var xoffset, yoffset, xsize, ysize; + +xoffset = view_xview[0]; +yoffset = view_yview[0]; +xsize = view_wview[0]; +ysize = view_hview[0]; + +randomize(); +with(victim.object) { + if((damageSource == WEAPON_ROCKETLAUNCHER + or damageSource == WEAPON_MINEGUN or damageSource == FRAG_BOX + or damageSource == WEAPON_REFLECTED_STICKY or damageSource == WEAPON_REFLECTED_ROCKET + or damageSource == FINISHED_OFF_GIB or damageSource == GENERATOR_EXPLOSION) + and (player.class != CLASS_QUOTE) and (global.gibLevel>1) + and distance_to_point(xoffset+xsize/2,yoffset+ysize/2) < 900) { + if (hasReward(victim, 'PumpkinGibs')) + { + repeat(global.gibLevel * 2) { + createGib(x,y,PumpkinGib,hspeed,vspeed,random(145)-72, choose(0,1,1,2,2,3), false, true) + } + } + else + { + repeat(global.gibLevel) { + createGib(x,y,Gib,hspeed,vspeed,random(145)-72, 0, false) + } + switch(player.team) + { + case TEAM_BLUE : + repeat(global.gibLevel - 1) { + createGib(x,y,BlueClump,hspeed,vspeed,random(145)-72, 0, false) + } + break; + case TEAM_RED : + repeat(global.gibLevel - 1) { + createGib(x,y,RedClump,hspeed,vspeed,random(145)-72, 0, false) + } + break; + } + } + + repeat(global.gibLevel * 14) { + var blood; + blood = instance_create(x+random(23)-11,y+random(23)-11,BloodDrop); + blood.hspeed=(random(21)-10); + blood.vspeed=(random(21)-13); + if (hasReward(victim, 'PumpkinGibs')) + { + blood.sprite_index = PumpkinJuiceS; + } + } + if (!hasReward(victim, 'PumpkinGibs')) + { + //All Classes gib head, hands, and feet + if(global.gibLevel > 2 || choose(0,1) == 1) + createGib(x,y,Headgib,0,0,random(105)-52, player.class, false); + repeat(global.gibLevel -1){ + //Medic has specially colored hands + if (player.class == CLASS_MEDIC){ + if (player.team == TEAM_RED) + createGib(x,y,Hand, hspeed, vspeed, random(105)-52 , 9, false); + else + createGib(x,y,Hand, hspeed, vspeed, random(105)-52 , 10, false); + }else{ + createGib(x,y,Hand, hspeed, vspeed, random(105)-52 , player.class, false); + } + createGib(x,y,Feet,random(5)-2,random(3),random(13)-6 , player.class, true); + } + } + + //Class specific gibs + switch(player.class) { + case CLASS_PYRO : + if(global.gibLevel > 2 || choose(0,1) == 1) + createGib(x,y,Accesory,hspeed,vspeed,random(105)-52, 4, false) + break; + case CLASS_SOLDIER : + if(global.gibLevel > 2 || choose(0,1) == 1){ + switch(player.team) { + case TEAM_BLUE : + createGib(x,y,Accesory,hspeed,vspeed,random(105)-52, 2, false); + break; + case TEAM_RED : + createGib(x,y,Accesory,hspeed,vspeed,random(105)-52, 1, false); + break; + } + } + break; + case CLASS_ENGINEER : + if(global.gibLevel > 2 || choose(0,1) == 1) + createGib(x,y,Accesory,hspeed,vspeed,random(105)-52, 3, false) + break; + case CLASS_SNIPER : + if(global.gibLevel > 2 || choose(0,1) == 1) + createGib(x,y,Accesory,hspeed,vspeed,random(105)-52, 0, false) + break; + } + playsound(x,y,Gibbing); + } else { + var deadbody; + if player.class != CLASS_QUOTE playsound(x,y,choose(DeathSnd1, DeathSnd2)); + deadbody = instance_create(x,y-30,DeadGuy); + // 'GS' reward - *G*olden *S*tatue + if(hasReward(player, 'GS')) + { + deadbody.sprite_index = haxxyStatue; + deadbody.image_index = 0; + } + else + { + deadbody.sprite_index = sprite_index; + deadbody.image_index = CHARACTER_ANIMATION_DEAD; + } + deadbody.hspeed=hspeed; + deadbody.vspeed=vspeed; + if(hspeed>0) { + deadbody.image_xscale = -1; + } + } +} + +if (global.gg_birthday){ + myHat = instance_create(victim.object.x,victim.object.y,PartyHat); + myHat.image_index = victim.team; +} +if (global.xmas){ + myHat = instance_create(victim.object.x,victim.object.y,XmasHat); + myHat.image_index = victim.team; +} + + +with(victim.object) { + instance_destroy(); +} + +//************************************* +//* Deathcam +//************************************* +if( global.killCam and victim == global.myself and killer and killer != victim and !(damageSource == KILL_BOX || damageSource == FRAG_BOX || damageSource == FINISHED_OFF || damageSource == FINISHED_OFF_GIB || damageSource == GENERATOR_EXPLOSION)) { + instance_create(0,0,DeathCam); + DeathCam.killedby=killer; + DeathCam.name=killer.name; + DeathCam.oldxview=view_xview[0]; + DeathCam.oldyview=view_yview[0]; + DeathCam.lastDamageSource=damageSource; + DeathCam.team = global.myself.team; +} diff --git a/samples/Game Maker Language/faucet-http.gml b/samples/Game Maker Language/faucet-http.gml new file mode 100644 index 00000000..c9b4d0f5 --- /dev/null +++ b/samples/Game Maker Language/faucet-http.gml @@ -0,0 +1,1469 @@ +#define __http_init +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Creates global.__HttpClient +// real __http_init() + +global.__HttpClient = object_add(); +object_set_persistent(global.__HttpClient, true); + +#define __http_split +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// real __http_split(string text, delimeter delimeter, real limit) +// Splits string into items + +// text - string comma-separated values +// delimeter - delimeter to split by +// limit if non-zero, maximum times to split text +// When limited, the remaining text will be left as the last item. +// E.g. splitting "1,2,3,4,5" with delimeter "," and limit 2 yields this list: +// "1", "2", "3,4,5" + +// return value - ds_list containing strings of values + +var text, delimeter, limit; +text = argument0; +delimeter = argument1; +limit = argument2; + +var list, count; +list = ds_list_create(); +count = 0; + +while (string_pos(delimeter, text) != 0) +{ + ds_list_add(list, string_copy(text, 1, string_pos(delimeter,text) - 1)); + text = string_copy(text, string_pos(delimeter, text) + string_length(delimeter), string_length(text) - string_pos(delimeter, text)); + + count += 1; + if (limit and count == limit) + break; +} +ds_list_add(list, text); + +return list; + +#define __http_parse_url +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Parses a URL into its components +// real __http_parse_url(string url) + +// Return value is a ds_map containing keys for the different URL parts: (or -1 on failure) +// "url" - the URL which was passed in +// "scheme" - the URL scheme (e.g. "http") +// "host" - the hostname (e.g. "example.com" or "127.0.0.1") +// "port" - the port (e.g. 8000) - this is a real, unlike the others +// "abs_path" - the absolute path (e.g. "/" or "/index.html") +// "query" - the query string (e.g. "a=b&c=3") +// Parts which are not included will not be in the map +// e.g. http://example.com will not have the "port", "path" or "query" keys + +// This will *only* work properly for URLs of format: +// scheme ":" "//" host [ ":" port ] [ abs_path [ "?" query ]]" +// where [] denotes an optional component +// file: URLs will *not* work as they lack the authority (host:port) component +// It will not work correctly for IPv6 host values + +var url; +url = argument0; + +var map; +map = ds_map_create(); +ds_map_add(map, 'url', url); + +// before scheme +var colonPos; +// Find colon for end of scheme +colonPos = string_pos(':', url); +// No colon - bad URL +if (colonPos == 0) + return -1; +ds_map_add(map, 'scheme', string_copy(url, 1, colonPos - 1)); +url = string_copy(url, colonPos + 1, string_length(url) - colonPos); + +// before double slash +// remove slashes (yes this will screw up file:// but who cares) +while (string_char_at(url, 1) == '/') + url = string_copy(url, 2, string_length(url) - 1); + +// before hostname +var slashPos, colonPos; +// Find slash for beginning of path +slashPos = string_pos('/', url); +// No slash ahead - http://host format with no ending slash +if (slashPos == 0) +{ + // Find : for beginning of port + colonPos = string_pos(':', url); +} +else +{ + // Find : for beginning of port prior to / + colonPos = string_pos(':', string_copy(url, 1, slashPos - 1)); +} +// No colon - no port +if (colonPos == 0) +{ + // There was no slash + if (slashPos == 0) + { + ds_map_add(map, 'host', url); + return map; + } + // There was a slash + else + { + ds_map_add(map, 'host', string_copy(url, 1, slashPos - 1)); + url = string_copy(url, slashPos, string_length(url) - slashPos + 1); + } +} +// There's a colon - port specified +else +{ + // There was no slash + if (slashPos == 0) + { + ds_map_add(map, 'host', string_copy(url, 1, colonPos - 1)); + ds_map_add(map, 'port', real(string_copy(url, colonPos + 1, string_length(url) - colonPos))); + return map; + } + // There was a slash + else + { + ds_map_add(map, 'host', string_copy(url, 1, colonPos - 1)); + url = string_copy(url, colonPos + 1, string_length(url) - colonPos); + slashPos = string_pos('/', url); + ds_map_add(map, 'port', real(string_copy(url, 1, slashPos - 1))); + url = string_copy(url, slashPos, string_length(url) - slashPos + 1); + } +} + +// before path +var queryPos; +queryPos = string_pos('?', url); +// There's no ? - no query +if (queryPos == 0) +{ + ds_map_add(map, 'abs_path', url); + return map; +} +else +{ + ds_map_add(map, 'abs_path', string_copy(url, 1, queryPos - 1)); + ds_map_add(map, 'query', string_copy(url, queryPos + 1, string_length(url) - queryPos)); + return map; +} + +// Return -1 upon unlikely error +ds_map_destroy(map); +return -1; + +#define __http_resolve_url +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Takes a base URL and a URL reference and applies it to the base URL +// Returns resulting absolute URL +// string __http_resolve_url(string baseUrl, string refUrl) + +// Return value is a string containing the new absolute URL, or "" on failure + +// Works only for restricted URL syntax as understood by by http_resolve_url +// The sole restriction of which is that only scheme://authority/path URLs work +// This notably excludes file: URLs which lack the authority component + +// As described by RFC3986: +// URI-reference = URI / relative-ref +// relative-ref = relative-part [ "?" query ] [ "#" fragment ] +// relative-part = "//" authority path-abempty +// / path-absolute +// / path-noscheme +// / path-empty +// However http_resolve_url does *not* deal with fragments + +// Algorithm based on that of section 5.2.2 of RFC 3986 + +var baseUrl, refUrl; +baseUrl = argument0; +refUrl = argument1; + +// Parse base URL +var urlParts; +urlParts = __http_parse_url(baseUrl); +if (urlParts == -1) + return ''; + +// Try to parse reference URL +var refUrlParts, canParseRefUrl; +refUrlParts = __http_parse_url(refUrl); +canParseRefUrl = (refUrlParts != -1); +if (refUrlParts != -1) + ds_map_destroy(refUrlParts); + +var result; +result = ''; + +// Parsing of reference URL succeeded - it's absolute and we're done +if (canParseRefUrl) +{ + result = refUrl; +} +// Begins with '//' - scheme-relative URL +else if (string_copy(refUrl, 1, 2) == '//' and string_length(refUrl) > 2) +{ + result = ds_map_find_value(urlParts, 'scheme') + ':' + refUrl; +} +// Is or begins with '/' - absolute path relative URL +else if (((string_char_at(refUrl, 1) == '/' and string_length(refUrl) > 1) or refUrl == '/') +// Doesn't begin with ':' and is not blank - relative path relative URL + or (string_char_at(refUrl, 1) != ':' and string_length(refUrl) > 0)) +{ + // Find '?' for query + var queryPos; + queryPos = string_pos('?', refUrl); + // No query + if (queryPos == 0) + { + refUrl = __http_resolve_path(ds_map_find_value(urlParts, 'abs_path'), refUrl); + ds_map_replace(urlParts, 'abs_path', refUrl); + if (ds_map_exists(urlParts, 'query')) + ds_map_delete(urlParts, 'query'); + } + // Query exists, split + else + { + var path, query; + path = string_copy(refUrl, 1, queryPos - 1); + query = string_copy(refUrl, queryPos + 1, string_length(relUrl) - queryPos); + path = __http_resolve_path(ds_map_find_value(urlParts, 'abs_path'), path); + ds_map_replace(urlParts, 'abs_path', path); + if (ds_map_exists(urlParts, 'query')) + ds_map_replace(urlParts, 'query', query); + else + ds_map_add(urlParts, 'query', query); + } + result = __http_construct_url(urlParts); +} + +ds_map_destroy(urlParts); +return result; + +#define __http_resolve_path +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Takes a base path and a path reference and applies it to the base path +// Returns resulting absolute path +// string __http_resolve_path(string basePath, string refPath) + +// Return value is a string containing the new absolute path + +// Deals with UNIX-style / paths, not Windows-style \ paths +// Can be used to clean up .. and . in non-absolute paths too ('' as basePath) + +var basePath, refPath; +basePath = argument0; +refPath = argument1; + +// refPath begins with '/' (is absolute), we can ignore all of basePath +if (string_char_at(refPath, 1) == '/') +{ + basePath = refPath; + refPath = ''; +} + +var parts, refParts; +parts = __http_split(basePath, '/', 0); +refParts = __http_split(refPath, '/', 0); + +if (refPath != '') +{ + // Find last part of base path + var lastPart; + lastPart = ds_list_find_value(parts, ds_list_size(parts) - 1); + + // If it's not blank (points to a file), remove it + if (lastPart != '') + { + ds_list_delete(parts, ds_list_size(parts) - 1); + } + + // Concatenate refParts to end of parts + var i; + for (i = 0; i < ds_list_size(refParts); i += 1) + ds_list_add(parts, ds_list_find_value(refParts, i)); +} + +// We now don't need refParts any more +ds_list_destroy(refParts); + +// Deal with '..' and '.' +for (i = 0; i < ds_list_size(parts); i += 1) +{ + var part; + part = ds_list_find_value(parts, i); + + if (part == '.') + { + if (i == 1 or i == ds_list_size(parts) - 1) + ds_list_replace(parts, i, ''); + else + ds_list_delete(parts, i); + i -= 1; + continue; + } + else if (part == '..') + { + if (i > 1) + { + ds_list_delete(parts, i - 1); + ds_list_delete(part, i); + i -= 2; + } + else + { + ds_list_replace(parts, i, ''); + i -= 1; + } + continue; + } + else if (part == '' and i != 0 and i != ds_list_size(parts) - 1) + { + ds_list_delete(parts, i); + i -= 1; + continue; + } +} + +// Reconstruct path from parts +var path; +path = ''; +for (i = 0; i < ds_list_size(parts); i += 1) +{ + if (i != 0) + path += '/'; + path += ds_list_find_value(parts, i); +} + +ds_map_destroy(parts); +return path; + +#define __http_parse_hex +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Takes a lowercase hexadecimal string and returns its integer value +// real __http_parse_hex(string hexString) + +// Return value is the whole number value (or -1 if invalid) +// Only works for whole numbers (non-fractional numbers >= 0) and lowercase hex + +var hexString; +hexString = argument0; + +var result, hexValues; +result = 0; +hexValues = "0123456789abcdef"; + +var i; +for (i = 1; i <= string_length(hexString); i += 1) { + result *= 16; + var digit; + digit = string_pos(string_char_at(hexString, i), hexValues) - 1; + if (digit == -1) + return -1; + result += digit; +} + +return result; + +#define __http_construct_url +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Constructs an URL from its components (as http_parse_url would return) +// string __http_construct_url(real parts) + +// Return value is the string of the constructed URL +// Keys of parts map: +// "scheme" - the URL scheme (e.g. "http") +// "host" - the hostname (e.g. "example.com" or "127.0.0.1") +// "port" - the port (e.g. 8000) - this is a real, unlike the others +// "abs_path" - the absolute path (e.g. "/" or "/index.html") +// "query" - the query string (e.g. "a=b&c=3") +// Parts which are omitted will be omitted in the URL +// e.g. http://example.com lacks "port", "path" or "query" keys + +// This will *only* work properly for URLs of format: +// scheme ":" "//" host [ ":" port ] [ abs_path [ "?" query ]]" +// where [] denotes an optional component +// file: URLs will *not* work as they lack the authority (host:port) component +// Should work correctly for IPv6 host values, but bare in mind parse_url won't + +var parts; +parts = argument0; + +var url; +url = ''; + +url += ds_map_find_value(parts, 'scheme'); +url += '://'; +url += ds_map_find_value(parts, 'host'); +if (ds_map_exists(parts, 'port')) + url += ':' + string(ds_map_find_value(parts, 'port')); +if (ds_map_exists(parts, 'abs_path')) +{ + url += ds_map_find_value(parts, 'abs_path'); + if (ds_map_exists(parts, 'query')) + url += '?' + ds_map_find_value(parts, 'query'); +} + +return url; + +#define __http_prepare_request +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Internal function - prepares request +// void __http_prepare_request(real client, string url, real headers) + +// client - HttpClient object to prepare +// url - URL to send GET request to +// headers - ds_map of extra headers to send, -1 if none + +var client, url, headers; +client = argument0; +url = argument1; +headers = argument2; + +var parsed; +parsed = __http_parse_url(url); + +if (parsed == -1) + show_error("Error when making HTTP GET request - can't parse URL: " + url, true); + +if (!ds_map_exists(parsed, 'port')) + ds_map_add(parsed, 'port', 80); +if (!ds_map_exists(parsed, 'abs_path')) + ds_map_add(parsed, 'abs_path', '/'); + +with (client) +{ + destroyed = false; + CR = chr(13); + LF = chr(10); + CRLF = CR + LF; + socket = tcp_connect(ds_map_find_value(parsed, 'host'), ds_map_find_value(parsed, 'port')); + state = 0; + errored = false; + error = ''; + linebuf = ''; + line = 0; + statusCode = -1; + reasonPhrase = ''; + responseBody = buffer_create(); + responseBodySize = -1; + responseBodyProgress = -1; + responseHeaders = ds_map_create(); + requestUrl = url; + requestHeaders = headers; + + // Request = Request-Line ; Section 5.1 + // *(( general-header ; Section 4.5 + // | request-header ; Section 5.3 + // | entity-header ) CRLF) ; Section 7.1 + // CRLF + // [ message-body ] ; Section 4.3 + + // "The Request-Line begins with a method token, followed by the + // Request-URI and the protocol version, and ending with CRLF. The + // elements are separated by SP characters. No CR or LF is allowed + // except in the final CRLF sequence." + if (ds_map_exists(parsed, 'query')) + write_string(socket, 'GET ' + ds_map_find_value(parsed, 'abs_path') + '?' + ds_map_find_value(parsed, 'query') + ' HTTP/1.1' + CRLF); + else + write_string(socket, 'GET ' + ds_map_find_value(parsed, 'abs_path') + ' HTTP/1.1' + CRLF); + + // "A client MUST include a Host header field in all HTTP/1.1 request + // messages." + // "A "host" without any trailing port information implies the default + // port for the service requested (e.g., "80" for an HTTP URL)." + if (ds_map_find_value(parsed, 'port') == 80) + write_string(socket, 'Host: ' + ds_map_find_value(parsed, 'host') + CRLF); + else + write_string(socket, 'Host: ' + ds_map_find_value(parsed, 'host') + + ':' + string(ds_map_find_value(parsed, 'port')) + CRLF); + + // "An HTTP/1.1 server MAY assume that a HTTP/1.1 client intends to + // maintain a persistent connection unless a Connection header including + // the connection-token "close" was sent in the request." + write_string(socket, 'Connection: close' + CRLF); + + // "If no Accept-Encoding field is present in a request, the server MAY + // assume that the client will accept any content coding." + write_string(socket, 'Accept-Encoding:' + CRLF); + + // If headers specified + if (headers != -1) + { + var key; + // Iterate over headers map + for (key = ds_map_find_first(headers); is_string(key); key = ds_map_find_next(headers, key)) + { + write_string(socket, key + ': ' + ds_map_find_value(headers, key) + CRLF); + } + } + + // Send extra CRLF to terminate request + write_string(socket, CRLF); + + socket_send(socket); + + ds_map_destroy(parsed); +} + +#define __http_parse_header +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Internal function - parses header +// real __http_parse_header(string linebuf, real line) +// Returns false if it errored (caller should return and destroy) + +var linebuf, line; +linebuf = argument0; +line = argument1; + +// "HTTP/1.1 header field values can be folded onto multiple lines if the +// continuation line begins with a space or horizontal tab." +if ((string_char_at(linebuf, 1) == ' ' or ord(string_char_at(linebuf, 1)) == 9)) +{ + if (line == 1) + { + errored = true; + error = "First header line of response can't be a continuation, right?"; + return false; + } + headerValue = ds_map_find_value(responseHeaders, string_lower(headerName)) + + string_copy(linebuf, 2, string_length(linebuf) - 1); +} +// "Each header field consists +// of a name followed by a colon (":") and the field value. Field names +// are case-insensitive. The field value MAY be preceded by any amount +// of LWS, though a single SP is preferred." +else +{ + var colonPos; + colonPos = string_pos(':', linebuf); + if (colonPos == 0) + { + errored = true; + error = "No colon in a header line of response"; + return false; + } + headerName = string_copy(linebuf, 1, colonPos - 1); + headerValue = string_copy(linebuf, colonPos + 1, string_length(linebuf) - colonPos); + // "The field-content does not include any leading or trailing LWS: + // linear white space occurring before the first non-whitespace + // character of the field-value or after the last non-whitespace + // character of the field-value. Such leading or trailing LWS MAY be + // removed without changing the semantics of the field value." + while (string_char_at(headerValue, 1) == ' ' or ord(string_char_at(headerValue, 1)) == 9) + headerValue = string_copy(headerValue, 2, string_length(headerValue) - 1); +} + +ds_map_add(responseHeaders, string_lower(headerName), headerValue); + +if (string_lower(headerName) == 'content-length') +{ + responseBodySize = real(headerValue); + responseBodyProgress = 0; +} + +return true; + +#define __http_client_step +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Steps the HTTP client (needs to be called each step or so) + +var client; +client = argument0; + +with (client) +{ + if (errored) + exit; + + // Socket error + if (socket_has_error(socket)) + { + errored = true; + error = "Socket error: " + socket_error(socket); + return __http_client_destroy(); + } + + var available; + available = tcp_receive_available(socket); + + switch (state) + { + // Receiving lines + case 0: + if (!available && tcp_eof(socket)) + { + errored = true; + error = "Unexpected EOF when receiving headers/status code"; + return __http_client_destroy(); + } + + var bytesRead, c; + for (bytesRead = 1; bytesRead <= available; bytesRead += 1) + { + c = read_string(socket, 1); + // Reached end of line + // "HTTP/1.1 defines the sequence CR LF as the end-of-line marker for all + // protocol elements except the entity-body (see appendix 19.3 for + // tolerant applications)." + if (c == LF and string_char_at(linebuf, string_length(linebuf)) == CR) + { + // Strip trailing CR + linebuf = string_copy(linebuf, 1, string_length(linebuf) - 1); + // First line - status code + if (line == 0) + { + // "The first line of a Response message is the Status-Line, consisting + // of the protocol version followed by a numeric status code and its + // associated textual phrase, with each element separated by SP + // characters. No CR or LF is allowed except in the final CRLF sequence." + var httpVer, spacePos; + spacePos = string_pos(' ', linebuf); + if (spacePos == 0) + { + errored = true; + error = "No space in first line of response"; + return __http_client_destroy(); + } + httpVer = string_copy(linebuf, 1, spacePos); + linebuf = string_copy(linebuf, spacePos + 1, string_length(linebuf) - spacePos); + + spacePos = string_pos(' ', linebuf); + if (spacePos == 0) + { + errored = true; + error = "No second space in first line of response"; + return __http_client_destroy(); + } + statusCode = real(string_copy(linebuf, 1, spacePos)); + reasonPhrase = string_copy(linebuf, spacePos + 1, string_length(linebuf) - spacePos); + } + // Other line + else + { + // Blank line, end of response headers + if (linebuf == '') + { + state = 1; + // write remainder + write_buffer_part(responseBody, socket, available - bytesRead); + responseBodyProgress = available - bytesRead; + break; + } + // Header + else + { + if (!__http_parse_header(linebuf, line)) + return __http_client_destroy(); + } + } + + linebuf = ''; + line += 1; + } + else + linebuf += c; + } + break; + // Receiving response body + case 1: + write_buffer(responseBody, socket); + responseBodyProgress += available; + if (tcp_eof(socket)) + { + if (ds_map_exists(responseHeaders, 'transfer-encoding')) + { + if (ds_map_find_value(responseHeaders, 'transfer-encoding') == 'chunked') + { + // Chunked transfer, let's decode it + var actualResponseBody, actualResponseSize; + actualResponseBody = buffer_create(); + actualResponseBodySize = 0; + + // Parse chunks + // chunk = chunk-size [ chunk-extension ] CRLF + // chunk-data CRLF + // chunk-size = 1*HEX + while (buffer_bytes_left(responseBody)) + { + var chunkSize, c; + chunkSize = ''; + + // Read chunk size byte by byte + while (buffer_bytes_left(responseBody)) + { + c = read_string(responseBody, 1); + if (c == CR or c == ';') + break; + else + chunkSize += c; + } + + // We found a semicolon - beginning of chunk-extension + if (c == ';') + { + // skip all extension stuff + while (buffer_bytes_left(responseBody) && c != CR) + { + c = read_string(responseBody, 1); + } + } + // Reached end of header + if (c == CR) + { + c += read_string(responseBody, 1); + // Doesn't end in CRLF + if (c != CRLF) + { + errored = true; + error = 'header of chunk in chunked transfer did not end in CRLF'; + buffer_destroy(actualResponseBody); + return __http_client_destroy(); + } + // chunk-size is empty - something's up! + if (chunkSize == '') + { + errored = true; + error = 'empty chunk-size in a chunked transfer'; + buffer_destroy(actualResponseBody); + return __http_client_destroy(); + } + chunkSize = __http_parse_hex(chunkSize); + // Parsing of size failed - not hex? + if (chunkSize == -1) + { + errored = true; + error = 'chunk-size was not hexadecimal in a chunked transfer'; + buffer_destroy(actualResponseBody); + return __http_client_destroy(); + } + // Is the chunk bigger than the remaining response? + if (chunkSize + 2 > buffer_bytes_left(responseBody)) + { + errored = true; + error = 'chunk-size was greater than remaining data in a chunked transfer'; + buffer_destroy(actualResponseBody); + return __http_client_destroy(); + } + // OK, everything's good, read the chunk + write_buffer_part(actualResponseBody, responseBody, chunkSize); + actualResponseBodySize += chunkSize; + // Check for CRLF + if (read_string(responseBody, 2) != CRLF) + { + errored = true; + error = 'chunk did not end in CRLF in a chunked transfer'; + return __http_client_destroy(); + } + } + else + { + errored = true; + error = 'did not find CR after reading chunk header in a chunked transfer, Faucet HTTP bug?'; + return __http_client_destroy(); + } + // if the chunk size is zero, then it was the last chunk + if (chunkSize == 0 + // trailer headers will be present + and ds_map_exists(responseHeaders, 'trailer')) + { + // Parse header lines + var line; + line = 1; + while (buffer_bytes_left(responseBody)) + { + var linebuf; + linebuf = ''; + while (buffer_bytes_left(responseBody)) + { + c = read_string(responseBody, 1); + if (c != CR) + linebuf += c; + else + break; + } + c += read_string(responseBody, 1); + if (c != CRLF) + { + errored = true; + error = 'trailer header did not end in CRLF in a chunked transfer'; + return __http_client_destroy(); + } + if (!__http_parse_header(linebuf, line)) + return __http_client_destroy(); + line += 1; + } + } + } + responseBodySize = actualResponseBodySize; + buffer_destroy(responseBody); + responseBody = actualResponseBody; + } + else + { + errored = true; + error = 'Unsupported Transfer-Encoding: "' + ds_map_find_value(responseHaders, 'transfer-encoding') + '"'; + return __http_client_destroy(); + } + } + else if (responseBodySize != -1) + { + if (responseBodyProgress < responseBodySize) + { + errored = true; + error = "Unexpected EOF, response body size is less than expected"; + return __http_client_destroy(); + } + } + // 301 Moved Permanently/302 Found/303 See Other/307 Moved Temporarily + if (statusCode == 301 or statusCode == 302 or statusCode == 303 or statusCode == 307) + { + if (ds_map_exists(responseHeaders, 'location')) + { + var location, resolved; + location = ds_map_find_value(responseHeaders, 'location'); + resolved = __http_resolve_url(requestUrl, location); + // Resolving URL didn't fail and it's http:// + if (resolved != '' and string_copy(resolved, 1, 7) == 'http://') + { + // Restart request + __http_client_destroy(); + __http_prepare_request(client, resolved, requestHeaders); + } + else + { + errored = true; + error = "301, 302, 303 or 307 response with invalid or unsupported Location URL ('" + location + "') - can't redirect"; + return __http_client_destroy(); + } + exit; + } + else + { + errored = true; + error = "301, 302, 303 or 307 response without Location header - can't redirect"; + return __http_client_destroy(); + } + } + else + state = 2; + } + break; + // Done. + case 2: + break; + } +} + +#define __http_client_destroy +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Clears up contents of an httpClient prior to destruction or after error + +if (!destroyed) { + socket_destroy(socket); + buffer_destroy(responseBody); + ds_map_destroy(responseHeaders); +} +destroyed = true; + +#define http_new_get +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Makes a GET HTTP request +// real http_new_get(string url) + +// url - URL to send GET request to + +// Return value is an HttpClient instance that can be passed to +// fct_http_request_status etc. +// (errors on failure to parse URL) + +var url, client; + +url = argument0; + +if (!variable_global_exists('__HttpClient')) + __http_init(); + +client = instance_create(0, 0, global.__HttpClient); +__http_prepare_request(client, url, -1); +return client; + +#define http_new_get_ex +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Makes a GET HTTP request with custom headers +// real http_new_get_ex(string url, real headers) + +// url - URL to send GET request to +// headers - ds_map of extra headers to send + +// Return value is an HttpClient instance that can be passed to +// fct_http_request_status etc. +// (errors on failure to parse URL) + +var url, headers, client; + +url = argument0; +headers = argument1; + +if (!variable_global_exists('__HttpClient')) + __http_init(); + +client = instance_create(0, 0, global.__HttpClient); +__http_prepare_request(client, url, headers); +return client; + +#define http_step +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Steps the HTTP client. This is what makes everything actually happen. +// Call it each step. Returns whether or not the request has finished. +// real http_step(real client) + +// client - HttpClient object + +// Return value is either: +// 0 - In progress +// 1 - Done or Errored + +// Example usage: +// req = http_new_get("http://example.com/x.txt"); +// while (http_step(req)) {} +// if (http_status_code(req) != 200) { +// // Errored! +// } else { +// // Hasn't errored, do stuff here. +// } + +var client; +client = argument0; + +__http_client_step(client); + +if (client.errored || client.state == 2) + return 1; +else + return 0; + +#define http_status_code +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Gets the status code +// real http_status_code(real client) + +// client - HttpClient object + +// Return value is either: +// * 0, if the request has not yet finished +// * a negative value, if there was an internal Faucet HTTP error +// * a positive value, the status code of the HTTP request + +// "The Status-Code element is a 3-digit integer result code of the +// attempt to understand and satisfy the request. These codes are fully +// defined in section 10. The Reason-Phrase is intended to give a short +// textual description of the Status-Code. The Status-Code is intended +// for use by automata and the Reason-Phrase is intended for the human +// user. The client is not required to examine or display the Reason- +// Phrase." + +// See also: http_reason_phrase, gets the Reason-Phrase + +var client; +client = argument0; + +if (client.errored) + return -1; +else if (client.state == 2) + return client.statusCode; +else + return 0; + +#define http_reason_phrase +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Gets the reason phrase +// string http_reason_phrase(real client) + +// client - HttpClient object +// Return value is either: +// * "", if the request has not yet finished +// * an internal Faucet HTTP error message, if there was one +// * the reason phrase of the HTTP request + +// "The Status-Code element is a 3-digit integer result code of the +// attempt to understand and satisfy the request. These codes are fully +// defined in section 10. The Reason-Phrase is intended to give a short +// textual description of the Status-Code. The Status-Code is intended +// for use by automata and the Reason-Phrase is intended for the human +// user. The client is not required to examine or display the Reason- +// Phrase." + +// See also: http_status_code, gets the Status-Code + +var client; +client = argument0; + +if (client.errored) + return client.error; +else if (client.state == 2) + return client.reasonPhrase; +else + return ""; + +#define http_response_body +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Gets the response body returned by an HTTP request as a buffer +// real http_response_body(real client) + +// client - HttpClient object + +// Return value is a buffer if client hasn't errored and is finished + +var client; +client = argument0; + +return client.responseBody; + +#define http_response_body_size +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Gets the size of response body returned by an HTTP request +// real http_response_body_size(real client) + +// client - HttpClient object + +// Return value is the size in bytes, or -1 if we don't know or don't know yet + +// Call this each time you use the size - it may have changed in the case of redirect + +var client; +client = argument0; + +return client.responseBodySize; + +#define http_response_body_progress +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Gets the size of response body returned by an HTTP request which is so far downloaded +// real http_response_body_progress(real client) + +// client - HttpClient object + +// Return value is the size in bytes, or -1 if we haven't started yet or client has errored + +var client; +client = argument0; + +return client.responseBodyProgress; + +#define http_response_headers +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Gets the response headers returned by an HTTP request as a ds_map +// real http_response_headers(real client) + +// client - HttpClient object + +// Return value is a ds_map if client hasn't errored and is finished + +// All headers will have lowercase keys +// The ds_map is owned by the HttpClient, do not use ds_map_destroy() yourself +// Call when the request has finished - otherwise may be incomplete or missing + +var client; +client = argument0; + +return client.responseHeaders; + +#define http_destroy +// *** +// This function forms part of Faucet HTTP v1.0 +// https://github.com/TazeTSchnitzel/Faucet-HTTP-Extension +// +// Copyright (c) 2013-2014, Andrea Faulds +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// *** + +// Cleans up HttpClient +// void http_destroy(real client) + +// client - HttpClient object + +var client; +client = argument0; + +with (client) +{ + __http_client_destroy(); + instance_destroy(); +} + diff --git a/samples/Game Maker Language/game_init.gml b/samples/Game Maker Language/game_init.gml new file mode 100644 index 00000000..4f5012d2 --- /dev/null +++ b/samples/Game Maker Language/game_init.gml @@ -0,0 +1,484 @@ +/* + Originally from /Source/gg2/Scripts/game_init.gml in Gang Garrison 2 + + Copyright (C) 2008-2013 Faucet Software + http://www.ganggarrison.com + + 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 3 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, + see . + + Additional permission under GNU GPL version 3 section 7 + If you modify this Program, or any covered work, by linking or combining it with the Game Maker runtime library, + the 39dll library/extension, Hobbel's Download Manager DLL, or modified versions of these libraries, + the licensors of this Program grant you additional permission to convey the resulting work. +*/ + +// Returns true if the game is successfully initialized, false if there was an error and we should quit. +{ + instance_create(0,0,RoomChangeObserver); + set_little_endian_global(true); + if file_exists("game_errors.log") file_delete("game_errors.log"); + if file_exists("last_plugin.log") file_delete("last_plugin.log"); + + // Delete old left-over files created by the updater + var backupFilename; + backupFilename = file_find_first("gg2-old.delete.me.*", 0); + while(backupFilename != "") + { + file_delete(backupFilename); + backupFilename = file_find_next(); + } + file_find_close(); + + var customMapRotationFile, restart; + restart = false; + + //import wav files for music + global.MenuMusic=sound_add(choose("Music/menumusic1.wav","Music/menumusic2.wav","Music/menumusic3.wav","Music/menumusic4.wav","Music/menumusic5.wav","Music/menumusic6.wav"), 1, true); + global.IngameMusic=sound_add("Music/ingamemusic.wav", 1, true); + global.FaucetMusic=sound_add("Music/faucetmusic.wav", 1, true); + if(global.MenuMusic != -1) + sound_volume(global.MenuMusic, 0.8); + if(global.IngameMusic != -1) + sound_volume(global.IngameMusic, 0.8); + if(global.FaucetMusic != -1) + sound_volume(global.FaucetMusic, 0.8); + + global.sendBuffer = buffer_create(); + global.tempBuffer = buffer_create(); + global.HudCheck = false; + global.map_rotation = ds_list_create(); + + global.CustomMapCollisionSprite = -1; + + window_set_region_scale(-1, false); + + ini_open("gg2.ini"); + global.playerName = ini_read_string("Settings", "PlayerName", "Player"); + if string_count("#",global.playerName) > 0 global.playerName = "Player"; + global.playerName = string_copy(global.playerName, 0, min(string_length(global.playerName), MAX_PLAYERNAME_LENGTH)); + global.fullscreen = ini_read_real("Settings", "Fullscreen", 0); + global.useLobbyServer = ini_read_real("Settings", "UseLobby", 1); + global.hostingPort = ini_read_real("Settings", "HostingPort", 8190); + global.music = ini_read_real("Settings", "Music", ini_read_real("Settings", "IngameMusic", MUSIC_BOTH)); + global.playerLimit = ini_read_real("Settings", "PlayerLimit", 10); + //thy playerlimit shalt not exceed 48! + if (global.playerLimit > 48) + { + if (global.dedicatedMode != 1) + show_message("Warning: Player Limit cannot exceed 48. It has been set to 48"); + global.playerLimit = 48; + ini_write_real("Settings", "PlayerLimit", 48); + } + global.multiClientLimit = ini_read_real("Settings", "MultiClientLimit", 3); + global.particles = ini_read_real("Settings", "Particles", PARTICLES_NORMAL); + global.gibLevel = ini_read_real("Settings", "Gib Level", 3); + global.killCam = ini_read_real("Settings", "Kill Cam", 1); + global.monitorSync = ini_read_real("Settings", "Monitor Sync", 0); + if global.monitorSync == 1 set_synchronization(true); + else set_synchronization(false); + global.medicRadar = ini_read_real("Settings", "Healer Radar", 1); + global.showHealer = ini_read_real("Settings", "Show Healer", 1); + global.showHealing = ini_read_real("Settings", "Show Healing", 1); + global.showHealthBar = ini_read_real("Settings", "Show Healthbar", 0); + global.showTeammateStats = ini_read_real("Settings", "Show Extra Teammate Stats", 0); + global.serverPluginsPrompt = ini_read_real("Settings", "ServerPluginsPrompt", 1); + global.restartPrompt = ini_read_real("Settings", "RestartPrompt", 1); + //user HUD settings + global.timerPos=ini_read_real("Settings","Timer Position", 0) + global.killLogPos=ini_read_real("Settings","Kill Log Position", 0) + global.kothHudPos=ini_read_real("Settings","KoTH HUD Position", 0) + global.clientPassword = ""; + // for admin menu + customMapRotationFile = ini_read_string("Server", "MapRotation", ""); + global.shuffleRotation = ini_read_real("Server", "ShuffleRotation", 1); + global.timeLimitMins = max(1, min(255, ini_read_real("Server", "Time Limit", 15))); + global.serverPassword = ini_read_string("Server", "Password", ""); + global.mapRotationFile = customMapRotationFile; + global.dedicatedMode = ini_read_real("Server", "Dedicated", 0); + global.serverName = ini_read_string("Server", "ServerName", "My Server"); + global.welcomeMessage = ini_read_string("Server", "WelcomeMessage", ""); + global.caplimit = max(1, min(255, ini_read_real("Server", "CapLimit", 5))); + global.caplimitBkup = global.caplimit; + global.autobalance = ini_read_real("Server", "AutoBalance",1); + global.Server_RespawntimeSec = ini_read_real("Server", "Respawn Time", 5); + global.rewardKey = unhex(ini_read_string("Haxxy", "RewardKey", "")); + global.rewardId = ini_read_string("Haxxy", "RewardId", ""); + global.mapdownloadLimitBps = ini_read_real("Server", "Total bandwidth limit for map downloads in bytes per second", 50000); + global.updaterBetaChannel = ini_read_real("General", "UpdaterBetaChannel", isBetaVersion()); + global.attemptPortForward = ini_read_real("Server", "Attempt UPnP Forwarding", 0); + global.serverPluginList = ini_read_string("Server", "ServerPluginList", ""); + global.serverPluginsRequired = ini_read_real("Server", "ServerPluginsRequired", 0); + if (string_length(global.serverPluginList) > 254) { + show_message("Error: Server plugin list cannot exceed 254 characters"); + return false; + } + var CrosshairFilename, CrosshairRemoveBG; + CrosshairFilename = ini_read_string("Settings", "CrosshairFilename", ""); + CrosshairRemoveBG = ini_read_real("Settings", "CrosshairRemoveBG", 1); + global.queueJumping = ini_read_real("Settings", "Queued Jumping", 0); + + global.backgroundHash = ini_read_string("Background", "BackgroundHash", "default"); + global.backgroundTitle = ini_read_string("Background", "BackgroundTitle", ""); + global.backgroundURL = ini_read_string("Background", "BackgroundURL", ""); + global.backgroundShowVersion = ini_read_real("Background", "BackgroundShowVersion", true); + + readClasslimitsFromIni(); + + global.currentMapArea=1; + global.totalMapAreas=1; + global.setupTimer=1800; + global.joinedServerName=""; + global.serverPluginsInUse=false; + // Create plugin packet maps + global.pluginPacketBuffers = ds_map_create(); + global.pluginPacketPlayers = ds_map_create(); + + ini_write_string("Settings", "PlayerName", global.playerName); + ini_write_real("Settings", "Fullscreen", global.fullscreen); + ini_write_real("Settings", "UseLobby", global.useLobbyServer); + ini_write_real("Settings", "HostingPort", global.hostingPort); + ini_key_delete("Settings", "IngameMusic"); + ini_write_real("Settings", "Music", global.music); + ini_write_real("Settings", "PlayerLimit", global.playerLimit); + ini_write_real("Settings", "MultiClientLimit", global.multiClientLimit); + ini_write_real("Settings", "Particles", global.particles); + ini_write_real("Settings", "Gib Level", global.gibLevel); + ini_write_real("Settings", "Kill Cam", global.killCam); + ini_write_real("Settings", "Monitor Sync", global.monitorSync); + ini_write_real("Settings", "Healer Radar", global.medicRadar); + ini_write_real("Settings", "Show Healer", global.showHealer); + ini_write_real("Settings", "Show Healing", global.showHealing); + ini_write_real("Settings", "Show Healthbar", global.showHealthBar); + ini_write_real("Settings", "Show Extra Teammate Stats", global.showTeammateStats); + ini_write_real("Settings", "Timer Position", global.timerPos); + ini_write_real("Settings", "Kill Log Position", global.killLogPos); + ini_write_real("Settings", "KoTH HUD Position", global.kothHudPos); + ini_write_real("Settings", "ServerPluginsPrompt", global.serverPluginsPrompt); + ini_write_real("Settings", "RestartPrompt", global.restartPrompt); + ini_write_string("Server", "MapRotation", customMapRotationFile); + ini_write_real("Server", "ShuffleRotation", global.shuffleRotation); + ini_write_real("Server", "Dedicated", global.dedicatedMode); + ini_write_string("Server", "ServerName", global.serverName); + ini_write_string("Server", "WelcomeMessage", global.welcomeMessage); + ini_write_real("Server", "CapLimit", global.caplimit); + ini_write_real("Server", "AutoBalance", global.autobalance); + ini_write_real("Server", "Respawn Time", global.Server_RespawntimeSec); + ini_write_real("Server", "Total bandwidth limit for map downloads in bytes per second", global.mapdownloadLimitBps); + ini_write_real("Server", "Time Limit", global.timeLimitMins); + ini_write_string("Server", "Password", global.serverPassword); + ini_write_real("General", "UpdaterBetaChannel", global.updaterBetaChannel); + ini_write_real("Server", "Attempt UPnP Forwarding", global.attemptPortForward); + ini_write_string("Server", "ServerPluginList", global.serverPluginList); + ini_write_real("Server", "ServerPluginsRequired", global.serverPluginsRequired); + ini_write_string("Settings", "CrosshairFilename", CrosshairFilename); + ini_write_real("Settings", "CrosshairRemoveBG", CrosshairRemoveBG); + ini_write_real("Settings", "Queued Jumping", global.queueJumping); + + ini_write_string("Background", "BackgroundHash", global.backgroundHash); + ini_write_string("Background", "BackgroundTitle", global.backgroundTitle); + ini_write_string("Background", "BackgroundURL", global.backgroundURL); + ini_write_real("Background", "BackgroundShowVersion", global.backgroundShowVersion); + + ini_write_real("Classlimits", "Scout", global.classlimits[CLASS_SCOUT]) + ini_write_real("Classlimits", "Pyro", global.classlimits[CLASS_PYRO]) + ini_write_real("Classlimits", "Soldier", global.classlimits[CLASS_SOLDIER]) + ini_write_real("Classlimits", "Heavy", global.classlimits[CLASS_HEAVY]) + ini_write_real("Classlimits", "Demoman", global.classlimits[CLASS_DEMOMAN]) + ini_write_real("Classlimits", "Medic", global.classlimits[CLASS_MEDIC]) + ini_write_real("Classlimits", "Engineer", global.classlimits[CLASS_ENGINEER]) + ini_write_real("Classlimits", "Spy", global.classlimits[CLASS_SPY]) + ini_write_real("Classlimits", "Sniper", global.classlimits[CLASS_SNIPER]) + ini_write_real("Classlimits", "Quote", global.classlimits[CLASS_QUOTE]) + + //screw the 0 index we will start with 1 + //map_truefort + maps[1] = ini_read_real("Maps", "ctf_truefort", 1); + //map_2dfort + maps[2] = ini_read_real("Maps", "ctf_2dfort", 2); + //map_conflict + maps[3] = ini_read_real("Maps", "ctf_conflict", 3); + //map_classicwell + maps[4] = ini_read_real("Maps", "ctf_classicwell", 4); + //map_waterway + maps[5] = ini_read_real("Maps", "ctf_waterway", 5); + //map_orange + maps[6] = ini_read_real("Maps", "ctf_orange", 6); + //map_dirtbowl + maps[7] = ini_read_real("Maps", "cp_dirtbowl", 7); + //map_egypt + maps[8] = ini_read_real("Maps", "cp_egypt", 8); + //arena_montane + maps[9] = ini_read_real("Maps", "arena_montane", 9); + //arena_lumberyard + maps[10] = ini_read_real("Maps", "arena_lumberyard", 10); + //gen_destroy + maps[11] = ini_read_real("Maps", "gen_destroy", 11); + //koth_valley + maps[12] = ini_read_real("Maps", "koth_valley", 12); + //koth_corinth + maps[13] = ini_read_real("Maps", "koth_corinth", 13); + //koth_harvest + maps[14] = ini_read_real("Maps", "koth_harvest", 14); + //dkoth_atalia + maps[15] = ini_read_real("Maps", "dkoth_atalia", 15); + //dkoth_sixties + maps[16] = ini_read_real("Maps", "dkoth_sixties", 16); + + //Server respawn time calculator. Converts each second to a frame. (read: multiply by 30 :hehe:) + if (global.Server_RespawntimeSec == 0) + { + global.Server_Respawntime = 1; + } + else + { + global.Server_Respawntime = global.Server_RespawntimeSec * 30; + } + + // I have to include this, or the client'll complain about an unknown variable. + global.mapchanging = false; + + ini_write_real("Maps", "ctf_truefort", maps[1]); + ini_write_real("Maps", "ctf_2dfort", maps[2]); + ini_write_real("Maps", "ctf_conflict", maps[3]); + ini_write_real("Maps", "ctf_classicwell", maps[4]); + ini_write_real("Maps", "ctf_waterway", maps[5]); + ini_write_real("Maps", "ctf_orange", maps[6]); + ini_write_real("Maps", "cp_dirtbowl", maps[7]); + ini_write_real("Maps", "cp_egypt", maps[8]); + ini_write_real("Maps", "arena_montane", maps[9]); + ini_write_real("Maps", "arena_lumberyard", maps[10]); + ini_write_real("Maps", "gen_destroy", maps[11]); + ini_write_real("Maps", "koth_valley", maps[12]); + ini_write_real("Maps", "koth_corinth", maps[13]); + ini_write_real("Maps", "koth_harvest", maps[14]); + ini_write_real("Maps", "dkoth_atalia", maps[15]); + ini_write_real("Maps", "dkoth_sixties", maps[16]); + + ini_close(); + + // parse the protocol version UUID for later use + global.protocolUuid = buffer_create(); + parseUuid(PROTOCOL_UUID, global.protocolUuid); + + global.gg2lobbyId = buffer_create(); + parseUuid(GG2_LOBBY_UUID, global.gg2lobbyId); + + // Create abbreviations array for rewards use + initRewards() + +var a, IPRaw, portRaw; +doubleCheck=0; +global.launchMap = ""; + + for(a = 1; a <= parameter_count(); a += 1) + { + if (parameter_string(a) == "-dedicated") + { + global.dedicatedMode = 1; + } + else if (parameter_string(a) == "-restart") + { + restart = true; + } + else if (parameter_string(a) == "-server") + { + IPRaw = parameter_string(a+1); + if (doubleCheck == 1) + { + doubleCheck = 2; + } + else + { + doubleCheck = 1; + } + } + else if (parameter_string(a) == "-port") + { + portRaw = parameter_string(a+1); + if (doubleCheck == 1) + { + doubleCheck = 2; + } + else + { + doubleCheck = 1; + } + } + else if (parameter_string(a) == "-map") + { + global.launchMap = parameter_string(a+1); + global.dedicatedMode = 1; + } + } + + if (doubleCheck == 2) + { + global.serverPort = real(portRaw); + global.serverIP = IPRaw; + global.isHost = false; + instance_create(0,0,Client); + } + + global.customMapdesginated = 0; + + // if the user defined a valid map rotation file, then load from there + + if(customMapRotationFile != "" && file_exists(customMapRotationFile) && global.launchMap == "") { + global.customMapdesginated = 1; + var fileHandle, i, mapname; + fileHandle = file_text_open_read(customMapRotationFile); + for(i = 1; !file_text_eof(fileHandle); i += 1) { + mapname = file_text_read_string(fileHandle); + // remove leading whitespace from the string + while(string_char_at(mapname, 0) == " " || string_char_at(mapname, 0) == chr(9)) { // while it starts with a space or tab + mapname = string_delete(mapname, 0, 1); // delete that space or tab + } + if(mapname != "" && string_char_at(mapname, 0) != "#") { // if it's not blank and it's not a comment (starting with #) + ds_list_add(global.map_rotation, mapname); + } + file_text_readln(fileHandle); + } + file_text_close(fileHandle); + } + + else if (global.launchMap != "") && (global.dedicatedMode == 1) + { + ds_list_add(global.map_rotation, global.launchMap); + } + + else { // else load from the ini file Maps section + //Set up the map rotation stuff + var i, sort_list; + sort_list = ds_list_create(); + for(i=1; i <= 16; i += 1) { + if(maps[i] != 0) ds_list_add(sort_list, ((100*maps[i])+i)); + } + ds_list_sort(sort_list, 1); + + // translate the numbers back into the names they represent + for(i=0; i < ds_list_size(sort_list); i += 1) { + switch(ds_list_find_value(sort_list, i) mod 100) { + case 1: + ds_list_add(global.map_rotation, "ctf_truefort"); + break; + case 2: + ds_list_add(global.map_rotation, "ctf_2dfort"); + break; + case 3: + ds_list_add(global.map_rotation, "ctf_conflict"); + break; + case 4: + ds_list_add(global.map_rotation, "ctf_classicwell"); + break; + case 5: + ds_list_add(global.map_rotation, "ctf_waterway"); + break; + case 6: + ds_list_add(global.map_rotation, "ctf_orange"); + break; + case 7: + ds_list_add(global.map_rotation, "cp_dirtbowl"); + break; + case 8: + ds_list_add(global.map_rotation, "cp_egypt"); + break; + case 9: + ds_list_add(global.map_rotation, "arena_montane"); + break; + case 10: + ds_list_add(global.map_rotation, "arena_lumberyard"); + break; + case 11: + ds_list_add(global.map_rotation, "gen_destroy"); + break; + case 12: + ds_list_add(global.map_rotation, "koth_valley"); + break; + case 13: + ds_list_add(global.map_rotation, "koth_corinth"); + break; + case 14: + ds_list_add(global.map_rotation, "koth_harvest"); + break; + case 15: + ds_list_add(global.map_rotation, "dkoth_atalia"); + break; + case 16: + ds_list_add(global.map_rotation, "dkoth_sixties"); + break; + + } + } + ds_list_destroy(sort_list); + } + + window_set_fullscreen(global.fullscreen); + + global.gg2Font = font_add_sprite(gg2FontS,ord("!"),false,0); + global.countFont = font_add_sprite(countFontS, ord("0"),false,2); + draw_set_font(global.gg2Font); + cursor_sprite = CrosshairS; + + if(!directory_exists(working_directory + "\Maps")) directory_create(working_directory + "\Maps"); + + instance_create(0, 0, AudioControl); + instance_create(0, 0, SSControl); + + // custom dialog box graphics + message_background(popupBackgroundB); + message_button(popupButtonS); + message_text_font("Century",9,c_white,1); + message_button_font("Century",9,c_white,1); + message_input_font("Century",9,c_white,0); + + //Key Mapping + ini_open("controls.gg2"); + global.jump = ini_read_real("Controls", "jump", ord("W")); + global.down = ini_read_real("Controls", "down", ord("S")); + global.left = ini_read_real("Controls", "left", ord("A")); + global.right = ini_read_real("Controls", "right", ord("D")); + global.attack = ini_read_real("Controls", "attack", MOUSE_LEFT); + global.special = ini_read_real("Controls", "special", MOUSE_RIGHT); + global.taunt = ini_read_real("Controls", "taunt", ord("F")); + global.chat1 = ini_read_real("Controls", "chat1", ord("Z")); + global.chat2 = ini_read_real("Controls", "chat2", ord("X")); + global.chat3 = ini_read_real("Controls", "chat3", ord("C")); + global.medic = ini_read_real("Controls", "medic", ord("E")); + global.drop = ini_read_real("Controls", "drop", ord("B")); + global.changeTeam = ini_read_real("Controls", "changeTeam", ord("N")); + global.changeClass = ini_read_real("Controls", "changeClass", ord("M")); + global.showScores = ini_read_real("Controls", "showScores", vk_shift); + ini_close(); + + calculateMonthAndDay(); + + if(!directory_exists(working_directory + "\Plugins")) directory_create(working_directory + "\Plugins"); + loadplugins(); + + /* Windows 8 is known to crash GM when more than three (?) sounds play at once + * We'll store the kernel version (Win8 is 6.2, Win7 is 6.1) and check it there. + ***/ + registry_set_root(1); // HKLM + global.NTKernelVersion = real(registry_read_string_ext("\SOFTWARE\Microsoft\Windows NT\CurrentVersion\", "CurrentVersion")); // SIC + + if (file_exists(CrosshairFilename)) + { + sprite_replace(CrosshairS,CrosshairFilename,1,CrosshairRemoveBG,false,0,0); + sprite_set_offset(CrosshairS,sprite_get_width(CrosshairS)/2,sprite_get_height(CrosshairS)/2); + } + if(global.dedicatedMode == 1) { + AudioControlToggleMute(); + room_goto_fix(Menu); + } else if(restart) { + room_goto_fix(Menu); + } + return true; +} diff --git a/samples/Game Maker Language/jsonion.gml b/samples/Game Maker Language/jsonion.gml new file mode 100644 index 00000000..e4d2a6cb --- /dev/null +++ b/samples/Game Maker Language/jsonion.gml @@ -0,0 +1,1861 @@ +// Originally from /jsonion.gml in JSOnion +// JSOnion v1.0.0d is licensed under the MIT licence. You may freely adapt and use this library in commercial and non-commercial projects. + +#define __jso_gmt_tuple +{ + + /** + tuple(>element_0<, >element_1<, ..., >element_n<): Return an n-tuple + @author: GameGeisha + @version: 1.2 (GMTuple) + */ + + //Position, address table and data + var pos, addr_table, data; + pos = 6*argument_count+4; + addr_table = ""; + data = ""; + + //Build the tuple element-by-element + var i, ca, isstr, datastr; + for (i=0; ith element of + @author: GameGeisha + @version: 1.2 (GMTuple) + */ + + //Capture arguments + var t, n, size; + t = argument0; + n = argument1; + size = __jso_gmt_size(t); + + //Search for the bounding positions for the th element in the address table + var start, afterend, isstr; + isstr = ord(string_char_at(t, 4+6*n))-$30; + start = real(string_copy(t, 5+6*n, 5)); + if (n < size-1) { + afterend = real(string_copy(t, 11+6*n, 5)); + } else { + afterend = string_length(t)+1; + } + + //Return the th element with the correct type + if (isstr) { + return string_copy(t, start, afterend-start); + } + else { + return real(string_copy(t, start, afterend-start)); + } + +} + +#define __jso_gmt_size +{ + + /** + size(tuple_source): Return the size of + @author: GameGeisha + @version: 1.2 (GMTuple) + */ + + return real(string_copy(argument0, 1, 3)); + +} + +#define __jso_gmt_numtostr +{ + + /** + __gmt_numtostr(num): Return string representation of . Decimal numbers expressed as scientific notation + with double precision (i.e. 15 digits) + @author: GameGeisha + @version: 1.2 (edited) + */ + if (frac(argument0) == 0) { + return string(argument0); + } + + var mantissa, exponent; + exponent = floor(log10(abs(argument0))); + mantissa = string_format(argument0/power(10,exponent), 15, 14); + var i, ca; + i = string_length(mantissa); + do { + ca = string_char_at(mantissa, i); + i -= 1; + } until (ca != "0") + if (ca != ".") { + mantissa = string_copy(mantissa, 1, i+1); + } + else { + mantissa = string_copy(mantissa, 1, i); + } + if (exponent != 0) { + return mantissa + "e" + string(exponent); + } + else { + return mantissa; + } + +} + +#define __jso_gmt_test_all +{ + + /** + test_all(): Runs all test suites + @author: GameGeisha + @version: 1.2 (GMTuple) + */ + + //Automated testing sequence + __jso_gmt_test_elem(); + __jso_gmt_test_size(); + __jso_gmt_test_numtostr(); + +} + +#define __jso_gmt_test_numtostr +{ + + /** + _test_numtostr(): Runs number-to-string tests + @author: GameGeisha + @version: 1.2 (GMTuple) + */ + + var tolerance; + tolerance = 1/10000000000; + + if (real(__jso_gmt_numtostr(9)) != 9) { + show_message("Scientific notation conversion failed for 1-digit integer! Result: " + __jso_gmt_numtostr(9)); + } + if (real(__jso_gmt_numtostr(500)) != 500) { + show_message("Scientific notation conversion failed for 3-digit integer! Result: " + __jso_gmt_numtostr(500)); + } + if (abs(real(__jso_gmt_numtostr(pi))-pi) > tolerance) { + show_message("Scientific notation conversion failed for pi! Result: " + __jso_gmt_numtostr(pi)); + } + if (abs(real(__jso_gmt_numtostr(104729.903455))-104729.903455) > tolerance) { + show_message("Scientific notation conversion failed for large decimal number! Result: " + __jso_gmt_numtostr(104729.903455)); + } + if (abs(real(__jso_gmt_numtostr(-pi))+pi) > tolerance) { + show_message("Scientific notation conversion failed for -pi! Result: " + __jso_gmt_numtostr(-pi)); + } + if (abs(real(__jso_gmt_numtostr(1/pi))-1/pi) > tolerance) { + show_message("Scientific notation conversion failed for 1/pi! Result: " + __jso_gmt_numtostr(1/pi)); + } + +} + +#define __jso_gmt_test_elem +{ + + /** + _test_elem(): Runs tuple element retrieval tests + @author: GameGeisha + @version: 1.2 (GMTuple) + */ + + if (__jso_gmt_elem(__jso_gmt_tuple("Qblock", "kll"), 0) != "Qblock") { + show_message("Element retrieval failed for simple string. #Should be:Qblock#Actual:" + __jso_gmt_elem(__jso_gmt_tuple("Qblock"), 0)); + } + if (__jso_gmt_elem(__jso_gmt_tuple(9, "Q", -7), 0) != 9) { + show_message("Element retrieval failed for simple number. #Should be 9#Actual:" + string(__jso_gmt_elem(__jso_gmt_tuple(9, "Q", 7), 0))); + } + if (__jso_gmt_elem(__jso_gmt_tuple("Qblock", "", "Negg"), 1) != "") { + show_message("Element retrieval failed for empty string#Should be empty string#Actual:"+string(__jso_gmt_elem(__jso_gmt_tuple("Qblock", "", "Negg"), 0))); + } + + if (__jso_gmt_elem(__jso_gmt_elem(__jso_gmt_tuple("Not this", __jso_gmt_tuple(0, 1, 2, 3), "Waahoo"), 1), 3) != 3) { + show_message("Element retrieval failed in nested tuple. #Should be 3#Actual:" + string(__jso_gmt_elem(__jso_gmt_elem(__jso_gmt_tuple("Not this", __jso_gmt_tuple(0, 1, 2, 3), "Waahoo"), 1), 3))); + } + +} + +#define __jso_gmt_test_size +{ + + /** + _test_size(): Runs tuple size tests + @author: GameGeisha + @version: 1.2 (GMTuple) + */ + + if (__jso_gmt_size(__jso_gmt_tuple("Waahoo", "Negg", 0)) != 3) { + show_message("Bad size for 3-tuple"); + } + if (__jso_gmt_size(__jso_gmt_tuple()) != 0) { + show_message("Bad size for null tuple"); + } + if (__jso_gmt_size(__jso_gmt_tuple(7)) != 1) { + show_message("Bad size for 1-tuple"); + } + if (__jso_gmt_size(__jso_gmt_tuple(1,2,3,4,5,6,7,8,9,10)) != 10) { + show_message("Bad size for 10-tuple"); + } + +} + +#define jso_new_map +{ + /** + jso_new_map(): Create a new map. + JSOnion version: 1.0.0d + */ + return ds_map_create(); +} + +#define jso_new_list +{ + /** + jso_new_list(): Create a new list. + JSOnion version: 1.0.0d + */ + return ds_list_create(); +} + +#define jso_map_add_real +{ + /** + jso_map_add_real(map, key, val): Add the key-value pair : to , where is a real value. + JSOnion version: 1.0.0d + */ + ds_map_add(argument0, argument1, argument2); +} + +#define jso_map_add_string +{ + /** + jso_map_add_string(map, key, str): Add the key-value pair : to , where is a string value. + JSOnion version: 1.0.0d + */ + ds_map_add(argument0, argument1, "s" + argument2); +} + +#define jso_map_add_sublist +{ + /** + jso_map_add_sublist(map, key, sublist): Add the key-value pair : to , where is a list. + JSOnion version: 1.0.0d + */ + ds_map_add(argument0, argument1, "l" + string(argument2)); +} + +#define jso_map_add_submap +{ + /** + jso_map_add_submap(map, key, submap): Add the key-value pair : to , where is a map. + JSOnion version: 1.0.0d + */ + ds_map_add(argument0, argument1, "m" + string(argument2)); +} + +#define jso_map_add_integer +{ + /** + jso_map_add_integer(map, key, int): Add the key-value pair : to , where is a integer value. + JSOnion version: 1.0.0d + */ + ds_map_add(argument0, argument1, floor(argument2)); +} + +#define jso_map_add_boolean +{ + /** + jso_map_add_boolean(map, key, bool): Add the key-value pair : to , where is a boolean value. + JSOnion version: 1.0.0d + */ + if (argument2) { + ds_map_add(argument0, argument1, "btrue"); + } else { + ds_map_add(argument0, argument1, "bfalse"); + } +} + +#define jso_list_add_real +{ + /** + jso_list_add_real(list, val): Append the real value to . + JSOnion version: 1.0.0d + */ + ds_list_add(argument0, argument1); +} + +#define jso_list_add_string +{ + /** + jso_list_add_string(list, str): Append the string value to . + JSOnion version: 1.0.0d + */ + ds_list_add(argument0, "s" + argument1); +} + +#define jso_list_add_sublist +{ + /** + jso_list_add_sublist(list, sublist): Append the list to . + JSOnion version: 1.0.0d + */ + ds_list_add(argument0, "l" + string(argument1)); +} + +#define jso_list_add_submap +{ + /** + jso_list_add_submap(list, submap): Append the map to . + JSOnion version: 1.0.0d + */ + ds_list_add(argument0, "m" + string(argument1)); +} + +#define jso_list_add_integer +{ + /** + jso_list_add_integer(list, int): Append the integer to . + JSOnion version: 1.0.0d + */ + ds_list_add(argument0, floor(argument1)); +} + +#define jso_list_add_boolean +{ + /** + jso_list_add_boolean(list, bool): Append the boolean value to . + JSOnion version: 1.0.0d + */ + if (argument1) { + ds_list_add(argument0, "btrue"); + } else { + ds_list_add(argument0, "bfalse"); + } +} + +#define jso_map_get +{ + /** + jso_map_get(map, key): Retrieve the value stored in with the key value , with the correct type. + JSOnion version: 1.0.0d + */ + + //Grab the value + var v; + v = ds_map_find_value(argument0, argument1); + + //String; could be string, map or list + if (is_string(v)) { + switch (string_char_at(v, 1)) { + case "s": + return string_delete(v, 1, 1); + break; + case "l": case "m": + return real(string_delete(v, 1, 1)); + break; + case "b": + if (v == "btrue") { + return true; + } + else if (v == "bfalse") { + return false; + } + else { + show_error("Invalid boolean value.", true); + } + break; + default: show_error("Invalid map contents.", true); break; + } + } + + //Real; return real value as-is + else { + return v; + } +} + +#define jso_map_get_type +{ + /** + jso_map_get_type(map, key): Return the type of value to which the key value is mapped to in . + JSOnion version: 1.0.0d + */ + + //Grab the value + var v; + v = ds_map_find_value(argument0, argument1); + + //String; could be string, map or list + if (is_string(v)) { + switch (string_char_at(v, 1)) { + case "s": + return jso_type_string; + break; + case "l": + return jso_type_list; + break; + case "m": + return jso_type_map; + break; + case "b": + return jso_type_boolean; + break; + default: show_error("Invalid map content type.", true); break; + } + } + + //Real + else { + return jso_type_real; + } +} + +#define jso_list_get +{ + /** + jso_list_get(list, index): Retrieve the value stored in at position , with the correct type. + JSOnion version: 1.0.0d + */ + + //Grab the value + var v; + v = ds_list_find_value(argument0, argument1); + + //String; could be string, map or list + if (is_string(v)) { + switch (string_char_at(v, 1)) { + case "s": + return string_delete(v, 1, 1); + break; + case "l": case "m": + return real(string_delete(v, 1, 1)); + break; + case "b": + if (v == "btrue") { + return true; + } + else if (v == "bfalse") { + return false; + } + else { + show_error("Invalid boolean value.", true); + } + break; + default: show_error("Invalid list contents.", true); break; + } + } + + //Real; return real value as-is + else { + return v; + } +} + +#define jso_list_get_type +{ + /** + jso_list_get_type(list, index): Retrieve the type of value found at position of . + JSOnion version: 1.0.0d + */ + + //Grab the value + var v; + v = ds_list_find_value(argument0, argument1); + + //String; could be string, map or list + if (is_string(v)) { + switch (string_char_at(v, 1)) { + case "s": + return jso_type_string; + break; + case "l": + return jso_type_list; + break; + case "m": + return jso_type_map; + break; + case "b": + return jso_type_boolean; + break; + default: show_error("Invalid list content type.", true); break; + } + } + + //Real + else { + return jso_type_real; + } +} + +#define jso_cleanup_map +{ + /** + jso_cleanup_map(map): Recursively free up . + JSOnion version: 1.0.0d + */ + + //Loop through all keys + var i, l, k; + l = ds_map_size(argument0); + k = ds_map_find_first(argument0); + for (i=0; i. + JSOnion version: 1.0.0d + */ + + //Loop through all elements + var i, l, v; + l = ds_list_size(argument0); + for (i=0; i): Return a JSON-encoded version of real value . + This uses scientific notation with up to 15 significant digits for decimal values. + For integers, it just uses string(). + This is adapted from the same algorithm used in GMTuple. + JSOnion version: 1.0.0d + */ + + return __jso_gmt_numtostr(argument0); +} + +#define jso_encode_string +{ + /** + jso_encode_string(str): Return a JSON-encoded version of string . + JSOnion version: 1.0.0d + */ + + //Iteratively reconstruct the string + var i, l, s, c; + s = ""; + l = string_length(argument0); + for (i=1; i<=l; i+=1) { + //Replace escape characters + c = string_char_at(argument0, i); + switch (ord(c)) { + case 34: case 92: case 47: //Double quotes, backslashes and slashes + s += "\" + c; + break; + case 8: //Backspace + s += "\b"; + break; + case 12: //Form feed + s += "\f"; + break; + case 10: //New line + s += "\n"; + break; + case 13: //Carriage return + s += "\r"; + break; + case 9: //Horizontal tab + s += "\t"; + break; + default: //Not an escape character + s += c; + break; + } + } + + //Add quotes + return '"' + s + '"'; +} + +#define jso_encode_list +{ + /** + jso_encode_list(list): Return a JSON-encoded version of list . + JSOnion version: 1.0.0d + */ + + //Iteratively encode each element + var i, l, s; + s = ""; + l = ds_list_size(argument0); + for (i=0; i 0) { + s += ","; + } + //Select correct encoding for each element, then recursively encode each + switch (jso_list_get_type(argument0, i)) { + case jso_type_real: + s += jso_encode_real(jso_list_get(argument0, i)); + break; + case jso_type_string: + s += jso_encode_string(jso_list_get(argument0, i)); + break; + case jso_type_map: + s += jso_encode_map(jso_list_get(argument0, i)); + break; + case jso_type_list: + s += jso_encode_list(jso_list_get(argument0, i)); + break; + case jso_type_boolean: + s += jso_encode_boolean(jso_list_get(argument0, i)); + break; + } + } + + //Done, add square brackets + return "[" + s + "]"; +} + +#define jso_encode_map +{ + /** + jso_encode_map(map): Return a JSON-encoded version of map . + JSOnion version: 1.0.0d + */ + + //Go through every key-value pair + var i, l, k, s; + s = ""; + l = ds_map_size(argument0); + k = ds_map_find_first(argument0); + for (i=0; i 0) { + s += ","; + } + //Find the key and encode it + if (is_real(k)) { + s += jso_encode_real(k); + } else { + s += jso_encode_string(k); + } + //Add the : separator + s += ":"; + //Select correct encoding for each value, then recursively encode each + switch (jso_map_get_type(argument0, k)) { + case jso_type_real: + s += jso_encode_real(jso_map_get(argument0, k)); + break; + case jso_type_string: + s += jso_encode_string(jso_map_get(argument0, k)); + break; + case jso_type_map: + s += jso_encode_map(jso_map_get(argument0, k)); + break; + case jso_type_list: + s += jso_encode_list(jso_map_get(argument0, k)); + break; + case jso_type_boolean: + s += jso_encode_boolean(jso_map_get(argument0, k)); + break; + } + //Get next key + k = ds_map_find_next(argument0, k); + } + + //Done, add braces + return "{" + s + "}"; +} + +#define jso_encode_integer +{ + /** + jso_encode_integer(int): Return a JSON-encoded version of the integer value . + JSOnion version: 1.0.0d + */ + return string(floor(argument0)); +} + +#define jso_encode_boolean +{ + /** + jso_encode_boolean(bool): Return a JSON-encoded version of the boolean value . + JSOnion version: 1.0.0d + */ + if (argument0) { + return "true"; + } else { + return "false"; + } +} + +#define jso_decode_map +{ + /** + jso_decode_map(json): Return a JSOnion-compatible map representing the JSON string . + JSOnion version: 1.0.0d + */ + return __jso_gmt_elem(_jso_decode_map(argument0, 1), 0); +} + +#define jso_decode_list +{ + /** + jso_decode_list(json): Return a JSOnion-compatible list representing the JSON string . + JSOnion version: 1.0.0d + */ + return __jso_gmt_elem(_jso_decode_list(argument0, 1), 0); +} + +#define jso_decode_string +{ + /** + jso_decode_string(json): Return a string representing the JSON string . + JSOnion version: 1.0.0d + */ + return __jso_gmt_elem(_jso_decode_string(argument0, 1), 0); +} + +#define jso_decode_boolean +{ + /** + jso_decode_boolean(json): Return a boolean value representing the JSON string . + JSOnion version: 1.0.0d + */ + return __jso_gmt_elem(_jso_decode_boolean(argument0, 1), 0); +} + +#define jso_decode_real +{ + /** + jso_decode_real(json): Return a real value representing the JSON string . + JSOnion version: 1.0.0d + */ + return __jso_gmt_elem(_jso_decode_real(argument0, 1), 0); +} + +#define jso_decode_integer +{ + /** + jso_decode_integer(json): Return an integer value representing the JSON string . + JSOnion version: 1.0.0d + */ + return __jso_gmt_elem(_jso_decode_integer(argument0, 1), 0); +} + +#define _jso_decode_map +{ + /** + _jso_decode_map(json, startindex): Extract a map from JSON string starting at position . + Return a 2-tuple of the extracted map handle and the position after the ending }. + JSOnion version: 1.0.0d + */ + var i, len, map; + i = argument1; + len = string_length(argument0); + map = jso_new_map(); + + //Seek to first { + var c; + c = string_char_at(argument0, i); + if (c != "{") { + do { + i += 1; + c = string_char_at(argument0, i); + if (!_jso_is_whitespace_char(c)) && (c != "{") { + show_error("Cannot parse map at position " + string(i), true); + } + } until (c == "{") + } + i += 1; + + //Read until end of JSON or ending } + var found_end, state, found, current_key; + found_end = false; + state = 0; + for (i=i; i<=len && !found_end; i+=1) { + c = string_char_at(argument0, i); + switch (state) { + //0: Looking for a key or closing } + case 0: + switch (c) { + case "}": + found_end = true; + break; + case '"': + found = _jso_decode_string(argument0, i); + current_key = __jso_gmt_elem(found, 0); + i = __jso_gmt_elem(found, 1)-1; + state = 1; + break; + case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": case "+": case "-": + found = _jso_decode_real(argument0, i); + current_key = __jso_gmt_elem(found, 0); + i = __jso_gmt_elem(found, 1)-1; + state = 1; + break; + default: + if (!_jso_is_whitespace_char(c)) { + show_error("Unexpected character at position " + string(i) + ".", true); + } + break; + } + break; + //1: Looking for the : separator + case 1: + switch (c) { + case ":": + state = 2; + break; + default: + if (!_jso_is_whitespace_char(c)) { + show_error("Unexpected character at position " + string(i) + ".", true); + } + break; + } + break; + //2: Looking for a value + case 2: + switch (c) { + case "[": + found = _jso_decode_list(argument0, i); + jso_map_add_sublist(map, current_key, __jso_gmt_elem(found, 0)); + i = __jso_gmt_elem(found, 1)-1; + state = 3; + break; + case "{": + found = _jso_decode_map(argument0, i); + jso_map_add_submap(map, current_key, __jso_gmt_elem(found, 0)); + i = __jso_gmt_elem(found, 1)-1; + state = 3; + break; + case '"': + found = _jso_decode_string(argument0, i); + jso_map_add_string(map, current_key, __jso_gmt_elem(found, 0)); + i = __jso_gmt_elem(found, 1)-1; + state = 3; + break; + case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": case "+": case "-": + found = _jso_decode_real(argument0, i); + jso_map_add_real(map, current_key, __jso_gmt_elem(found, 0)); + i = __jso_gmt_elem(found, 1)-1; + state = 3; + break; + case "t": case "f": + found = _jso_decode_boolean(argument0, i); + jso_map_add_boolean(map, current_key, __jso_gmt_elem(found, 0)); + i = __jso_gmt_elem(found, 1)-1; + state = 3; + break; + default: + if (!_jso_is_whitespace_char(c)) { + show_error("Unexpected character at position " + string(i) + ".", true); + } + break; + } + break; + //3: Done looking for an entry, want comma or } + case 3: + switch (c) { + case "}": + found_end = true; + break; + case ",": + state = 0; + break; + default: + if (!_jso_is_whitespace_char(c)) { + show_error("Unexpected character at position " + string(i) + ".", true); + } + break; + } + break; + } + } + + //Return extracted map with ending position if the ending } is found + if (found_end) { + return __jso_gmt_tuple(map, i); + } + //Ended too early, throw error + else { + show_error("Unexpected end of map in JSON string.", true); + } +} + +#define _jso_decode_list +{ + /** + _jso_decode_list(json, startindex): Extract a list from JSON string starting at position . + Return a 2-tuple of the extracted list handle and the position after the ending ]. + JSOnion version: 1.0.0d + */ + var i, len, list; + i = argument1; + len = string_length(argument0); + list = jso_new_list(); + + //Seek to first [ + var c; + c = string_char_at(argument0, i); + if (c != "[") { + do { + i += 1; + c = string_char_at(argument0, i); + if (!_jso_is_whitespace_char(c)) && (c != "[") { + show_error("Cannot parse list at position " + string(i), true); + } + } until (c == "[") + } + i += 1; + + //Read until end of JSON or ending ] + var found_end, state, found; + found_end = false; + state = 0; + for (i=i; i<=len && !found_end; i+=1) { + c = string_char_at(argument0, i); + switch (state) { + //0: Looking for an item + case 0: + switch (c) { + case "]": + found_end = true; + break; + case "[": + found = _jso_decode_list(argument0, i); + jso_list_add_sublist(list, __jso_gmt_elem(found, 0)); + i = __jso_gmt_elem(found, 1)-1; + state = 1; + break; + case "{": + found = _jso_decode_map(argument0, i); + jso_list_add_submap(list, __jso_gmt_elem(found, 0)); + i = __jso_gmt_elem(found, 1)-1; + state = 1; + break; + case '"': + found = _jso_decode_string(argument0, i); + jso_list_add_string(list, __jso_gmt_elem(found, 0)); + i = __jso_gmt_elem(found, 1)-1; + state = 1; + break; + case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": case "+": case "-": + found = _jso_decode_real(argument0, i); + jso_list_add_real(list, __jso_gmt_elem(found, 0)); + i = __jso_gmt_elem(found, 1)-1; + state = 1; + break; + case "t": case "f": + found = _jso_decode_boolean(argument0, i); + jso_list_add_boolean(list, __jso_gmt_elem(found, 0)); + i = __jso_gmt_elem(found, 1)-1; + state = 1; + break; + default: + if (!_jso_is_whitespace_char(c)) { + show_error("Unexpected character at position " + string(i) + ".", true); + } + break; + } + break; + //1: Done looking for an item, want comma or ] + case 1: + switch (c) { + case "]": + found_end = true; + break; + case ",": + state = 0; + break; + default: + if (!_jso_is_whitespace_char(c)) { + show_error("Unexpected character at position " + string(i) + ".", true); + } + break; + } + break; + } + } + + //Return extracted list with ending position if the ending ] is found + if (found_end) { + return __jso_gmt_tuple(list, i); + } + //Ended too early, throw error + else { + show_error("Unexpected end of list in JSON string.", true); + } +} + +#define _jso_decode_string +{ + /** + _jso_decode_string(json, startindex): Extract a string from JSON string starting at position . + Return a 2-tuple of the extracted string and the position after the ending double quote. + JSOnion version: 1.0.0d + */ + var i, len, str; + i = argument1; + len = string_length(argument0); + str = ""; + + //Seek to first double quote + var c; + c = string_char_at(argument0, i); + if (c != '"') { + do { + i += 1; + c = string_char_at(argument0, i); + } until (c == '"') + } + i += 1; + + //Read until end of JSON or ending double quote + var found_end, escape_mode; + found_end = false; + escape_mode = false; + for (i=i; i<=len && !found_end; i+=1) { + c = string_char_at(argument0, i); + //Escape mode + if (escape_mode) { + switch (c) { + case '"': case "\": case "/": + str += c; + escape_mode = false; + break; + case "b": + str += chr(8); + escape_mode = false; + break; + case "f": + str += chr(12); + escape_mode = false; + break; + case "n": + str += chr(10); + escape_mode = false; + break; + case "r": + str += chr(13); + escape_mode = false; + break; + case "t": + str += chr(9); + escape_mode = false; + break; + case "u": + var u; + if (len-i < 5) { + show_error("Invalid escape character at position " + string(i) + ".", true); + } else { + str += chr(_jso_hex_to_decimal(string_copy(argument0, i+1, 4))); + escape_mode = false; + i += 4; + } + break; + default: + show_error("Invalid escape character at position " + string(i) + ".", true); + break; + } + } + //Regular mode + else { + switch (c) { + case '"': found_end = true; break; + case "\": escape_mode = true; break; + default: str += c; break; + } + } + } + + //Return extracted string with ending position if the ending double quote is found + if (found_end) { + return __jso_gmt_tuple(str, i); + } + //Ended too early, throw error + else { + show_error("Unexpected end of string in JSON string.", true); + } +} + +#define _jso_decode_boolean +{ + /** + _jso_decode_boolean(json, startindex): Extract a boolean from JSON string starting at position . + Return a 2-tuple of the extracted boolean and the position after the last e. + JSOnion version: 1.0.0d + */ + var i, len, str; + i = argument1; + len = string_length(argument0); + + //Seek to first t or f that can be found + var c; + c = string_char_at(argument0, i); + if (c != "t") && (c != "f") { + do { + i += 1; + c = string_char_at(argument0, i); + if (!_jso_is_whitespace_char(c)) && (c != "t") && (c != "f") { + show_error("Cannot parse boolean value at position " + string(i), true); + } + } until (c == "t") || (c == "f") + } + + //Look for true if t is found + if (c == "t") && (string_copy(argument0, i, 4) == "true") { + return __jso_gmt_tuple(true, i+4); + } + //Look for false if f is found + else if (c == "f") && (string_copy(argument0, i, 5) == "false") { + return __jso_gmt_tuple(false, i+5); + } + //Error: unexpected ending + else { + show_error("Unexpected end of boolean in JSON string.", true); + } +} + +#define _jso_decode_real +{ + /** + _jso_decode_real(json, startindex): Extract a real value from JSON string starting at position . + Return a 2-tuple of the extracted real value and the position after the real value. + JSOnion version: 1.0.0d + */ + var i, len, str; + i = argument1; + len = string_length(argument0); + str = ""; + + //Seek to first character: +, -, or 0-9 + var c; + c = string_char_at(argument0, i); + if (string_pos(c, "0123456789+-") == 0) { + do { + i += 1; + c = string_char_at(argument0, i); + if (!_jso_is_whitespace_char(c)) && (string_pos(c, "0123456789+-") == 0) { + show_error("Cannot parse real value at position " + string(i), true); + } + } until (string_pos(c, "0123456789+-") > 0) + } + + //Determine starting state + var state; + switch (c) { + case "+": case "-": + state = 0; + break; + default: + state = 1; + break; + } + str += c; + i += 1; + + //Loop until no more digits found + var done; + done = false; + for (i=i; i<=len && !done; i+=1) { + c = string_char_at(argument0, i); + switch (state) { + //0: Found a sign, looking for a starting number + case 0: + switch (c) { + case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": + str += c; + state = 1; + break; + default: + show_error("Unexpected character at position " + string(i) + ", expecting a digit.", true); + break; + } + break; + //1: Found a starting digit, looking for decimal dot, e, E, or more digits + case 1: + if (_jso_is_whitespace_char(c)) || (string_pos(c, ":,]}") > 0) { + done = true; + i -= 1; + } else { + switch (c) { + case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": + str += c; + break; + case ".": + str += c; + state = 2; + break; + case "e": case "E": + str += c; + state = 3; + break; + default: + show_error("Unexpected character at position " + string(i) + ", expecting a dot, e, E or a digit.", true); + break; + } + } + break; + //2: Found a decimal dot, looking for more digits + case 2: + switch (c) { + case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": + str += c; + state = -2; + break; + default: + show_error("Unexpected character at position " + string(i) + ", expecting a digit.", true); + break; + } + break; + //-2: Found a decimal dot and a digit after it, looking for more digits, e, or E + case -2: + if (_jso_is_whitespace_char(c)) || (string_pos(c, ":,]}") > 0) { + done = true; + i -= 1; + } else { + switch (c) { + case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": + str += c; + break; + case "e": case "E": + str += c; + state = 3; + break; + default: + show_error("Unexpected character at position " + string(i) + ", expecting an e, E or a digit.", true); + break; + } + } + break; + //3: Found an e/E, looking for +, - or more digits + case 3: + switch (c) { + case "+": case "-": + str += c; + state = 4; + break; + case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": + str += c; + state = 5; + break; + default: + show_error("Unexpected character at position " + string(i) + ", expecting a +, - or a digit.", true); + break; + } + break; + //4: Found an e/E exponent sign, looking for more digits + case 4: + switch (c) { + case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": + str += c; + state = 5; + break; + default: + show_error("Unexpected character at position " + string(i) + ", expecting a digit.", true); + break; + } + break; + //5: Looking for final digits of the exponent + case 5: + if (_jso_is_whitespace_char(c)) || (string_pos(c, ":,]}") > 0) { + done = true; + i -= 1; + } else { + switch (c) { + case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": + str += c; + state = 5; + break; + default: + show_error("Unexpected character at position " + string(i) + ", expecting a digit.", true); + break; + } + } + break; + } + } + + //Am I still expecting more characters? + if (done) || (state == 1) || (state == -2) || (state == 5) { + return __jso_gmt_tuple(real(str), i); + } + //Error: unexpected ending + else { + show_error("Unexpected end of real in JSON string.", true); + } +} + +#define _jso_decode_integer +{ + /** + _jso_decode_real(json, startindex): Extract a real value from JSON string starting at position . + Return a 2-tuple of the extracted integer value (with leading i) and the position after the real value. + JSOnion version: 1.0.0d + */ + var i, len, str; + i = argument1; + len = string_length(argument0); + str = ""; + + //Seek to first character: +, -, or 0-9 + var c; + c = string_char_at(argument0, i); + if (string_pos(c, "0123456789+-") == 0) { + do { + i += 1; + c = string_char_at(argument0, i); + if (!_jso_is_whitespace_char(c)) && (string_pos(c, "0123456789+-") == 0) { + show_error("Cannot parse integer value at position " + string(i), true); + } + } until (string_pos(c, "0123456789+-") > 0) + } + + //Determine starting state + var state; + switch (c) { + case "+": case "-": + state = 0; + break; + default: + state = 1; + break; + } + str += c; + i += 1; + + //Loop until no more digits found + var done; + done = false; + for (i=i; i<=len && !done; i+=1) { + c = string_char_at(argument0, i); + switch (state) { + //0: Found a sign, looking for a starting number + case 0: + switch (c) { + case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": + str += c; + state = 1; + break; + default: + show_error("Unexpected character at position " + string(i) + ", expecting a digit.", true); + break; + } + break; + //1: Found a starting digit, looking for decimal dot, e, E, or more digits + case 1: + if (_jso_is_whitespace_char(c)) || (string_pos(c, ":,]}") > 0) { + done = true; + i -= 1; + } else { + switch (c) { + case "0": case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": + str += c; + break; + default: + show_error("Unexpected character at position " + string(i) + ", expecting a digit.", true); + break; + } + } + break; + } + } + + //Am I still expecting more characters? + if (done) || (state == 1) { + return __jso_gmt_tuple(floor(real(str)), i); + } + //Error: unexpected ending + else { + show_error("Unexpected end of integer in JSON string.", true); + } +} + +#define jso_compare_maps +{ + /** + jso_compare_maps(map1, map2): Return whether the contents of JSOnion-compatible maps and are the same. + JSOnion version: 1.0.0d + */ + + //If they aren't the same size, they can't be the same + var size; + size = ds_map_size(argument0); + if (size != ds_map_size(argument1)) { + return false; + } + + //Compare contents pairwise + var i, k, type, a, b; + k = ds_map_find_first(argument0); + for (i=0; i and are the same. + JSOnion version: 1.0.0d + */ + + //If they aren't the same size, they can't be the same + var size; + size = ds_list_size(argument0); + if (size != ds_list_size(argument1)) { + return false; + } + + //Compare contents pairwise + var i, type, a, b; + for (i=0; i, return whether a value exists there. + JSOnion version: 1.0.0d + */ + + //Catch empty calls + if (argument_count < 2) { + show_error("Expected at least 2 arguments, got " + string(argument_count) + ".", true); + } + + //Build list of keys/indices + var i, key_list; + key_list = ds_list_create(); + for (i=1; i, return whether a value exists there. + JSOnion version: 1.0.0d + */ + + //Catch empty calls + if (argument_count < 2) { + show_error("Expected at least 2 arguments, got " + string(argument_count) + ".", true); + } + + //Build list of keys/indices + var i, key_list; + key_list = ds_list_create(); + for (i=1; i, return the value that exists there. + JSOnion version: 1.0.0d + */ + + //Catch empty calls + if (argument_count < 2) { + show_error("Expected at least 2 arguments, got " + string(argument_count) + ".", true); + } + + //Build list of keys/indices + var i, key_list; + key_list = ds_list_create(); + for (i=1; i, return the type of value that exists there. + JSOnion version: 1.0.0d + */ + + //Catch empty calls + if (argument_count < 2) { + show_error("Expected at least 2 arguments, got " + string(argument_count) + ".", true); + } + + //Build list of keys/indices + var i, key_list; + key_list = ds_list_create(); + for (i=1; i, return the value that exists there. + JSOnion version: 1.0.0d + */ + + //Catch empty calls + if (argument_count < 2) { + show_error("Expected at least 2 arguments, got " + string(argument_count) + ".", true); + } + + //Build list of keys/indices + var i, key_list; + key_list = ds_list_create(); + for (i=1; i, return the type of value that exists there. + JSOnion version: 1.0.0d + */ + + //Catch empty calls + if (argument_count < 2) { + show_error("Expected at least 2 arguments, got " + string(argument_count) + ".", true); + } + + //Build list of keys/indices + var i, key_list; + key_list = ds_list_create(); + for (i=1; i= ds_list_size(data)) { + switch (task_type) { + case 0: + return false; + break; + default: + show_error("Index overflow for nested lists in " + type_string + " lookup.", true); + break; + } + } + type = jso_list_get_type(data, k); + data = jso_list_get(data, k); + break; + //Trying to go through a leaf; don't attempt to look further + default: + switch (task_type) { + case 0: + return false; + break; + default: + show_error("Recursive overflow in " + type_string + " lookup.", true); + break; + } + break; + } + } + + //Can find something, return the value requested by the task + switch (task_type) { + case 0: + return true; + break; + case 1: + return data; + break; + case 2: + return type; + break; + } +} + +#define _jso_is_whitespace_char +{ + /** + _jso_is_whitespace_char(char): Return whether is a whitespace character. + Definition of whitespace is given by Unicode 6.0, Chapter 4.6. + JSOnion version: 1.0.0d + */ + switch (ord(argument0)) { + case $0009: + case $000A: + case $000B: + case $000C: + case $000D: + case $0020: + case $0085: + case $00A0: + case $1680: + case $180E: + case $2000: + case $2001: + case $2002: + case $2003: + case $2004: + case $2005: + case $2006: + case $2007: + case $2008: + case $2009: + case $200A: + case $2028: + case $2029: + case $202F: + case $205F: + case $3000: + return true; + } + return false; +} + +#define _jso_hex_to_decimal +{ + /** + _jso_hex_to_decimal(hex_string): Return the decimal value of the hex number represented by + JSOnion version: 1.0.0d + */ + var hex_string, hex_digits; + hex_string = string_lower(argument0); + hex_digits = "0123456789abcdef"; + + //Convert digit-by-digit + var i, len, digit_value, num; + len = string_length(hex_string); + num = 0; + for (i=1; i<=len; i+=1) { + digit_value = string_pos(string_char_at(hex_string, i), hex_digits)-1; + if (digit_value >= 0) { + num *= 16; + num += digit_value; + } + //Unknown character + else { + show_error("Invalid hex number: " + argument0, true); + } + } + return num; +} + diff --git a/samples/Game Maker Language/jsonion_test.gml b/samples/Game Maker Language/jsonion_test.gml new file mode 100644 index 00000000..3cf0d423 --- /dev/null +++ b/samples/Game Maker Language/jsonion_test.gml @@ -0,0 +1,1169 @@ +// Originally from /jsonion_test.gml in JSOnion +// JSOnion v1.0.0d is licensed under the MIT licence. You may freely adapt and use this library in commercial and non-commercial projects. + +#define assert_true +{ + /** + assert_true(result, errormsg): Display error unless is true. + */ + + if (!argument0) { + _assert_error_popup(argument1 + string_repeat(_assert_newline(), 2) + "Expected true, got false."); + } +} + +#define assert_false +{ + /** + assert_false(result, errormsg): Display error unless is false. + */ + + if (argument0) { + _assert_error_popup(argument1 + string_repeat(_assert_newline(), 2) + "Expected false, got true."); + } +} + +#define assert_equal +{ + /** + assert_equal(expected, result, errormsg): Display error unless and are equal. + */ + + //Safe equality check; won't crash even if the two are different types + var match; + match = is_string(argument0) == is_string(argument1); + if (match) { + match = argument0 == argument1; + } + + //No match? + if (!match) { + //Data types + var type; + type[0] = "(Real)"; + type[1] = "(String)"; + + //Construct message + var msg; + msg = argument2; + //Add expected value + msg += string_repeat(_assert_newline(), 2); + msg += "Expected " + type[is_string(argument0)] + ":" + _assert_newline(); + msg += _assert_debug_value(argument0); + //Add actual value + msg += string_repeat(_assert_newline(), 2); + msg += "Actual " + type[is_string(argument1)] + ":" + _assert_newline(); + msg += _assert_debug_value(argument1); + + //Display message + _assert_error_popup(msg); + } +} + +#define _assert_error_popup +{ + /** + _assert_error_popup(msg): Display an assertion error. + */ + + //Display message + if (os_browser == browser_not_a_browser) { + show_error(argument0, false); //Full-fledged error message for non-browser environments + } else { + show_message(argument0); //Browsers don't support show_error(), use show_message() instead + } +} + +#define _assert_debug_value +{ + /** + _assert_debug_value(val): Returns a low-level debug value for the value . + can be a string or a real value. + */ + + //String + if (is_string(argument0)) { + if (os_browser == browser_not_a_browser) { + return argument0; + } else { + return string_replace_all(argument0, "#", "\#"); + } + } + + //Numeric --- use GMTuple's algorithm + else { + + //Integers + if (frac(argument0) == 0) { + return string(argument0); + } + + //Decimal numbers; get exponent and mantissa + var mantissa, exponent; + exponent = floor(log10(abs(argument0))); + mantissa = string_format(argument0/power(10,exponent), 15, 14); + //Look for trailing zeros in the mantissa + var i, ca; + i = string_length(mantissa); + do { + ca = string_char_at(mantissa, i); + i -= 1; + } until (ca != "0") + //Remove the dot if only the first digit of the normalized mantissa is nonzero + if (ca != ".") { + mantissa = string_copy(mantissa, 1, i+1); + } + else { + mantissa = string_copy(mantissa, 1, i); + } + //Remove the exponent if it is 0 + if (exponent != 0) { + return mantissa + "e" + string(exponent); + } else { + return mantissa; + } + + //GMTuple algorithm done + } +} + +#define _assert_newline +{ + /** + _assert_newline(): Returns a system-appropriate newline character sequence. + */ + + if (os_browser == browser_not_a_browser) { + return chr(13) + chr(10); + } else { + return "#"; + } +} + +#define jso_test_all +{ + /** + jso_test_all(): Run the test suite for the JSOnion library. + JSOnion version: 1.0.0d + */ + var a, b; + a = current_time; + _test_jso_new(); + _test_jso_map_add(); + _test_jso_list_add(); + _test_jso_encode(); + _test_jso_compare(); + _test_jso_decode(); + _test_jso_lookup(); + _test_jso_bugs(); + __jso_gmt_test_all(); + b = current_time; + show_debug_message("JSOnion: Tests completed in " + string(b-a) + "ms."); +} + +#define _test_jso_new +{ + /** + _test_jso_new(): Test jso_new_*() functions. + JSOnion version: 1.0.0d + */ + + var expected, actual; + + //jso_new_map() + actual = jso_new_map(); + assert_true(actual >= 0, "jso_new_map() failed to create a new map."); + ds_map_destroy(actual); + + //jso_new_list() + actual = jso_new_list(); + assert_true(actual >= 0, "jso_new_list() failed to create a new list."); + ds_list_destroy(actual); +} + +#define _test_jso_map_add +{ + /** + _test_jso_map_add(): Test jso_map_add_*() functions. + JSOnion version: 1.0.0d + */ + + var expected, actual, key, map; + map = jso_new_map(); + + //jso_map_add_real() + expected = pi; + key = "pi"; + jso_map_add_real(map, key, expected); + actual = jso_map_get(map, key); + assert_true(ds_map_exists(map, key), "jso_map_add_real() failed to add the number."); + assert_equal(expected, actual, "jso_map_add_real() added the wrong number."); + ds_map_delete(map, key); + + //jso_map_add_string() + expected = "waahoo"; + key = "str"; + jso_map_add_string(map, key, expected); + actual = jso_map_get(map, key); + assert_true(ds_map_exists(map, key), "jso_map_add_string() failed to add the string."); + assert_equal(expected, actual, "jso_map_add_string() added the wrong string."); + ds_map_delete(map, key); + + //jso_map_add_sublist() + expected = jso_new_list(); + key = "sublist"; + jso_map_add_sublist(map, key, expected); + actual = jso_map_get(map, key); + assert_true(ds_map_exists(map, key), "jso_map_add_sublist() failed to add the new sublist."); + assert_equal(expected, actual, "jso_map_add_sublist() added the wrong sublist."); + ds_map_delete(map, key); + ds_list_destroy(expected); + + //jso_map_add_submap() + expected = jso_new_map(); + key = "sublist"; + jso_map_add_sublist(map, key, expected); + actual = jso_map_get(map, key); + assert_true(ds_map_exists(map, key), "jso_map_add_submap() failed to add the new submap."); + assert_equal(expected, actual, "jso_map_add_submap() added the wrong submap."); + ds_map_delete(map, key); + ds_map_destroy(expected); + + //jso_map_add_integer() + expected = 2345; + key = "integer"; + jso_map_add_integer(map, key, expected); + actual = jso_map_get(map, key); + assert_true(ds_map_exists(map, key), "jso_map_add_integer() failed to add the new integer."); + assert_equal(expected, actual, "jso_map_add_integer() added the wrong integer."); + ds_map_delete(map, key); + + //jso_map_add_boolean() --- true + expected = true; + key = "booleantrue"; + jso_map_add_boolean(map, key, expected); + actual = jso_map_get(map, key); + assert_true(ds_map_exists(map, key), "jso_map_add_boolean() failed to add true."); + assert_true(actual, "jso_map_add_boolean() added the wrong true."); + ds_map_delete(map, key); + + //jso_map_add_boolean() --- false + expected = false; + key = "booleanfalse"; + jso_map_add_boolean(map, key, expected); + actual = jso_map_get(map, key) + assert_true(ds_map_exists(map, key), "jso_map_add_boolean() failed to add false."); + assert_false(actual, "jso_map_add_boolean() added the wrong false."); + ds_map_delete(map, key); + + //Cleanup + ds_map_destroy(map); +} + +#define _test_jso_list_add +{ + /** + _test_jso_list_add(): Test jso_list_add_*() functions. + JSOnion version: 1.0.0d + */ + + var expected, actual, list; + list = jso_new_list(); + + //jso_list_add_real() + expected = pi; + jso_list_add_real(list, expected); + actual = jso_list_get(list, 0); + assert_false(ds_list_empty(list), "jso_list_add_real() failed to add the number."); + assert_equal(expected, actual, "jso_list_add_real() added the wrong number."); + ds_list_clear(list); + + //jso_list_add_string() + expected = "waahoo"; + jso_list_add_string(list, expected); + actual = jso_list_get(list, 0); + assert_false(ds_list_empty(list), "jso_list_add_string() failed to add the string."); + assert_equal(expected, actual, "jso_list_add_string() added the wrong string."); + ds_list_clear(list); + + //jso_list_add_sublist() + expected = jso_new_list(); + jso_list_add_sublist(list, expected); + actual = jso_list_get(list, 0); + assert_false(ds_list_empty(list), "jso_list_add_sublist() failed to add the sublist."); + assert_equal(expected, actual, "jso_list_add_sublist() added the wrong sublist."); + ds_list_clear(list); + ds_list_destroy(expected); + + //jso_list_add_submap() + expected = jso_new_map(); + jso_list_add_submap(list, expected); + actual = jso_list_get(list, 0); + assert_false(ds_list_empty(list), "jso_list_add_submap() failed to add the submap."); + assert_equal(expected, actual, "jso_list_add_submap() added the wrong submap."); + ds_list_clear(list); + ds_map_destroy(expected); + + //jso_list_add_integer() + expected = 2345; + jso_list_add_integer(list, expected); + actual = jso_list_get(list, 0); + assert_false(ds_list_empty(list), "jso_list_add_integer() failed to add integer."); + assert_equal(expected, actual, "jso_list_add_integer() added the wrong integer."); + ds_list_clear(list); + + //jso_list_add_boolean() --- true + expected = true; + jso_list_add_boolean(list, expected); + actual = jso_list_get(list, 0); + assert_false(ds_list_empty(list), "jso_list_add_boolean() failed to add boolean true."); + assert_true(actual, "jso_list_add_boolean() added the wrong boolean true."); + ds_list_clear(list); + + //jso_list_add_boolean() --- false + expected = false; + jso_list_add_boolean(list, expected); + actual = jso_list_get(list, 0); + assert_false(ds_list_empty(list), "jso_list_add_boolean() failed to add boolean false."); + assert_false(actual, "jso_list_add_boolean() added the wrong boolean false."); + ds_list_clear(list); + + //Cleanup + ds_list_destroy(list); +} + +#define _test_jso_encode +{ + /** + _test_jso_encode(): Tests jso_encode_*() functions. + JSOnion version: 1.0.0d + */ + + var original, expected, actual; + + //jso_encode_real() --- Positive + expected = 3.1415; + actual = jso_encode_real(3.1415); + assert_equal(expected, real(actual), "jso_encode_real() failed to encode positive real!"); + + //jso_encode_real() --- Negative + expected = -2.71828; + actual = jso_encode_real(-2.71828); + assert_equal(expected, real(actual), "jso_encode_real() failed to encode negative real!"); + + //jso_encode_real() --- Zero + expected = 0; + actual = jso_encode_integer(0); + assert_equal(expected, real(actual), "jso_encode_real() failed to encode zero!"); + + //jso_encode_integer() --- Positive + expected = "2345"; + actual = jso_encode_integer(2345); + assert_equal(expected, actual, "jso_encode_integer() failed to encode positive integer!"); + + //jso_encode_integer() --- Negative + expected = "-45"; + actual = jso_encode_integer(-45); + assert_equal(expected, actual, "jso_encode_integer() failed to encode negative integer!"); + + //jso_encode_integer() --- Zero + expected = "0"; + actual = jso_encode_integer(0); + assert_equal(expected, actual, "jso_encode_integer() failed to encode zero!"); + + //jso_encode_boolean() --- true + expected = "true"; + actual = jso_encode_boolean(true); + assert_equal(expected, actual, "jso_encode_boolean() failed to encode true!"); + + //jso_encode_boolean() --- false + expected = "false"; + actual = jso_encode_boolean(false); + assert_equal(expected, actual, "jso_encode_boolean() failed to encode false!"); + + //jso_encode_string() --- Simple string + expected = '"waahoo"'; + actual = jso_encode_string("waahoo"); + assert_equal(expected, actual, "jso_encode_string() failed to encode simple string!"); + + //jso_encode_string() --- Empty string + expected = '""'; + actual = jso_encode_string(""); + assert_equal(expected, actual, "jso_encode_string() failed to encode empty string!"); + + //jso_encode_string() --- Basic escape characters + expected = '"\\\"\b\f\n\r\t"'; + actual = jso_encode_string('\"' + chr(8) + chr(12) + chr(10) + chr(13) + chr(9)); + assert_equal(expected, actual, "jso_encode_string() failed to encode escape character string!"); + + //jso_encode_map() --- Empty map + var empty_map; + empty_map = jso_new_map(); + expected = "{}"; + actual = jso_encode_map(empty_map); + assert_equal(expected, actual, "jso_encode_map() failed to encode empty map!"); + jso_cleanup_map(empty_map); + + //jso_encode_map() --- One-element map + var one_map; + one_map = jso_new_map(); + jso_map_add_string(one_map, "key", "value"); + expected = '{"key":"value"}'; + actual = jso_encode_map(one_map); + assert_equal(expected, actual, "jso_encode_map() failed to encode one-element map!"); + jso_cleanup_map(one_map); + + //jso_encode_map() --- Multi-element map + var multi_map, ok1, ok2; + multi_map = jso_new_map(); + jso_map_add_string(multi_map, "key1", "value\1"); + jso_map_add_integer(multi_map, "key2", 2); + ok1 = '{"key1":"value\\1","key2":2}'; + ok2 = '{"key2":2,"key1":"value\\1"}'; + actual = jso_encode_map(multi_map); + assert_true((actual == ok1) || (actual == ok2), "jso_encode_map() failed to encode multi-element map!"); + jso_cleanup_map(multi_map); + + //jso_encode_list() --- Empty list + var empty_list; + empty_list = jso_new_list(); + expected = "[]"; + actual = jso_encode_list(empty_list); + assert_equal(expected, actual, "jso_encode_list() failed to encode empty list!"); + jso_cleanup_list(empty_list); + + //jso_encode_list() --- One-element nested list + var one_list; + one_list = jso_new_list(); + jso_list_add_submap(one_list, jso_new_map()); + expected = "[{}]"; + actual = jso_encode_list(one_list); + assert_equal(expected, actual, "jso_encode_list() failed to encode one-element nested list!"); + jso_cleanup_list(one_list); + + //jso_encode_list() --- Multi-element nested list + var multi_list, submap, sublist; + multi_list = jso_new_list(); + submap = jso_new_map(); + jso_map_add_string(submap, "1", "one"); + jso_list_add_submap(multi_list, submap); + jso_list_add_integer(multi_list, 2); + sublist = jso_new_list(); + jso_list_add_string(sublist, "three"); + jso_list_add_boolean(sublist, true); + jso_list_add_sublist(multi_list, sublist); + expected = '[{"1":"one"},2,["three",true]]'; + actual = jso_encode_list(multi_list); + assert_equal(expected, actual, "jso_encode_list() failed to encode one-element nested list!"); + jso_cleanup_list(multi_list); +} + +#define _test_jso_decode +{ + /** + _test_jso_decode(): Test core _jso_decode_*() functions. + The formatting is intentionally erratic here to simulate actual formatting deviations. + JSOnion version: 1.0.0d + */ + var json, expected, actual, expected_structure, actual_structure; + + ////Primitives + + //_jso_decode_string(): Empty string + json = '""'; + expected = __jso_gmt_tuple("", 3); + actual = _jso_decode_string(json, 1); + assert_equal(expected, actual, "_jso_decode_string() failed to decode an empty string!"); + + //_jso_decode_string(): Small string + json = '"key" '; + expected = __jso_gmt_tuple("key", 6); + actual = _jso_decode_string(json, 1); + assert_equal(expected, actual, "_jso_decode_string() failed to decode a small string!"); + + //_jso_decode_string(): Simple string + json = ' "The quick brown fox jumps over the lazy dog." '; + expected = __jso_gmt_tuple("The quick brown fox jumps over the lazy dog.", 49); + actual = _jso_decode_string(json, 1); + assert_equal(expected, actual, "_jso_decode_string() failed to decode a simple string!"); + + //_jso_decode_string(): Escape characters + json = ' "\"\\\b\f\n\r\t\u003A"'; + expected = __jso_gmt_tuple('"\' + chr(8) + chr(12) + chr(10) + chr(13) + chr(9) + chr($003a), 24); + actual = _jso_decode_string(json, 1); + assert_equal(expected, actual, "_jso_decode_string() failed to decode a string with escape characters!"); + + //_jso_decode_string(): Mixed characters + json = ' "\"\\\bWaahoo\f\n\r\tnegg\u003a"'; + expected = __jso_gmt_tuple('"\' + chr(8) + "Waahoo" + chr(12) + chr(10) + chr(13) + chr(9) + "negg" + chr($003a), 34); + actual = _jso_decode_string(json, 1); + assert_equal(expected, actual, "_jso_decode_string() failed to decode a string with mixed characters!"); + + //_jso_decode_boolean(): True + json = 'true'; + expected = __jso_gmt_tuple(true, 5); + actual = _jso_decode_boolean(json, 1); + assert_equal(expected, actual, "_jso_decode_boolean() failed to decode true!"); + + //_jso_decode_boolean(): False + json = ' false '; + expected = __jso_gmt_tuple(false, 8); + actual = _jso_decode_boolean(json, 1); + assert_equal(expected, actual, "_jso_decode_boolean() failed to decode false!"); + + //_jso_decode_real(): Zero + json = '0'; + expected = __jso_gmt_tuple(0, 2); + actual = _jso_decode_real(json, 1); + assert_equal(expected, actual, "_jso_decode_real() failed to decode standard zero!"); + + //_jso_decode_real(): Signed zero + json = ' +0 '; + expected = __jso_gmt_tuple(0, 5); + actual = _jso_decode_real(json, 1); + assert_equal(expected, actual, "_jso_decode_real() failed to decode signed zero!"); + + //_jso_decode_real(): Signed zero with decimal digits + json = ' -0.000'; + expected = __jso_gmt_tuple(0, 8); + actual = _jso_decode_real(json, 1); + assert_equal(expected, actual, "_jso_decode_real() failed to decode signed zero with decimal digits!"); + + //_jso_decode_real(): Positive real + json = '3.14159'; + expected = __jso_gmt_tuple(3.14159, 8); + actual = _jso_decode_real(json, 1); + assert_equal(expected, actual, "_jso_decode_real() failed to decode positive real number!"); + + //_jso_decode_real(): Negative real + json = ' -2.71828'; + expected = __jso_gmt_tuple(-2.71828, 10); + actual = _jso_decode_real(json, 1); + assert_equal(expected, actual, "_jso_decode_real() failed to decode negative real number!"); + + //_jso_decode_real(): Positive real with positive exponent + json = ' 3.14159e2'; + expected = __jso_gmt_tuple(3.14159*100, 11); + actual = _jso_decode_real(json, 1); + assert_equal(expected, actual, "_jso_decode_real() failed to decode positive real number with positive exponent!"); + + //_jso_decode_real(): Negative real with positive exponent + json = ' -2.71828E2'; + expected = __jso_gmt_tuple(-2.71828*100, 12); + actual = _jso_decode_real(json, 1); + assert_equal(expected, actual, "_jso_decode_real() failed to decode negative real number with positive exponent!"); + + //_jso_decode_real(): Positive real with negative exponent + json = ' 314.159e-2'; + expected = __jso_gmt_tuple(3.14159, 12); + actual = _jso_decode_real(json, 1); + assert_equal(expected, actual, "_jso_decode_real() failed to decode positive real number with negative exponent!"); + + //_jso_decode_real(): Negative real with negative exponent + json = ' -271.828E-2'; + expected = __jso_gmt_tuple(-2.71828, 13); + actual = _jso_decode_real(json, 1); + assert_equal(expected, actual, "_jso_decode_real() failed to decode negative real number with negative exponent!"); + + //_jso_decode_real(): Positive integer + json = ' +1729'; + expected = __jso_gmt_tuple(1729, 7); + actual = _jso_decode_real(json, 1); + assert_equal(expected, actual, "_jso_decode_real() failed to decode positive integer!"); + + //_jso_decode_real(): Negative integer + json = '-583'; + expected = __jso_gmt_tuple(-583, 5); + actual = _jso_decode_real(json, 1); + assert_equal(expected, actual, "_jso_decode_real() failed to decode negative integer!"); + + //_jso_decode_integer(): Zero + json = ' 0 '; + expected = __jso_gmt_tuple(0, 3); + actual = _jso_decode_integer(json, 1); + assert_equal(expected, actual, "_jso_decode_integer() failed to decode zero!"); + + //_jso_decode_integer(): Positive integer + json = ' 1729 '; + expected = __jso_gmt_tuple(1729, 6); + actual = _jso_decode_integer(json, 1); + assert_equal(expected, actual, "_jso_decode_integer() failed to decode positive integer!"); + + //_jso_decode_integer(): Negative integer + json = ' -583'; + expected = __jso_gmt_tuple(-583, 8); + actual = _jso_decode_integer(json, 1); + assert_equal(expected, actual, "_jso_decode_integer() failed to decode negative integer!"); + + + ////Data structures + + //_jso_decode_map(): Empty map #1 + json = '{}'; + expected_structure = jso_new_map(); + expected = __jso_gmt_tuple(expected_structure, 3); + actual = _jso_decode_map(json, 1); + actual_structure = __jso_gmt_elem(actual, 0); + assert_equal(__jso_gmt_elem(expected, 1), __jso_gmt_elem(actual, 1), "_jso_decode_map() didn't stop at the right place! (#1)"); + assert_equal(actual_structure, __jso_gmt_elem(actual, 0), "_jso_decode_map() didn't include the right prefix! (#1)"); + assert_true(jso_compare_maps(expected_structure, actual_structure), "_jso_decode_map() failed to decode an empty map! (#1)"); + jso_cleanup_map(expected_structure); + jso_cleanup_map(actual_structure); + + //_jso_decode_map(): Empty map #2 + json = ' { } '; + expected_structure = jso_new_map(); + expected = __jso_gmt_tuple(expected_structure, 7); + actual = _jso_decode_map(json, 1); + actual_structure = __jso_gmt_elem(actual, 0); + assert_equal(__jso_gmt_elem(expected, 1), __jso_gmt_elem(actual, 1), "_jso_decode_map() didn't stop at the right place! (#2)"); + assert_equal(actual_structure, __jso_gmt_elem(actual, 0), "_jso_decode_map() didn't include the right prefix! (#2)"); + assert_true(jso_compare_maps(expected_structure, actual_structure), "_jso_decode_map() failed to decode an empty map! (#2)"); + jso_cleanup_map(expected_structure); + jso_cleanup_map(actual_structure); + + //_jso_decode_map(): One-entry map + json = ' {"key": "value"} '; + expected_structure = jso_new_map(); + expected = __jso_gmt_tuple(expected_structure, 18); + jso_map_add_string(expected_structure, "key", "value"); + actual = _jso_decode_map(json, 1); + actual_structure = __jso_gmt_elem(actual, 0); + assert_equal(__jso_gmt_elem(expected, 1), __jso_gmt_elem(actual, 1), "_jso_decode_map() didn't stop at the right place! (one-entry map)"); + assert_equal(actual_structure, __jso_gmt_elem(actual, 0), "_jso_decode_map() didn't include the right prefix! (one-entry map)"); + assert_true(jso_compare_maps(expected_structure, actual_structure), "_jso_decode_map() failed to decode a one-entry map!"); + jso_cleanup_map(expected_structure); + jso_cleanup_map(actual_structure); + + //_jso_decode_map(): Multi-entry map + json = ' {"key" : "value", "pi":3.14, "bool" : true} '; + expected_structure = jso_new_map(); + expected = __jso_gmt_tuple(expected_structure, 48); + jso_map_add_string(expected_structure, "key", "value"); + jso_map_add_real(expected_structure, "pi", 3.14); + jso_map_add_boolean(expected_structure, "bool", true); + actual = _jso_decode_map(json, 1); + actual_structure = __jso_gmt_elem(actual, 0); + assert_equal(__jso_gmt_elem(expected, 1), __jso_gmt_elem(actual, 1), "_jso_decode_map() didn't stop at the right place! (multi-entry map)"); + assert_equal(actual_structure, __jso_gmt_elem(actual, 0), "_jso_decode_map() didn't include the right prefix! (multi-entry map)"); + assert_true(jso_compare_maps(expected_structure, actual_structure), "_jso_decode_map() failed to decode a multi-entry map!"); + jso_cleanup_map(expected_structure); + jso_cleanup_map(actual_structure); + + //_jso_decode_map(): Nested maps + var submap; + json = '{ "waahoo" : { "woohah" : 3 } , "woohah" : 4 }'; + expected_structure = jso_new_map(); + expected = __jso_gmt_tuple(expected_structure, 47); + jso_map_add_integer(expected_structure, "woohah", 4); + submap = jso_new_map(); + jso_map_add_integer(submap, "woohah", 3); + jso_map_add_submap(expected_structure, "waahoo", submap); + actual = _jso_decode_map(json, 1); + actual_structure = __jso_gmt_elem(actual, 0); + assert_equal(__jso_gmt_elem(expected, 1), __jso_gmt_elem(actual, 1), "_jso_decode_map() didn't stop at the right place! (nested map)"); + assert_equal(actual_structure, __jso_gmt_elem(actual, 0), "_jso_decode_map() didn't include the right prefix! (nested map)"); + assert_true(jso_compare_maps(expected_structure, actual_structure), "_jso_decode_map() failed to decode a nested map!"); + jso_cleanup_map(expected_structure); + jso_cleanup_map(actual_structure); + + //_jso_decode_map(): Map with nested lists + var sublist, subsublist; + json = '{ "waahoo" : [ "woohah", [true] ] , "woohah" : 4 }'; + expected_structure = jso_new_map(); + expected = __jso_gmt_tuple(expected_structure, 51); + jso_map_add_real(expected_structure, "woohah", 4); + sublist = jso_new_list(); + jso_list_add_string(sublist, "woohah"); + subsublist = jso_new_list(); + jso_list_add_boolean(subsublist, true); + jso_list_add_sublist(sublist, subsublist); + jso_map_add_sublist(expected_structure, "waahoo", sublist); + actual = _jso_decode_map(json, 1); + actual_structure = __jso_gmt_elem(actual, 0); + assert_equal(__jso_gmt_elem(expected, 1), __jso_gmt_elem(actual, 1), "_jso_decode_map() didn't stop at the right place! (map with nested lists)"); + assert_equal(actual_structure, __jso_gmt_elem(actual, 0), "_jso_decode_map() didn't include the right prefix! (map with nested lists)"); + assert_true(jso_compare_maps(expected_structure, actual_structure), "_jso_decode_map() failed to decode a map with nested lists!"); + jso_cleanup_map(expected_structure); + jso_cleanup_map(actual_structure); + + //_jso_decode_map(): Mix-up nested map + var sublist; + json = ' { "waahoo" : [{}, "a", 1] }'; + expected_structure = jso_new_map(); + expected = __jso_gmt_tuple(expected_structure, 30); + sublist = jso_new_list(); + jso_list_add_submap(sublist, jso_new_map()); + jso_list_add_string(sublist, "a"); + jso_list_add_real(sublist, 1); + jso_map_add_sublist(expected_structure, "waahoo", sublist); + actual = _jso_decode_map(json, 1); + actual_structure = __jso_gmt_elem(actual, 0); + assert_equal(__jso_gmt_elem(expected, 1), __jso_gmt_elem(actual, 1), "_jso_decode_map() didn't stop at the right place! (mix-up nested map)"); + assert_equal(actual_structure, __jso_gmt_elem(actual, 0), "_jso_decode_map() didn't include the right prefix! (mix-up nested map)"); + assert_true(jso_compare_maps(expected_structure, actual_structure), "_jso_decode_map() failed to decode a mix-up nested map!"); + jso_cleanup_map(expected_structure); + jso_cleanup_map(actual_structure); + + //_jso_decode_list(): Empty list #1 + json = '[]'; + expected_structure = jso_new_list(); + expected = __jso_gmt_tuple(expected_structure, 3); + actual = _jso_decode_list(json, 1); + actual_structure = __jso_gmt_elem(actual, 0); + assert_equal(__jso_gmt_elem(expected, 1), __jso_gmt_elem(actual, 1), "_jso_decode_list() didn't stop at the right place! (#1)"); + assert_equal(actual_structure, __jso_gmt_elem(actual, 0), "_jso_decode_list() didn't include the right prefix! (#1)"); + assert_true(jso_compare_lists(expected_structure, actual_structure), "_jso_decode_list() failed to decode an empty list! (#1)"); + jso_cleanup_list(expected_structure); + jso_cleanup_list(actual_structure) + + //_jso_decode_list(): Empty list #2 + json = ' [ ] '; + expected_structure = jso_new_list(); + expected = __jso_gmt_tuple(expected_structure, 6); + actual = _jso_decode_list(json, 1); + actual_structure = __jso_gmt_elem(actual, 0); + assert_equal(__jso_gmt_elem(expected, 1), __jso_gmt_elem(actual, 1), "_jso_decode_list() didn't stop at the right place! (#2)"); + assert_equal(actual_structure, __jso_gmt_elem(actual, 0), "_jso_decode_list() didn't include the right prefix! (#2)"); + assert_true(jso_compare_lists(expected_structure, actual_structure), "_jso_decode_list() failed to decode an empty list! (#2)"); + jso_cleanup_list(expected_structure); + jso_cleanup_list(actual_structure); + + //_jso_decode_list(): One-entry list + json = '[3]'; + expected_structure = jso_new_list(); + expected = __jso_gmt_tuple(expected_structure, 4); + jso_list_add_integer(expected_structure, 3); + actual = _jso_decode_list(json, 1); + actual_structure = __jso_gmt_elem(actual, 0); + assert_equal(__jso_gmt_elem(expected, 1), __jso_gmt_elem(actual, 1), "_jso_decode_list() didn't stop at the right place! (one-entry list)"); + assert_equal(actual_structure, __jso_gmt_elem(actual, 0), "_jso_decode_list() didn't include the right prefix! (one-entry list)"); + assert_true(jso_compare_lists(expected_structure, actual_structure), "_jso_decode_list() failed to decode a one-entry list!"); + jso_cleanup_list(expected_structure); + jso_cleanup_list(actual_structure); + + //_jso_decode_list(): Multi-entry list + json = ' [4,"multi-entry",true]'; + expected_structure = jso_new_list(); + expected = __jso_gmt_tuple(expected_structure, 24); + jso_list_add_real(expected_structure, 4); + jso_list_add_string(expected_structure, "multi-entry"); + jso_list_add_boolean(expected_structure, true); + actual = _jso_decode_list(json, 1); + actual_structure = __jso_gmt_elem(actual, 0); + assert_equal(__jso_gmt_elem(expected, 1), __jso_gmt_elem(actual, 1), "_jso_decode_list() didn't stop at the right place! (multi-entry list)"); + assert_equal(actual_structure, __jso_gmt_elem(actual, 0), "_jso_decode_list() didn't include the right prefix! (multi-entry list)"); + assert_true(jso_compare_lists(expected_structure, actual_structure), "_jso_decode_list() failed to decode a multi-entry list!"); + jso_cleanup_list(expected_structure); + jso_cleanup_list(actual_structure); + + //_jso_decode_list(): Nested list + var sublist; + json = ' [ [], 3, false, ["waahoo"]]'; + expected_structure = jso_new_list(); + expected = __jso_gmt_tuple(expected_structure, 29); + jso_list_add_sublist(expected_structure, jso_new_list()); + jso_list_add_integer(expected_structure, 3); + jso_list_add_boolean(expected_structure, false); + sublist = jso_new_list(); + jso_list_add_string(sublist, "waahoo"); + jso_list_add_sublist(expected_structure, sublist); + actual = _jso_decode_list(json, 1); + actual_structure = __jso_gmt_elem(actual, 0); + assert_equal(__jso_gmt_elem(expected, 1), __jso_gmt_elem(actual, 1), "_jso_decode_list() didn't stop at the right place! (nested list)"); + assert_equal(actual_structure, __jso_gmt_elem(actual, 0), "_jso_decode_list() didn't include the right prefix! (nested list)"); + assert_true(jso_compare_lists(expected_structure, actual_structure), "_jso_decode_list() failed to decode a nested list!"); + jso_cleanup_list(expected_structure); + jso_cleanup_list(actual_structure); + + //_jso_decode_list(): List with nested maps + var submap; + json = ' [3, false, { "waahoo":"woo"}]'; + expected_structure = jso_new_list(); + expected = __jso_gmt_tuple(expected_structure, 31); + jso_list_add_integer(expected_structure, 3); + jso_list_add_boolean(expected_structure, false); + submap = jso_new_map(); + jso_map_add_string(submap, "waahoo", "woo"); + jso_list_add_submap(expected_structure, submap); + actual = _jso_decode_list(json, 1); + actual_structure = __jso_gmt_elem(actual, 0); + assert_equal(__jso_gmt_elem(expected, 1), __jso_gmt_elem(actual, 1), "_jso_decode_list() didn't stop at the right place! (list with nested maps)"); + assert_equal(actual_structure, __jso_gmt_elem(actual, 0), "_jso_decode_list() didn't include the right prefix! (list with nested maps)"); + assert_true(jso_compare_lists(expected_structure, actual_structure), "_jso_decode_list() failed to decode a list with nested maps!"); + jso_cleanup_list(expected_structure); + jso_cleanup_list(actual_structure); + + //_jso_decode_list(): Mix-up nested list + var submap; + json = '[{}, {"a":[]}]'; + expected_structure = jso_new_list(); + expected = __jso_gmt_tuple(expected_structure, 15); + jso_list_add_submap(expected_structure, jso_new_map()); + submap = jso_new_map(); + jso_map_add_sublist(submap, "a", jso_new_list()); + jso_list_add_submap(expected_structure, submap); + actual = _jso_decode_list(json, 1); + actual_structure = __jso_gmt_elem(actual, 0); + assert_equal(__jso_gmt_elem(expected, 1), __jso_gmt_elem(actual, 1), "_jso_decode_list() didn't stop at the right place! (mix-up nested list)"); + assert_equal(actual_structure, __jso_gmt_elem(actual, 0), "_jso_decode_list() didn't include the right prefix! (mix-up nested list)"); + assert_true(jso_compare_lists(expected_structure, actual_structure), "_jso_decode_list() failed to decode a mix-up nested list!"); + jso_cleanup_list(expected_structure); + jso_cleanup_list(actual_structure); +} + +#define _test_jso_compare +{ + /** + _test_jso_compare(): Test basic jso_compare_*() functions. + JSOnion version: 1.0.0d + */ + var a, b; + + //jso_compare_maps(): Empty maps should equal each other + a = jso_new_map(); + b = jso_new_map(); + assert_true(jso_compare_maps(a, b), "Empty maps should equal each other. (#1)"); + assert_true(jso_compare_maps(b, a), "Empty maps should equal each other. (#2)"); + jso_cleanup_map(a); + jso_cleanup_map(b); + + //jso_compare_maps(): An empty map should not equal a filled map + a = jso_new_map(); + b = jso_new_map(); + jso_map_add_string(b, "junk", "info"); + jso_map_add_integer(b, "taxi", 1729); + assert_false(jso_compare_maps(a, b), "An empty map should not equal a filled map. (#1)"); + assert_false(jso_compare_maps(b, a), "An empty map should not equal a filled map. (#2)"); + jso_cleanup_map(a); + jso_cleanup_map(b); + + //jso_compare_maps(): Maps with same content entered in different orders should equal each other + a = jso_new_map(); + b = jso_new_map(); + jso_map_add_real(a, "A", 1); + jso_map_add_real(a, "B", 2); + jso_map_add_real(a, "C", 3); + jso_map_add_real(b, "C", 3); + jso_map_add_real(b, "A", 1); + jso_map_add_real(b, "B", 2); + assert_true(jso_compare_maps(a, b), "Maps with same content entered in different orders should equal each other. (#1)"); + assert_true(jso_compare_maps(b, a), "Maps with same content entered in different orders should equal each other. (#2)"); + jso_cleanup_map(a); + jso_cleanup_map(b); + + //jso_compare_maps(): Maps with different keys should not equal each other + a = jso_new_map(); + b = jso_new_map(); + jso_map_add_real(a, "A", 1); + jso_map_add_real(a, "B", 2); + jso_map_add_real(a, "C", 3); + jso_map_add_real(b, "D", 3); + jso_map_add_real(b, "A", 1); + jso_map_add_real(b, "B", 2); + assert_false(jso_compare_maps(a, b), "Maps with different keys should not equal each other. (#1)"); + assert_false(jso_compare_maps(b, a), "Maps with different keys should not equal each other. (#2)"); + jso_cleanup_map(a); + jso_cleanup_map(b); + + //jso_compare_maps(): Maps with different values should not equal each other + a = jso_new_map(); + b = jso_new_map(); + jso_map_add_real(a, "A", 5); + jso_map_add_real(a, "B", 6); + jso_map_add_real(a, "C", 9); + jso_map_add_real(b, "A", 5); + jso_map_add_real(b, "B", 6); + jso_map_add_real(b, "C", 8); + assert_false(jso_compare_maps(a, b), "Maps with different values should not equal each other. (#1)"); + assert_false(jso_compare_maps(b, a), "Maps with different values should not equal each other. (#2)"); + jso_cleanup_map(a); + jso_cleanup_map(b); + + //jso_compare_maps(): Maps with corresponding values of different types should not equal each other, and should not crash. + a = jso_new_map(); + b = jso_new_map(); + jso_map_add_real(a, "A", 5); + jso_map_add_real(a, "B", 6); + jso_map_add_real(a, "C", 8); + jso_map_add_real(b, "A", 5); + jso_map_add_string(b, "B", "six"); + jso_map_add_real(b, "C", 8); + assert_false(jso_compare_maps(a, b), "Maps with corresponding values of different types should not equal each other, and should not crash. (#1)"); + assert_false(jso_compare_maps(b, a), "Maps with corresponding values of different types should not equal each other, and should not crash. (#2)"); + jso_cleanup_map(a); + jso_cleanup_map(b); + + //jso_compare_lists(): Empty lists should equal each other + a = jso_new_list(); + b = jso_new_list(); + assert_true(jso_compare_lists(a, b), "Empty lists should equal each other. (#1)"); + assert_true(jso_compare_lists(b, a), "Empty lists should equal each other. (#2)"); + jso_cleanup_list(a); + jso_cleanup_list(b); + + //jso_compare_lists(): An empty list should not equal a filled list + a = jso_new_list(); + b = jso_new_list(); + jso_list_add_string(b, "junk"); + jso_list_add_integer(b, 1729); + assert_false(jso_compare_lists(a, b), "An empty list should not equal a filled list. (#1)"); + assert_false(jso_compare_lists(b, a), "An empty list should not equal a filled list. (#2)"); + jso_cleanup_list(a); + jso_cleanup_list(b); + + //jso_compare_lists(): Lists with same content entered in different orders should not equal each other + a = jso_new_list(); + b = jso_new_list(); + jso_list_add_real(a, 1); + jso_list_add_real(a, 2); + jso_list_add_real(a, 3); + jso_list_add_real(b, 3); + jso_list_add_real(b, 1); + jso_list_add_real(b, 2); + assert_false(jso_compare_lists(a, b), "Lists with same content entered in different orders should not equal each other. (#1)"); + assert_false(jso_compare_lists(b, a), "Lists with same content entered in different orders should not equal each other. (#2)"); + jso_cleanup_list(a); + jso_cleanup_list(b); + + //jso_compare_lists(): Lists with corresponding entries of different types should not equal each other, should also not crash. + a = jso_new_list(); + b = jso_new_list(); + jso_list_add_real(a, 1); + jso_list_add_real(a, 2); + jso_list_add_real(a, 3); + jso_list_add_real(b, 1); + jso_list_add_string(b, "two"); + jso_list_add_real(b, 3); + assert_false(jso_compare_lists(a, b), "Lists with corresponding entries of different types should not equal each other, should also not crash. (#1)"); + assert_false(jso_compare_lists(b, a), "Lists with corresponding entries of different types should not equal each other, should also not crash. (#2)"); + jso_cleanup_list(a); + jso_cleanup_list(b); +} + +#define _test_jso_lookup +{ + /** + _test_jso_lookup(): Test core jso_*_lookup() and jso_*_check() functions for nested structures. + JSOnion version: 1.0.0d + */ + var json, structure, expected, actual; + + //jso_map_check(): Single argument --- exists + json = '{ "one" : 1, "two" : 2, "three" : 3 }'; + structure = jso_decode_map(json); + assert_true(jso_map_check(structure, "one"), "jso_map_check() failed to find existing entry! (single argument)"); + jso_cleanup_map(structure); + + //jso_map_lookup(): Single argument --- exists + json = '{ "one" : 1, "two" : 2, "three" : 3 }'; + structure = jso_decode_map(json); + expected = 1; + actual = jso_map_lookup(structure, "one") + assert_equal(expected, actual, "jso_map_lookup() found the wrong entry! (single argument)"); + jso_cleanup_map(structure); + + //jso_map_lookup_type(): Single argument --- exists + json = '{ "one" : -1, "two" : true, "three" : "trap" }'; + structure = jso_decode_map(json); + expected = jso_type_real; + actual = jso_map_lookup_type(structure, "one") + assert_equal(expected, actual, "jso_map_lookup_type() found the wrong type! (single argument)"); + jso_cleanup_map(structure); + + //jso_map_check(): Single argument --- doesn't exist + json = '{ "one" : 1, "two" : 2, "three" : 3 }'; + structure = jso_decode_map(json); + assert_false(jso_map_check(structure, "four"), "jso_map_check() found an inexistent entry! (single argument)"); + jso_cleanup_map(structure); + + //jso_map_check(): Single argument --- doesn't exist + json = '{ "one" : 1, "two" : 2, "three" : 3, "four" : { "A":true, "B":false } }'; + structure = jso_decode_map(json); + assert_false(jso_map_check(structure, "A"), "jso_map_check() found an inexistent entry! (single argument, nested)"); + jso_cleanup_map(structure); + + //jso_map_check(): Multiple arguments (recurse) --- exists + json = '{ "one" : 1, "two" : 2, "three" : 3, "four" : { "A":true, "B":false } }'; + structure = jso_decode_map(json); + assert_true(jso_map_check(structure, "four", "A"), "jso_map_check() failed to find existing entry! (multiple arguments)"); + jso_cleanup_map(structure); + + //jso_map_lookup(): Multiple arguments (recurse) --- exists + json = '{ "one" : 1, "two" : 2, "three" : 3, "four" : { "A":true, "B":false } }'; + structure = jso_decode_map(json); + expected = true; + actual = jso_map_lookup(structure, "four", "A"); + assert_equal(expected, actual, "jso_map_lookup() found the wrong entry! (multiple arguments)"); + jso_cleanup_map(structure); + + //jso_map_lookup_type(): Multiple arguments (recurse) --- exists + json = '{ "one" : 1, "two" : 2, "three" : 3, "four" : { "A":true, "B":"trap" } }'; + structure = jso_decode_map(json); + expected = jso_type_boolean; + actual = jso_map_lookup_type(structure, "four", "A"); + assert_equal(expected, actual, "jso_map_lookup_type() found the wrong type! (multiple arguments)"); + jso_cleanup_map(structure); + + //jso_map_check(): Multiple arguments (recurse) --- doesn't exist + json = '{ "one" : 1, "two" : 2, "three" : 3, "four" : { "A":true, "B":false } }'; + structure = jso_decode_map(json); + assert_false(jso_map_check(structure, "four", "C"), "jso_map_check() found an inexistent entry! (multiple arguments, 1)"); + jso_cleanup_map(structure); + + //jso_map_check(): Multiple arguments (recurse) --- doesn't exist + json = '{ "one" : 1, "two" : 2, "three" : 3, "four" : { "A":true, "B":false } }'; + structure = jso_decode_map(json); + assert_false(jso_map_check(structure, "three", ""), "jso_map_check() found an inexistent entry! (multiple arguments, 2)"); + jso_cleanup_map(structure); + + //jso_map_check(): Multiple arguments with nested list --- exists + json = '{ "one" : 1, "two" : 2, "three" : 3, "four" : [ "A", true, ["B", false] ] }'; + structure = jso_decode_map(json); + assert_true(jso_map_check(structure, "four", 2, 1), "jso_map_check() failed to find an existing entry! (multiple arguments, nested)"); + jso_cleanup_map(structure); + + //jso_map_lookup(): Multiple arguments with nested list --- exists + json = '{ "one" : 1, "two" : 2, "three" : 3, "four" : [ "A", true, ["B", false] ] }'; + structure = jso_decode_map(json); + expected = false; + actual = jso_map_lookup(structure, "four", 2, 1); + assert_equal(expected, actual, "jso_map_lookup() failed to find an existing entry! (multiple arguments, nested)"); + jso_cleanup_map(structure); + + //jso_map_lookup_type(): Multiple arguments with nested list --- exists + json = '{ "one" : 1, "two" : 2, "three" : 3, "four" : [ "A", true, [false, "false"] ] }'; + structure = jso_decode_map(json); + expected = jso_type_string; + actual = jso_map_lookup_type(structure, "four", 2, 1); + assert_equal(expected, actual, "jso_map_lookup_type() found the wrong type! (multiple arguments, nested)"); + jso_cleanup_map(structure); + + //jso_map_check(): Multiple arguments with nested list --- wrong type + json = '{ "one" : 1, "two" : 2, "three" : 3, "four" : [ "A", true, ["B", false] ] }'; + structure = jso_decode_map(json); + assert_false(jso_map_check(structure, "four", "A", 1), "jso_map_check() found an inexistent entry! (multiple arguments, nested, wrong type)"); + jso_cleanup_map(structure); + + //jso_map_check(): Multiple arguments with nested list --- index overflow + json = '{ "one" : 1, "two" : 2, "three" : 3, "four" : [ "A", true, ["B", false] ] }'; + structure = jso_decode_map(json); + assert_false(jso_map_check(structure, "four", 2, 3), "jso_map_check() found an inexistent entry! (multiple arguments, nested, index overflow)"); + jso_cleanup_map(structure); + + //jso_list_check(): Single argument --- exists + json = '["one", 2, "three", true, 5]'; + structure = jso_decode_list(json); + assert_true(jso_list_check(structure, 2), "jso_list_check() failed to find an existing index! (single argument)"); + jso_cleanup_list(structure); + + //jso_list_lookup(): Single argument --- exists + json = '["one", 2, "three", true, 5]'; + structure = jso_decode_list(json); + expected = "three"; + actual = jso_list_lookup(structure, 2); + assert_equal(expected, actual, "jso_list_lookup() found the wrong index! (single argument)"); + jso_cleanup_list(structure); + + //jso_list_lookup_type(): Single argument --- exists + json = '["one", 2, "three", true, 5]'; + structure = jso_decode_list(json); + expected = jso_type_string; + actual = jso_list_lookup_type(structure, 2); + assert_equal(expected, actual, "jso_list_lookup_type() found the wrong type! (single argument)"); + jso_cleanup_list(structure); + + //jso_list_check(): Single argument --- doesn't exist + json = '["one", 2, "three", true, 5]'; + structure = jso_decode_list(json); + assert_false(jso_list_check(structure, 5), "jso_list_check() found an inexistent index! (single argument)"); + jso_cleanup_list(structure); + + //jso_list_check(): Multiple arguments (recurse) --- exists + json = '["one", 2, ["three", 3], true, 5]'; + structure = jso_decode_list(json); + assert_true(jso_list_check(structure, 2, 1), "jso_list_check() failed to find an existing index! (multiple arguments)"); + jso_cleanup_list(structure); + + //jso_list_lookup(): Multiple arguments (recurse) --- exists + json = '["one", 2, ["three", 3], true, 5]'; + structure = jso_decode_list(json); + expected = 3; + actual = jso_list_lookup(structure, 2, 1); + assert_equal(expected, actual, "jso_list_lookup() failed to find an existing index! (multiple arguments)"); + jso_cleanup_list(structure); + + //jso_list_lookup_type(): Multiple arguments (recurse) --- exists + json = '["one", 2, ["three", 3], true, 5]'; + structure = jso_decode_list(json); + expected = jso_type_real; + actual = jso_list_lookup_type(structure, 2, 1); + assert_equal(expected, actual, "jso_list_lookup_type() found the wrong type! (multiple arguments)"); + jso_cleanup_list(structure); + + //jso_list_check(): Multiple arguments (recurse) --- doesn't exist, inner index overflow + json = '["one", 2, ["three", 3], true, 5]'; + structure = jso_decode_list(json); + assert_false(jso_list_check(structure, 2, 2), "jso_list_check() found an inexistent index! (multiple arguments, inner index overflow)"); + jso_cleanup_list(structure); + + //jso_list_check(): Multiple arguments (recurse) --- doesn't exist, trying to index single entry + json = '["one", 2, ["three", 3], true, 5]'; + structure = jso_decode_list(json); + assert_false(jso_list_check(structure, 1, 0), "jso_list_check() found an inexistent index! (multiple arguments, indexing single entry)"); + jso_cleanup_list(structure); + + //jso_list_check(): Multiple arguments with nested map --- exists + json = '["one", 2, {"three":3}, true, 5]'; + structure = jso_decode_list(json); + assert_true(jso_list_check(structure, 2, "three"), "jso_list_check() failed to find an existing entry! (multiple arguments, nested map)"); + jso_cleanup_list(structure); + + //jso_list_lookup(): Multiple arguments with nested map --- exists + json = '["one", 2, {"three":3}, true, 5]'; + structure = jso_decode_list(json); + expected = 3; + actual = jso_list_lookup(structure, 2, "three"); + assert_equal(expected, actual, "jso_list_lookup() failed to find an existing entry! (multiple arguments, nested map)"); + jso_cleanup_list(structure); + + //jso_list_lookup_type(): Multiple arguments with nested map --- exists + json = '["one", 2, {"three":false}, true, 5]'; + structure = jso_decode_list(json); + expected = jso_type_boolean; + actual = jso_list_lookup_type(structure, 2, "three"); + assert_equal(expected, actual, "jso_list_lookup_type() found the wrong type! (multiple arguments, nested map)"); + jso_cleanup_list(structure); + + //jso_list_exists(): Multiple arguments with nested map --- doesn't exist, key on single entry + json = '["one", 2, {"three":3}, true, 5]'; + structure = jso_decode_list(json); + assert_false(jso_list_check(structure, 1, ""), "jso_list_check() found an inexistent entry! (multiple arguments, nested map, key on single entry)"); + jso_cleanup_list(structure); + + //jso_list_exists(): Multiple arguments with nested map --- doesn't exist, bad key + json = '["one", 2, {"three":3}, true, 5]'; + structure = jso_decode_list(json); + assert_false(jso_list_check(structure, 2, ""), "jso_list_check() found an inexistent entry! (multiple arguments, nested map, bad key)"); + jso_cleanup_list(structure); +} + +#define _test_jso_bugs +{ + /** + _test_jso_bugs(): Test reported bugs. + JSOnion version: 1.0.0d + */ + var expected, actual; + + //jso_encode_map() --- One-element map + //Bug 1: Crash when encoding boolean-valued entries in maps, unknown variable jso_type_integer + var one_map; + one_map = jso_new_map(); + jso_map_add_boolean(one_map, "true", true); + expected = '{"true":true}'; + actual = jso_encode_map(one_map); + assert_equal(expected, actual, "jso_encode_map() failed to encode one-element map with boolean entry!"); + jso_cleanup_map(one_map); +} + diff --git a/samples/Game Maker Language/loadserverplugins.gml b/samples/Game Maker Language/loadserverplugins.gml new file mode 100644 index 00000000..26a26758 --- /dev/null +++ b/samples/Game Maker Language/loadserverplugins.gml @@ -0,0 +1,252 @@ +/* + Originally from /Source/gg2/Scripts/Plugins/loadserverplugins.gml in Gang Garrison 2 + + Copyright (C) 2008-2013 Faucet Software + http://www.ganggarrison.com + + 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 3 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, + see . + + Additional permission under GNU GPL version 3 section 7 + If you modify this Program, or any covered work, by linking or combining it with the Game Maker runtime library, + the 39dll library/extension, Hobbel's Download Manager DLL, or modified versions of these libraries, + the licensors of this Program grant you additional permission to convey the resulting work. +*/ + +// loads plugins from ganggarrison.com asked for by server +// argument0 - comma separated plugin list (pluginname@md5hash) +// returns true on success, false on failure +var list, hashList, text, i, pluginname, pluginhash, realhash, url, handle, filesize, progress, tempfile, tempdir, failed, lastContact, isCached; + +failed = false; +list = ds_list_create(); +lastContact = 0; +isCached = false; +isDebug = false; +hashList = ds_list_create(); + +// split plugin list string +list = split(argument0, ','); + +// Split hashes from plugin names +for (i = 0; i < ds_list_size(list); i += 1) +{ + text = ds_list_find_value(list, i); + pluginname = string_copy(text, 0, string_pos("@", text) - 1); + pluginhash = string_copy(text, string_pos("@", text) + 1, string_length(text) - string_pos("@", text)); + ds_list_replace(list, i, pluginname); + ds_list_add(hashList, pluginhash); +} + +// Check plugin names and check for duplicates +for (i = 0; i < ds_list_size(list); i += 1) +{ + pluginname = ds_list_find_value(list, i); + + // invalid plugin name + if (!checkpluginname(pluginname)) + { + show_message('Error loading server-sent plugins - invalid plugin name:#"' + pluginname + '"'); + return false; + } + // is duplicate + else if (ds_list_find_index(list, pluginname) != i) + { + show_message('Error loading server-sent plugins - duplicate plugin:#"' + pluginname + '"'); + return false; + } +} + +// Download plugins +for (i = 0; i < ds_list_size(list); i += 1) +{ + pluginname = ds_list_find_value(list, i); + pluginhash = ds_list_find_value(hashList, i); + isDebug = file_exists(working_directory + "\ServerPluginsDebug\" + pluginname + ".zip"); + isCached = file_exists(working_directory + "\ServerPluginsCache\" + pluginname + "@" + pluginhash); + tempfile = temp_directory + "\" + pluginname + ".zip.tmp"; + tempdir = temp_directory + "\" + pluginname + ".tmp"; + + // check to see if we have a local copy for debugging + if (isDebug) + { + file_copy(working_directory + "\ServerPluginsDebug\" + pluginname + ".zip", tempfile); + // show warning + if (global.isHost) + { + show_message( + "Warning: server-sent plugin '" + + pluginname + + "' is being loaded from ServerPluginsDebug. Make sure clients have the same version, else they may be unable to connect." + ); + } + else + { + show_message( + "Warning: server-sent plugin '" + + pluginname + + "' is being loaded from ServerPluginsDebug. Make sure the server has the same version, else you may be unable to connect." + ); + } + } + // otherwise, check if we have it cached + else if (isCached) + { + file_copy(working_directory + "\ServerPluginsCache\" + pluginname + "@" + pluginhash, tempfile); + } + // otherwise, download as usual + else + { + // construct the URL + // http://www.ganggarrison.com/plugins/$PLUGINNAME$@$PLUGINHASH$.zip) + url = PLUGIN_SOURCE + pluginname + "@" + pluginhash + ".zip"; + + // let's make the download handle + handle = httpGet(url, -1); + + // download it + while (!httpRequestStatus(handle)) { + // prevent game locking up + io_handle(); + + httpRequestStep(handle); + + if (!global.isHost) { + // send ping if we haven't contacted server in 20 seconds + // we need to do this to keep the connection open + if (current_time-lastContact > 20000) { + write_byte(global.serverSocket, PING); + socket_send(global.serverSocket); + lastContact = current_time; + } + } + + // draw progress bar since they may be waiting a while + filesize = httpRequestResponseBodySize(handle); + progress = httpRequestResponseBodyProgress(handle); + draw_background_ext(background_index[0], 0, 0, background_xscale[0], background_yscale[0], 0, c_white, 1); + draw_set_color(c_white); + draw_set_alpha(1); + draw_set_halign(fa_left); + draw_rectangle(50, 550, 300, 560, 2); + draw_text(50, 530, "Downloading server-sent plugin " + string(i + 1) + "/" + string(ds_list_size(list)) + ' - "' + pluginname + '"'); + if (filesize != -1) + draw_rectangle(50, 550, 50 + progress / filesize * 250, 560, 0); + screen_refresh(); + } + + // errored + if (httpRequestStatus(handle) == 2) + { + show_message('Error loading server-sent plugins - download failed for "' + pluginname + '":#' + httpRequestError(handle)); + failed = true; + break; + } + + // request failed + if (httpRequestStatusCode(handle) != 200) + { + show_message('Error loading server-sent plugins - download failed for "' + pluginname + '":#' + string(httpRequestStatusCode(handle)) + ' ' + httpRequestReasonPhrase(handle)); + failed = true; + break; + } + else + { + write_buffer_to_file(httpRequestResponseBody(handle), tempfile); + if (!file_exists(tempfile)) + { + show_message('Error loading server-sent plugins - download failed for "' + pluginname + '":# No such file?'); + failed = true; + break; + } + } + + httpRequestDestroy(handle); + } + + // check file integrity + realhash = GG2DLL_compute_MD5(tempfile); + if (realhash != pluginhash) + { + show_message('Error loading server-sent plugins - integrity check failed (MD5 hash mismatch) for:#"' + pluginname + '"'); + failed = true; + break; + } + + // don't try to cache debug plugins + if (!isDebug) + { + // add to cache if we don't already have it + if (!file_exists(working_directory + "\ServerPluginsCache\" + pluginname + "@" + pluginhash)) + { + // make sure directory exists + if (!directory_exists(working_directory + "\ServerPluginsCache")) + { + directory_create(working_directory + "\ServerPluginsCache"); + } + // store in cache + file_copy(tempfile, working_directory + "\ServerPluginsCache\" + pluginname + "@" + pluginhash); + } + } + + // let's get 7-zip to extract the files + extractzip(tempfile, tempdir); + + // if the directory doesn't exist, extracting presumably failed + if (!directory_exists(tempdir)) + { + show_message('Error loading server-sent plugins - extracting zip failed for:#"' + pluginname + '"'); + failed = true; + break; + } +} + +if (!failed) +{ + // Execute plugins + for (i = 0; i < ds_list_size(list); i += 1) + { + pluginname = ds_list_find_value(list, i); + tempdir = temp_directory + "\" + pluginname + ".tmp"; + + // Debugging facility, so we know *which* plugin caused compile/execute error + fp = file_text_open_write(working_directory + "\last_plugin.log"); + file_text_write_string(fp, pluginname); + file_text_close(fp); + + // packetID is (i), so make queues for it + ds_map_add(global.pluginPacketBuffers, i, ds_queue_create()); + ds_map_add(global.pluginPacketPlayers, i, ds_queue_create()); + + // Execute plugin + execute_file( + // the plugin's main gml file must be in the root of the zip + // it is called plugin.gml + tempdir + "\plugin.gml", + // the plugin needs to know where it is + // so the temporary directory is passed as first argument + tempdir, + // the plugin needs to know its packetID + // so it is passed as the second argument + i + ); + } +} + +// Delete last plugin log +file_delete(working_directory + "\last_plugin.log"); + +// Get rid of plugin list +ds_list_destroy(list); + +// Get rid of plugin hash list +ds_list_destroy(hashList); + +return !failed; diff --git a/samples/Game Maker Language/processClientCommands.gml b/samples/Game Maker Language/processClientCommands.gml new file mode 100644 index 00000000..6c241d4b --- /dev/null +++ b/samples/Game Maker Language/processClientCommands.gml @@ -0,0 +1,384 @@ +/* + Originally from /Source/gg2/Scripts/GameServer/processClientCommands.gml in Gang Garrison 2 + + Copyright (C) 2008-2013 Faucet Software + http://www.ganggarrison.com + + 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 3 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, + see . + + Additional permission under GNU GPL version 3 section 7 + If you modify this Program, or any covered work, by linking or combining it with the Game Maker runtime library, + the 39dll library/extension, Hobbel's Download Manager DLL, or modified versions of these libraries, + the licensors of this Program grant you additional permission to convey the resulting work. +*/ + +var player, playerId, commandLimitRemaining; + +player = argument0; +playerId = argument1; + +// To prevent players from flooding the server, limit the number of commands to process per step and player. +commandLimitRemaining = 10; + +with(player) { + if(!variable_local_exists("commandReceiveState")) { + // 0: waiting for command byte. + // 1: waiting for command data length (1 byte) + // 2: waiting for command data. + commandReceiveState = 0; + commandReceiveExpectedBytes = 1; + commandReceiveCommand = 0; + } +} + +while(commandLimitRemaining > 0) { + var socket; + socket = player.socket; + if(!tcp_receive(socket, player.commandReceiveExpectedBytes)) { + return 0; + } + + switch(player.commandReceiveState) + { + case 0: + player.commandReceiveCommand = read_ubyte(socket); + switch(commandBytes[player.commandReceiveCommand]) { + case commandBytesInvalidCommand: + // Invalid byte received. Wait for another command byte. + break; + + case commandBytesPrefixLength1: + player.commandReceiveState = 1; + player.commandReceiveExpectedBytes = 1; + break; + + case commandBytesPrefixLength2: + player.commandReceiveState = 3; + player.commandReceiveExpectedBytes = 2; + break; + + default: + player.commandReceiveState = 2; + player.commandReceiveExpectedBytes = commandBytes[player.commandReceiveCommand]; + break; + } + break; + + case 1: + player.commandReceiveState = 2; + player.commandReceiveExpectedBytes = read_ubyte(socket); + break; + + case 3: + player.commandReceiveState = 2; + player.commandReceiveExpectedBytes = read_ushort(socket); + break; + + case 2: + player.commandReceiveState = 0; + player.commandReceiveExpectedBytes = 1; + commandLimitRemaining -= 1; + + switch(player.commandReceiveCommand) + { + case PLAYER_LEAVE: + socket_destroy(player.socket); + player.socket = -1; + break; + + case PLAYER_CHANGECLASS: + var class; + class = read_ubyte(socket); + if(getCharacterObject(player.team, class) != -1) + { + if(player.object != -1) + { + with(player.object) + { + if (collision_point(x,y,SpawnRoom,0,0) < 0) + { + if (!instance_exists(lastDamageDealer) || lastDamageDealer == player) + { + sendEventPlayerDeath(player, player, noone, BID_FAREWELL); + doEventPlayerDeath(player, player, noone, BID_FAREWELL); + } + else + { + var assistant; + assistant = secondToLastDamageDealer; + if (lastDamageDealer.object) + if (lastDamageDealer.object.healer) + assistant = lastDamageDealer.object.healer; + sendEventPlayerDeath(player, lastDamageDealer, assistant, FINISHED_OFF); + doEventPlayerDeath(player, lastDamageDealer, assistant, FINISHED_OFF); + } + } + else + instance_destroy(); + + } + } + else if(player.alarm[5]<=0) + player.alarm[5] = 1; + class = checkClasslimits(player, player.team, class); + player.class = class; + ServerPlayerChangeclass(playerId, player.class, global.sendBuffer); + } + break; + + case PLAYER_CHANGETEAM: + var newTeam, balance, redSuperiority; + newTeam = read_ubyte(socket); + + redSuperiority = 0 //calculate which team is bigger + with(Player) + { + if(team == TEAM_RED) + redSuperiority += 1; + else if(team == TEAM_BLUE) + redSuperiority -= 1; + } + if(redSuperiority > 0) + balance = TEAM_RED; + else if(redSuperiority < 0) + balance = TEAM_BLUE; + else + balance = -1; + + if(balance != newTeam) + { + if(getCharacterObject(newTeam, player.class) != -1 or newTeam==TEAM_SPECTATOR) + { + if(player.object != -1) + { + with(player.object) + { + if (!instance_exists(lastDamageDealer) || lastDamageDealer == player) + { + sendEventPlayerDeath(player, player, noone, BID_FAREWELL); + doEventPlayerDeath(player, player, noone, BID_FAREWELL); + } + else + { + var assistant; + assistant = secondToLastDamageDealer; + if (lastDamageDealer.object) + if (lastDamageDealer.object.healer) + assistant = lastDamageDealer.object.healer; + sendEventPlayerDeath(player, lastDamageDealer, assistant, FINISHED_OFF); + doEventPlayerDeath(player, lastDamageDealer, assistant, FINISHED_OFF); + } + } + player.alarm[5] = global.Server_Respawntime; + } + else if(player.alarm[5]<=0) + player.alarm[5] = 1; + var newClass; + newClass = checkClasslimits(player, newTeam, player.class); + if newClass != player.class + { + player.class = newClass; + ServerPlayerChangeclass(playerId, player.class, global.sendBuffer); + } + player.team = newTeam; + ServerPlayerChangeteam(playerId, player.team, global.sendBuffer); + ServerBalanceTeams(); + } + } + break; + + case CHAT_BUBBLE: + var bubbleImage; + bubbleImage = read_ubyte(socket); + if(global.aFirst) { + bubbleImage = 0; + } + write_ubyte(global.sendBuffer, CHAT_BUBBLE); + write_ubyte(global.sendBuffer, playerId); + write_ubyte(global.sendBuffer, bubbleImage); + + setChatBubble(player, bubbleImage); + break; + + case BUILD_SENTRY: + if(player.object != -1) + { + if(player.class == CLASS_ENGINEER + and collision_circle(player.object.x, player.object.y, 50, Sentry, false, true) < 0 + and player.object.nutsNBolts == 100 + and (collision_point(player.object.x,player.object.y,SpawnRoom,0,0) < 0) + and !player.sentry + and !player.object.onCabinet) + { + write_ubyte(global.sendBuffer, BUILD_SENTRY); + write_ubyte(global.sendBuffer, playerId); + write_ushort(global.serializeBuffer, round(player.object.x*5)); + write_ushort(global.serializeBuffer, round(player.object.y*5)); + write_byte(global.serializeBuffer, player.object.image_xscale); + buildSentry(player, player.object.x, player.object.y, player.object.image_xscale); + } + } + break; + + case DESTROY_SENTRY: + with(player.sentry) + instance_destroy(); + break; + + case DROP_INTEL: + if (player.object != -1) + { + if (player.object.intel) + { + sendEventDropIntel(player); + doEventDropIntel(player); + } + } + break; + + case OMNOMNOMNOM: + if(player.object != -1) { + if(!player.humiliated + and !player.object.taunting + and !player.object.omnomnomnom + and player.object.canEat + and player.class==CLASS_HEAVY) + { + write_ubyte(global.sendBuffer, OMNOMNOMNOM); + write_ubyte(global.sendBuffer, playerId); + with(player.object) + { + omnomnomnom = true; + if player.team == TEAM_RED { + omnomnomnomindex=0; + omnomnomnomend=31; + } else if player.team==TEAM_BLUE { + omnomnomnomindex=32; + omnomnomnomend=63; + } + xscale=image_xscale; + } + } + } + break; + + case TOGGLE_ZOOM: + if player.object != -1 { + if player.class == CLASS_SNIPER { + write_ubyte(global.sendBuffer, TOGGLE_ZOOM); + write_ubyte(global.sendBuffer, playerId); + toggleZoom(player.object); + } + } + break; + + case PLAYER_CHANGENAME: + var nameLength; + nameLength = socket_receivebuffer_size(socket); + if(nameLength > MAX_PLAYERNAME_LENGTH) + { + write_ubyte(player.socket, KICK); + write_ubyte(player.socket, KICK_NAME); + socket_destroy(player.socket); + player.socket = -1; + } + else + { + with(player) + { + if(variable_local_exists("lastNamechange")) + if(current_time - lastNamechange < 1000) + break; + lastNamechange = current_time; + name = read_string(socket, nameLength); + if(string_count("#",name) > 0) + { + name = "I <3 Bacon"; + } + write_ubyte(global.sendBuffer, PLAYER_CHANGENAME); + write_ubyte(global.sendBuffer, playerId); + write_ubyte(global.sendBuffer, string_length(name)); + write_string(global.sendBuffer, name); + } + } + break; + + case INPUTSTATE: + if(player.object != -1) + { + with(player.object) + { + keyState = read_ubyte(socket); + netAimDirection = read_ushort(socket); + aimDirection = netAimDirection*360/65536; + event_user(1); + } + } + break; + + case REWARD_REQUEST: + player.rewardId = read_string(socket, socket_receivebuffer_size(socket)); + player.challenge = rewardCreateChallenge(); + + write_ubyte(socket, REWARD_CHALLENGE_CODE); + write_binstring(socket, player.challenge); + break; + + case REWARD_CHALLENGE_RESPONSE: + var answer, i, authbuffer; + answer = read_binstring(socket, 16); + + with(player) + if(variable_local_exists("challenge") and variable_local_exists("rewardId")) + rewardAuthStart(player, answer, challenge, true, rewardId); + + break; + + case PLUGIN_PACKET: + var packetID, buf, success; + + packetID = read_ubyte(socket); + + // get packet data + buf = buffer_create(); + write_buffer_part(buf, socket, socket_receivebuffer_size(socket)); + + // try to enqueue + success = _PluginPacketPush(packetID, buf, player); + + // if it returned false, packetID was invalid + if (!success) + { + // clear up buffer + buffer_destroy(buf); + + // kick player + write_ubyte(player.socket, KICK); + write_ubyte(player.socket, KICK_BAD_PLUGIN_PACKET); + socket_destroy(player.socket); + player.socket = -1; + } + break; + + case CLIENT_SETTINGS: + var mirror; + mirror = read_ubyte(player.socket); + player.queueJump = mirror; + + write_ubyte(global.sendBuffer, CLIENT_SETTINGS); + write_ubyte(global.sendBuffer, playerId); + write_ubyte(global.sendBuffer, mirror); + break; + + } + break; + } +} diff --git a/samples/Game Maker Language/scrInitLevel.gml b/samples/Game Maker Language/scrInitLevel.gml new file mode 100644 index 00000000..af0cf274 --- /dev/null +++ b/samples/Game Maker Language/scrInitLevel.gml @@ -0,0 +1,298 @@ +// Originally from /spelunky/Scripts/Level Generation/scrInitLevel.gml in the Spelunky Community Update Project + +// +// scrInitLevel() +// +// Calls scrLevelGen(), scrRoomGen*(), and scrEntityGen() to build level. +// + +/********************************************************************************** + Copyright (c) 2008, 2009 Derek Yu and Mossmouth, LLC + + This file is part of Spelunky. + + You can redistribute and/or modify Spelunky, including its source code, under + the terms of the Spelunky User License. + + Spelunky is distributed in the hope that it will be entertaining and useful, + but WITHOUT WARRANTY. Please see the Spelunky User License for more details. + + The Spelunky User License should be available in "Game Information", which + can be found in the Resource Explorer, or as an external file called COPYING. + If not, please obtain a new copy of Spelunky from + +***********************************************************************************/ + +global.levelType = 0; +//global.currLevel = 16; +if (global.currLevel > 4 and global.currLevel < 9) global.levelType = 1; +if (global.currLevel > 8 and global.currLevel < 13) global.levelType = 2; +if (global.currLevel > 12 and global.currLevel < 16) global.levelType = 3; +if (global.currLevel == 16) global.levelType = 4; + +if (global.currLevel <= 1 or + global.currLevel == 5 or + global.currLevel == 9 or + global.currLevel == 13) +{ + global.hadDarkLevel = false; +} + +// global.levelType = 3; // debug + +// DEBUG MODE // +/* +if (global.currLevel == 2) global.levelType = 4; +if (global.currLevel == 3) global.levelType = 2; +if (global.currLevel == 4) global.levelType = 3; +if (global.currLevel == 5) global.levelType = 4; +*/ + +// global.levelType = 0; + +global.startRoomX = 0; +global.startRoomY = 0; +global.endRoomX = 0; +global.endRoomY = 0; +oGame.levelGen = false; + +// this is used to determine the path to the exit (generally no bombs required) +for (i = 0; i < 4; i += 1) +{ + for (j = 0; j < 4; j += 1) + { + global.roomPath[i,j] = 0; + } +} + +// side walls +if (global.levelType == 4) + k = 54; +else if (global.levelType == 2) + k = 38; +else if (global.lake) + k = 41; +else + k = 33; +for (i = 0; i <= 42; i += 1) +{ + for (j = 0; j <= k; j += 1) + { + if (not isLevel()) + { + i = 999; + j = 999; + } + else if (global.levelType == 2) + { + if (i*16 == 0 or + i*16 == 656 or + j*16 == 0) + { + obj = instance_create(i*16, j*16, oDark); + obj.invincible = true; + obj.sprite_index = sDark; + } + } + else if (global.levelType == 4) + { + if (i*16 == 0 or + i*16 == 656 or + j*16 == 0) + { + obj = instance_create(i*16, j*16, oTemple); + obj.invincible = true; + if (not global.cityOfGold) obj.sprite_index = sTemple; + } + } + else if (global.lake) + { + if (i*16 == 0 or + i*16 == 656 or + j*16 == 0 or + j*16 >= 656) + { + obj = instance_create(i*16, j*16, oLush); obj.sprite_index = sLush; + obj.invincible = true; + } + } + else if (i*16 == 0 or + i*16 == 656 or + j*16 == 0 or + j*16 >= 528) + { + if (global.levelType == 0) { obj = instance_create(i*16, j*16, oBrick); obj.sprite_index = sBrick; } + else if (global.levelType == 1) { obj = instance_create(i*16, j*16, oLush); obj.sprite_index = sLush; } + else { obj = instance_create(i*16, j*16, oTemple); if (not global.cityOfGold) obj.sprite_index = sTemple; } + obj.invincible = true; + } + } +} + +if (global.levelType == 2) +{ + for (i = 0; i <= 42; i += 1) + { + instance_create(i*16, 40*16, oDark); + //instance_create(i*16, 35*16, oSpikes); + } +} + +if (global.levelType == 3) +{ + background_index = bgTemple; +} + +global.temp1 = global.gameStart; +scrLevelGen(); + +global.cemetary = false; +if (global.levelType == 1 and rand(1,global.probCemetary) == 1) global.cemetary = true; + +with oRoom +{ + if (global.levelType == 0) scrRoomGen(); + else if (global.levelType == 1) + { + if (global.blackMarket) scrRoomGenMarket(); + else scrRoomGen2(); + } + else if (global.levelType == 2) + { + if (global.yetiLair) scrRoomGenYeti(); + else scrRoomGen3(); + } + else if (global.levelType == 3) scrRoomGen4(); + else scrRoomGen5(); +} + +global.darkLevel = false; +//if (not global.hadDarkLevel and global.currLevel != 0 and global.levelType != 2 and global.currLevel != 16 and rand(1,1) == 1) +if (not global.hadDarkLevel and not global.noDarkLevel and global.currLevel != 0 and global.currLevel != 1 and global.levelType != 2 and global.currLevel != 16 and rand(1,global.probDarkLevel) == 1) +{ + global.darkLevel = true; + global.hadDarkLevel = true; + //instance_create(oPlayer1.x, oPlayer1.y, oFlare); +} + +if (global.blackMarket) global.darkLevel = false; + +global.genUdjatEye = false; +if (not global.madeUdjatEye) +{ + if (global.currLevel == 2 and rand(1,3) == 1) global.genUdjatEye = true; + else if (global.currLevel == 3 and rand(1,2) == 1) global.genUdjatEye = true; + else if (global.currLevel == 4) global.genUdjatEye = true; +} + +global.genMarketEntrance = false; +if (not global.madeMarketEntrance) +{ + if (global.currLevel == 5 and rand(1,3) == 1) global.genMarketEntrance = true; + else if (global.currLevel == 6 and rand(1,2) == 1) global.genMarketEntrance = true; + else if (global.currLevel == 7) global.genMarketEntrance = true; +} + +//////////////////////////// +// ENTITY / TREASURES +//////////////////////////// +global.temp2 = global.gameStart; +if (not isRoom("rTutorial") and not isRoom("rLoadLevel")) scrEntityGen(); + +if (instance_exists(oEntrance) and not global.customLevel) +{ + oPlayer1.x = oEntrance.x+8; + oPlayer1.y = oEntrance.y+8; +} + +if (global.darkLevel or + global.blackMarket or + global.snakePit or + global.cemetary or + global.lake or + global.yetiLair or + global.alienCraft or + global.sacrificePit or + global.cityOfGold) +{ + if (not isRoom("rLoadLevel")) + { + with oPlayer1 { alarm[0] = 10; } + } +} + +if (global.levelType == 4) scrSetupWalls(864); +else if (global.lake) scrSetupWalls(656); +else scrSetupWalls(528); + +// add background details +if (global.graphicsHigh) +{ + repeat(20) + { + // bg = instance_create(16*rand(1,42), 16*rand(1,33), oCaveBG); + if (global.levelType == 1 and rand(1,3) < 3) + tile_add(bgExtrasLush, 32*rand(0,1), 0, 32, 32, 16*rand(1,42), 16*rand(1,33), 10002); + else if (global.levelType == 2 and rand(1,3) < 3) + tile_add(bgExtrasIce, 32*rand(0,1), 0, 32, 32, 16*rand(1,42), 16*rand(1,33), 10002); + else if (global.levelType == 3 and rand(1,3) < 3) + tile_add(bgExtrasTemple, 32*rand(0,1), 0, 32, 32, 16*rand(1,42), 16*rand(1,33), 10002); + else + tile_add(bgExtras, 32*rand(0,1), 0, 32, 32, 16*rand(1,42), 16*rand(1,33), 10002); + } +} + +oGame.levelGen = true; + +// generate angry shopkeeper at exit if murderer or thief +if ((global.murderer or global.thiefLevel > 0) and isRealLevel()) +{ + with oExit + { + if (type == "Exit") + { + obj = instance_create(x, y, oShopkeeper); + obj.status = 4; + } + } + // global.thiefLevel -= 1; +} + +with oTreasure +{ + if (collision_point(x, y, oSolid, 0, 0)) + { + obj = instance_place(x, y, oSolid); + if (obj.invincible) instance_destroy(); + } +} + +with oWater +{ + if (sprite_index == sWaterTop or sprite_index == sLavaTop) + { + scrCheckWaterTop(); + } + /* + obj = instance_place(x-16, y, oWater); + if (instance_exists(obj)) + { + if (obj.sprite_index == sWaterTop or obj.sprite_index == sLavaTop) + { + if (type == "Lava") sprite_index = sLavaTop; + else sprite_index = sWaterTop; + } + } + obj = instance_place(x+16, y, oWater); + if (instance_exists(obj)) + { + if (obj.sprite_index == sWaterTop or obj.sprite_index == sLavaTop) + { + if (type == "Lava") sprite_index = sLavaTop; + else sprite_index = sWaterTop; + } + } + */ +} + +global.temp3 = global.gameStart;