CodeSOD: Extended and Empty |
We've discussed extension methods in .NET in the past. If you write a method with a signature like public static void foo(this string input)
, then you can invoke this method like myString.foo()
. It acts like an instance method to a caller, but it really just invokes foo(myString)
. It's a nice way to inject functionality into existing classes, and many .NET libraries use this feature.
Esromhaz's co-worker found an… application of this.
public static bool IsNullOrEmpty(this string str)
{
return string.IsNullOrEmpty(str);
}
public static bool IsNotNullOrEmpty(this string str)
{
return !string.IsNullOrEmpty(str);
}
Now, the default IsNullOrEmpty
is a static method, because it has to be- if myString
is null
, I can't call IsNullOrEmpty
on it. That doesn't make sense. But the co-worker clearly disagreed with this limitation and wanted it to look like an instance method.
So, this actually does work:
string myString = null;
bool isNull = myString.IsNullOrEmpty();
Which is… an interesting behavior. This code is old enough that it might predate .NET's addition of nullable types, which it ends up sort of imitating, though I think that's more of an accident.
This makes the code weirder, and certainly would confuse someone not used to the codebase. I'm sure the original developer thought it was mighty convenient though.
Метки: CodeSOD |
CodeSOD: Rename This |
Visual Basic .NET is actually not a terrible language. I don't think it'd ever be anyone's favorite language, and these days it's basically deceased, but it's essentially C# with verbose syntax. Even the APIs are identical…
… almost.
Because, you see, when Microsoft released VB .NET, their core plan was that it should be "easy" to port your existing VB6 code into VB .NET. This meant recreating a lot of static functions in the global namespace, like Mid
(substring), or Kill
(file delete). There were better, cleaner ways to do all of those things. For example, Kill
throws an exception if you try and delete a file that doesn't exist, while File.Delete
doesn't (but provides a status code).
So while you should use the newer APIs, you could still do things the old fashioned way. It was like GoTo
- you shouldn't use it, but you could.
Russell F inherited some code which has all of these fun tricks.
Private Sub Rename_OutputFile()
Dim strWrkName As String
Dim Counter1 As Integer 'Bob Flemp
'Set output file variable...
strWrkName = Mid(strInput, 1, InStr(1, strInput, ".")) & "tmp"
strOutput = Mid(strInput, 1, InStr(1, strInput, ".")) & "out"
' If Dir$(strOutput) = "" Then GoTo RenameOutput1 'tempremove Bob Flemp
'Delete Existing output file...
Try
Kill(strOutput)
Catch
End Try
Counter1 = 0
RenameOutput1:
'Rename temp file to output file...
Try
Rename(strWrkName, strOutput)
Catch ex As Exception
Counter1 = Counter1 + 1
If Counter1 > 10 Then
If ActionCode.ToLower <> "t" AndAlso Not File.Exists(strWrkName) Then Exit Sub 'Bob Flemp - 112613
MsgBox("Cannot rename temporary output file from" & strWrkName &
" to " & strOutput & "." & ControlChars.CrLf &
"Reason is: " & ex.ToString)
Else
System.Threading.Thread.Sleep(100)
GoTo RenameOutput1
End If
End Try
Log("leaving Rename_OutputFile")
End Sub
"Bob Flemp" helpfully commented the lines he changed. Sometimes with just his name, sometimes with the date. The team was using source control, so none of that was necessary.
The pattern here is that they first do some string munging to get the paths arranged correctly. Then they try and delete the file at the destination, if it exists, catching any exceptions in that process. It's worth noting that there are many possible errors arising from deleting a file, not just that it didn't exist, but we just ignore them all. Plus, as deployed, the file being deleted should almost never exist, so almost all of the exceptions being thrown would be real exceptions that we need to handle properly.
Note also the commented out Dir$
call, which enumerates all the files matching the input pattern- this is a vestigal check to see if the output file exists before deleting it. This was probably a better choice than the catch-all exception block, which adds the frustration of seeing that they had a better thought at one point, but changed their minds. Then again, it also uses GoTo
for control flow, so let's not credit them too much.
We get our block to actually do the renaming, complete with a retry counter and a loop implemented in GoTo
s. If, after ten attempts, we fail to rename, we either exist the subroutine without any hint of what just happened, or we pop up a message box explaining the error. I'm sure this never confuses any users.
It's worth noting that retrying the move every tenth of a second for one whole second is probably not going to resolve whatever locking issue prevented the rename in the first place. In the end, it's just a nice convenient way to waste some time before popping up an error- but time that other parts of the program might be depending on.
Russell writes:
I was allowed to refactor it, but I was too afraid to really change the basic structure (this code is such a mess that it may break a lot of other stuff) so my refactored version isn't really much better.
Private Sub RenameOutputFile()
Dim basePath As String = BaseAuth()
Dim srcPath As String = basePath & "tmp"
Dim destPath As String = basePath & "out"
For i As Integer = 1 To 10
Try
File.Delete(destPath)
File.Move(srcPath, destPath)
Return
Catch
If i = 10 Then
Throw
Else
Threading.Thread.Sleep(100)
End If
End Try
Next
End Sub
It's easier to read, at least, but the basic stupidity is still there.
Метки: CodeSOD |
Court-Martial |
At the age of 17, our friend Argle had a job as a programmer for an aerospace firm, mostly working with commercial flight-deck equipment. Like with anyone new to a given industry, he found himself puzzling over the plethora of acronyms that got thrown around in conversation without a thought. Lacking an Internet to go look these things up in, Argle was forced to ask people to stop, go back, and explain. But what 17 year-old feels comfortable interrupting much older adults like that? Most of the time, the acronyms were scribbled down on a yellow legal pad, to be figured out later.
The programming side of the job was no problem, at least. There was one case of something a little odd: someone handed him some coefficients of a polynomial and told him to make them into a FORTRAN function. He had no familiarity with curve fitting, but that part of the work had already been done for him. He implemented the function and moved on to other things.
Some weeks later, he was working at his cube when a stern voice behind him caught his attention. "Mr. Argle."
It was that tone of voice specifically reserved for calling someone to the carpet, and it made his viscera plummet. Swiveling around in his chair, he beheld a dozen people or more crammed all along the perimeter of his cube and spilling into its cramped confines. Some were in fancy business suits, some in full military regalia. All were wearing the most furious expressions he'd ever seen.
Argle must've started an inch out of his chair.
"Mr. Argle." The military man closest to him spoke again, this time with the full weight of his glare bearing down on him. "We have jets that won't fly because of you."
"Me?" Argle sputtered. "But—"
"That FORTRAN polynomial function we assigned to you!" another voice piped up. "That has to be it! You either made a mistake because you were careless, incompetent, or for some worse reason."
"That?!" Argle's brain raced faster than it had ever raced before. "No! Here!" He swiveled to face his desk and sifted through a pile of folders sitting there. He'd been good about documenting all his work thus far, detailing the whats, whys, and hows. It took him a minute, as his hands shook and his panicked brain had trouble processing the words he looked at, but he was able to find his notes, along with the original coefficients that had been handed to him. He all but heaved the assembled evidence toward his prosecutors. "Here! This is all I did, I swear!"
The tight crowd of higher-ups crowded even tighter around the papers, muttering inaudibly to one another and occasionally coughing. Their collective wrath dissipated, leaving behind an awkward gap of silence. At last, someone said a simple "Thanks" and handed the papers back to Argle. They marched off together, presumably to do this all over again to another poor soul who lacked adequate shielding.
Argle heaved a sigh of relief. That day, he learned that the most important business-related acronym is and always shall be CYA.
Метки: Feature Articles |
CodeSOD: When You Can't Use WordPress |
Initech had a lovely little Content Management System built by a third party that was good at "building" CMSes. That is to say, this company knew its way around a WordPress template.
When Initech needed a new public-facing website, they went straight back to that vendor. Now, this new website was a complicated statistical tool, with all sorts of complicated business rules and requiring a large amount of custom programming. So the vendor that just cranked out WordPress templates may not have been a good fit for the project, but that didn't stop anybody.
That's how Magnus M found code which looks like this:
$dataTotalArray[$md['adt_group_id']][$md['atss_id']]['adt_text'] = $md['adt_text'];
if($md['type'] == "totalthisyear") {
$dataTotalArray[$md['adt_group_id']][$md['atss_id']]['amount'] = $md['amount'];
$dataTotalArray[$md['adt_group_id']][$md['atss_id']]['count'] = $md['count'];
}
There are so many nested braces here I'd almost suspect this was a LISP dialect instead of PHP.
That's just ugly and incomprehensible, and it isn't the WTF. The real WTF is that someone had it in their head that "web code" meant anyone doing "view source" could see the code. This complete misunderstanding about what constitutes server-side code lead them to ensure that all their constants were obfuscated via an MD5 hash.
if($_GET['size_cat'] == '59e9effecfe048c8-b13a76da69cc12df') {
$adt_old_type = 0;
} else {
$adt_old_type = 1;
}
// later in document - in the "presentation" part
if ($_GET['size_cat'] == '59e9effecfe048c8-b13a76da69cc12df') {
echo "ADT";
} else {
echo "ADT";
}
Magnus writes:
In some cases even the arguments to their stored procedures requires the arguments MD5'ed. Makes for wonderful debugging.
https://thedailywtf.com/articles/when-you-can-t-use-wordpress
Метки: CodeSOD |
Error'd: Past Time |
This week we're throwing a bone to all the antipunsters in the audience and declaring a unilateral cease-fire on all alliteration, wordplay, jabs, jokes, or other japery. Starting now.
Allie shares a redundancy that I think we've seen here before, saying "It may be _technically_ right, but I don't have to like it."
Dane Christian sarcastically intones "Nice error message and if that's really the full stack trace I'm happy I'm not supporting this site."
Regular contributor Peter G. figures the math doesn't add up [I tried, honest!], reporting "It looks like Covid-19 has jumped to sheep in New Zealand since there's only about 5 million people there but apparently 17 million cases." Seems like we've seen an another Error'd much like this one out of NZ quite recently. I wonder if it's something in the water.
Max chimes in with an observation on irony. "You'd think NIST would keep better track of time and dates."
Finally, another repeat contributor Marek (aka NeverConfusedForNaught) shares a very odd graph. "My battery was failing me - that I already knew. I thought I anticipated all failure modes of a dying battery, but this one they taught me impossible!"
Метки: Error'd |
CodeSOD: Constantly Sinister |
Cat has some Java code which handles text layout. Now, someone writing the code didn't understand the idea of enumerated types, so every constant is a string.
That's a nuisance, but not a terrible problem. Of course, with Java's String handling, you also run into the fact that ==
will frequently work, because Java tries to reuse String instances, but it won't always work, and thus equals
exists.
So this code didn't actually work:
if (alignment == LEFT_ALIGN) {
return RIGHT_ALIGN;
} else if (alignment == RIGHT_ALIGN) {
return LEFT_ALIGN;
} else {
return CENTER_ALIGN;
}
But even if it did, the only thing I can think when I look at this code is: "No, your other left!"
Метки: CodeSOD |
CodeSOD: Wear a Wrap |
While jQuery isn't as popular today as it once was, its still a widely used "utility belt" library. Its popularity arises from the fact that it takes cumbersome APIs and wraps convenience functions around them.
But what if those convenience functions are too convenient, like Ryan's co-worker found. For example, if you want to perform an asynchronous HTTP request using jQuery, you could do something like:
$.ajax({
url: urltosend,
type: posttype,
data: datatosend,
dataType: datatype,
context:cntxt,
success: successfunction,
error: errorfunction
});
Now, we can object to passing callbacks directly instead of using promises here, but we have a complex call with a lot of parameters and so we wrap those parameters in an object to ensure they're clearly named and readable. It allows the flexibility of constructing the request in multiple steps before actually sending it.
But what if you don't want any of that, and like counting parameters to make sure you've properly passed your inputs?
function callAjax(urltosend,posttype,datatosend,datatype,cntxt,successfunction,errorfunction){
$.ajax({
url: urltosend,
type: posttype,
data: datatosend,
dataType: datatype,
context:cntxt,
success: successfunction,
error: errorfunction
});
}
Now that's convenient.
Метки: CodeSOD |
CodeSOD: Hey, Backoff! |
The Strategy Pattern, like any other design pattern, is supposed to help us write code that's flexible and easy to modify. In the case of the strategy pattern, you have one class, the Context
, which has a member which implements a Strategy
interface. The actual implementation lives in the Strategy
, but the Context
is the object that other code in the program consumes. Ideally, this means that you can change the behavior of your Context
by simply swapping a different Strategy
implementation in.
On the flip side, when it gets out of hand, you might end up like Dotan, consuming an API from a major cloud vendor, written with a terminal case of "Pattern Brain".
$backoff = $config->get(Options::BACKOFF);
if ($backoff === null) {
$backoff = new BackoffPlugin(
// Retry failed requests up to 3 times if it is determined that the request can be retried
new TruncatedBackoffStrategy(3,
// Retry failed requests with 400-level responses due to throttling
new ThrottlingErrorChecker($this->exceptionParser,
// Retry failed requests with 500-level responses
new HttpBackoffStrategy(array(500, 503, 509),
// Retry failed requests due to transient network or cURL problems
new CurlBackoffStrategy(null,
// Retry requests that failed due to expired credentials
new ExpiredCredentialsChecker($this->exceptionParser,
new ExponentialBackoffStrategy()
)
)
)
)
)
);
}
This was from the official vendor documentation, representing a pretty standard request that would run on every single page load.
Now, I'm sure this code works, and I'm sure that this chain of tested strategies and error handlers has a very good purpose. And maybe my hatred of deep nesting is just an aesthetic objection, and this isn't really a WTF.
But this is the worst way I can imagine to do this, even if you're insistent on chaining together all these objects. It's hard to read, and difficult to understand the purpose of all these, even the order of them- does it matter?
This is an API that's so flexible that it forgot to make the common use cases accessible to most developers. And while it'd be easy to wrap this up in some convenience methods, if it's a major vendor, one would hope that the API is a little more finished- even if it is the PHP implementation.
Метки: CodeSOD |
CodeSOD: Administrative Transfer |
When the pandemic started, a lot of companies needed to cut back, and Initech was no exception. In the Before Times™, Initech had an offshore development team that maintained an application inherited from an acquisition. Now that everyone was tightening their belts, though, that had to go. The offshore team was released and the application they supported ended up in the hands of Lovelace.
Lovelace dug in, and they quickly discovered a few things. From 2010-2017, the application's idea of source control was a folder on an FTP server and a lot of files named some_module.php.old
and some_module.php.donotuse
. In 2017, someone put the project into Git, but the commits were just point-in-time snapshots of the FTP folder. Deployment? Just work right on that FTP folder, it's fine. And if you forget to commit those changes back to source control? No biggie- consider the FTP folder the source of truth.
Then again, when Lovelace saw the "truth" that was there, they weren't exactly excited to be supporting this PHP application.
if (!isset($_SESSION['ADMIN_ID']) or empty($_SESSION['ADMIN_ID']) or $_SESSION['ADMIN_ID'] == NULL) {
$isAdmin = false;
}
else {
$isAdmin = true;
}
Now, this isn't going to be anybody's favorite code, but it's not a WTF. If the session variable ADMIN_ID
doesn't have a meaningful value, then we're not in admin mode, otherwise we are. It's ugly, and I think redundant: while isset
and empty
are actually different, isset
includes a NULL
check.
As Lovelace writes:
While ugly, this really isn't so bad. It's not like you can set session variables from the client.
Of course you can't. Right? Right?
if (isset($_COOKIE["SESSION"])) {
foreach ($_COOKIE['SESSION'] as $name => $value) {
$_SESSION[$name]= $value;
}
}
This block is copy-pasted into the top of every page, because what's an "include file"? This looks for all the sub-cookies under SESSION
and just loads them directly into $_SESSION
. So a cookie named SESSION[ADMIN_ID]
would get stored in $_SESSION['ADMIN_ID']
.
So now, anyone who could open up the dev tools could easily make themselves an admin. And there were many admin pages, and as you can imagine, the admin validation was also copy-pasted into every admin page, right under the cookie/session loader. And an exploiter wouldn't need to guess the correct value- any non-false, non-null value is going to grant them admin access.
Or set/alter any session variable. Who knows what else might be in there?
Lovelace raised this up to their management. "Oh, it's no big deal," management said, "because we're going to stop selling this software sometime really soon. I mean, we did just sign a three year contract with a large customer, but like, really soon, nobody will be using this anymore."
Метки: CodeSOD |
Error'd: The Hardest Problem |
It has been infamously stated that the two hardest problems in computer science are naming, caching, and off-by-one errors. Mixing and matching arrays indexed from 0 with arrays indexed from 1 could be a special-case of the off-by-one error.
The zeroth of a sudden rash of anonymous posters spotted this initial example, coughing "Pre-increment seems somehow inappropriate here."
Carlos follows up with a third, muttering "The image is pretty self-explanatory. Off-by-one error, maybe? I don't even know." Honestly, it took me a moment.
Another topically anonymous donor wonders "Are those $USD or $AUD?" Must be US dollars; in OZ they'd be $aNa.
The last of our anonymous cohort crowed "Vaccination rate is better than I thought."
Finally, the not entirely anonymous David B. dishes on his disguised employer. "At $WORK to log into some of the external sites requires SSO, sometimes SSO gives you a nice error message. Sometimes you get this. $WORK is a very large software company, it should be able to do better."
Метки: Error'd |
CodeSOD: A Cup of CIDR |
Marco sends us some code that he wrote, back in the far off days of 2003. He wrote some code to handle network addresses. Unfortunately, it never quite worked. Specifically it could only handle addresses in the /24
subnet.
Now, this was written in Perl, so you know it involves regexes.
@IPstart = split(/\./,$start);
@IPend = split(/\./,$end);
&Check_Start_and_EndIP;
# converts "short" (dotted) IPs to "long" (undotted) IPs
$IPstart = (($IPstart[0]*16777216)+($IPstart[1]*65536)+($IPstart[2]*256)+$IPstart[3]);
$IPend = (($IPend[0]*16777216)+($IPend[1]*65536)+($IPend[2]*256)+$IPend[3]);
if($IPend < $IPstart) { die "Can't scan backwards"; }
$CountIp = $IPstart;
$EndIp = $IPend+1;
while($CountIp ne $EndIp)
{
@Class = &GetIP($CountIp);
push(@targetlist, "$Class[1]\.$Class[2]\.$Class[3]\.$Class[4]");
$CountIp++;
}
This, by the way, is Marco's attempt to fix the broken code. As Marco puts it: "I am not a coder. I 'fix' things."
Метки: CodeSOD |
CodeSOD: A Forgotten Memory |
Starting in 1985, a tiny little language called Clipper briefly conquered the world. It was a dBase-derivative, with the bonus that its programs could be compiled and run as stand-alone software on MS-DOS. That meant small programs which couldn't justify getting time on a mainframe or a minicomputer could be whipped up to run on a desktop. It was popular, and for about a decade, it was everywhere. Then Windows 95 came out, and Clipper refused to pivot to Windows and became a footnote in history.
It mostly worked pretty great, or at least as great as one of those tiny business-focused languages ever got. Unfortunately for Drake, he encountered some issues in the latter days of Clipper in the early 90s.
Drake's team wrote an image display library for Clipper programs. An end-user of their library complained that it crashed when displaying images. They supplied a sample program that replicated the issue, and Drake and his colleagues were able to repeat the issue.
The core pattern was: allocate memory for an image, display the image, release the memory. Repeat that a few times, and eventually it crashed. There were a few other memory allocations along the way, and they all went through Clipper's memory manager.
So Drake and his team started stripping back their code, commenting out operations until they had a bare minimum program which could replicate the crash. By the time they were finished, the only code left was the code which allocated memory and released it. The simple test case program simply performed a series of memory allocations and releases, and repeated that, printing out the iteration number.
On some machines, the program crashed after 22 iterations. On others, it crashed after 151. It was consistent, always crashing on the same iteration on the same machine. Machines with otherwise identical hardware could end up being in either the 22 or the 151 group. A consistent program to reproduce the bug meant that Drake had something to send Nantucket, the company making Clipper.
Drake explains what happens next:
The interesting part was some of the feedback we got. Our customer service contact told us that our test program had kind of blown up on their internal message board. We were told that some of the developers were complaining that our program was silly, because it didn't do anything with the memory we were allocating.
Doing anything or not, the program triggered its crash consistently. Tempers were flaring on the Nantucket side, and after some increasingly heated back and forth, the customer service contact gently ended the conversation: "We'll get back to you in a few days."
A few days later, there was a solution: the current memory manager in Clipper would be deprecated, and Drake's software should just start using the new one that would become the standard in a future release. This did fix the crash, and so that's what Drake's team did.
We never did get an explanation of why simple memory allocations were hosing their memory manager, nor if they ever fixed it.
Well, arguably, they did fix it, simply by completely replacing their approach to memory management. The actual source of the bug may be lost to time, though I'd love to find out what really happened on the other side of that customer service rep.
For now, though, it needs to remain an interesting historical mystery…
Метки: CodeSOD |
CodeSOD: It's Not None of Your Business is True |
Patricia's employer hired her because she knew Python. They were a Python shop, at least at this point, but they'd never actually hired a developer with Python experience before.
At some point, their PHP developer wanted to branch out, and decided to solve a problem using a small Python script. It was very successful, and features got added to it. In a surprisingly short amount of time, the script expanded until it was two separate web applications which were absolutely critical to business operations.
It was a bit of a mess, when Patricia took over. The code was split across three source control repositories. One was for "Application A", in its entirety. One was called "Application B Backend", which as the name implied was the backend of Application B. The third was called "Application B" and was described as the "front end" but it was actually a copy of "Application B Backend".
It was 40,000 lines of copy-pasted nonsense, and the core of it was a module called "gsf" or "general standard functions". It was almost all of the shared logic across modules. It was designed so that it never raised an exception, and instead every method that could possibly have to deal with an exception condition instead returned a tuple: (actualReturnValue,successBoolean)
. Thus, you could invoke something like this:
entity, succesful = gsf.getCustomerByPrimaryKey(primary_key)
if successful:
…
Now, that'd be annoying but tolerable. Definitely not a WTF. But that's not how this code got used. Instead of that, the developer wrote it like this:
entity, succesful = gsf.getCustomeryByPrimaryKey(primary_key)
if not (entity is None) == True:
# hundreds of lines of code...
else:
gsf.saveLogInDatabase("Something went wrong")
return
You see, if the operation failed, successful
would be false, but the entity
returned would also be None
.
Now, the Pythonic way to write that would be to say: if entity:
- None
is always "false-y". Instead, the developer responsible found the most convoluted way to express the idea, ignoring the perfectly good boolean value they could have used in the first place, ignoring the behavior of None
values, and instead just… spamming operations.
The preferred way would be to throw exceptions. The second best would be to if successful
, then if entity
, then if entity is not None
, then if not (entity is None)
, and if you'd just consumed a vat of rubbing alcohol and held your breath for five minutes, finally your choice would be if not (entity is None) == True
.
So yes, I hate that, but let's not ignore the absolute glory that is the else
error handler. I'll let Patricia summarize:
The error message "Something went wrong"t was not printed to a logger or to stdout or to a file, but to a database table that contained about two million rows of the sentence "Something went wrong"
I'm just surprised that it was only two million, and I assume someone truncated the table.
Oh, there is another surprise here. Again, I'll let Patricia explain:
Luckily for me, the software manager understood my plight and let me rewrite everything from scratch.
https://thedailywtf.com/articles/it-s-not-none-of-your-business-is-true
Метки: CodeSOD |
CodeSOD: Switch Kicker |
As covered previously, game code generally doesn't represent an interesting WTF. But bad e-commerce code certainly does. So today, Rhys sends us some JavaScript from a web-based fantasy-football (for non-USA values of football) site. Given that it handles microtransactions and in-game items passed between players, it's definitely more on the e-commerce side of things than anything else.
And much like that previous article, this one does involve a very large switch, but before we get to the switch, we have to get to the state validator:
else if(propertyName == "initUser" ||
propertyName == "updateUser" ||
propertyName == "submitLogin" ||
propertyName == "initRequestPasswordForm" ||
propertyName == "submitPasswordRequest" ||
propertyName == "resetPassowrd" ||
propertyName == "submitGameLogin" ||
propertyName == "deleteAlertReturn" ||
propertyName == "submitRegistration" ||
propertyName == "submitGameRegistration" ||
propertyName == "acceptGiftReturn" ||
propertyName == "sellGiftReturn" ||
propertyName == "buildTicker" ||
propertyName == "submitAward" ||
propertyName == "submitGameAward" ||
propertyName == "buyAsset" ||
propertyName == "sellAsset" ||
propertyName == "giftAsset" ||
propertyName == "loadArcadeProgress" ||
propertyName == "getGameCompositeAward" ||
propertyName == "visitFriend" ||
propertyName == "createGuestUser" ||
propertyName == "isOnBoard" ||
propertyName == "processCompletedCompetition" ||
propertyName == "handleNewPasswordSent" ||
propertyName == "resetPassword" ||
propertyName == "submitFriendMail" ||
propertyName == "savePitchPosition" ||
propertyName == "cardGiftConfirmed-noOverlay" ||
propertyName == "startCompetitionAfterCRNUpload" ||
propertyName == "handleReferralThanks" ||
propertyName == "loadPriamStore" ||
propertyName == "buyPriamItem") {
handlePostRender(propertyName, data);
}
Now, they're validating inputs, which is good, but the pile of magic strings begs for being turned into constants (and thus helping avoid input errors in the first place), or some sort of array check or something to make this more readable.
But inside the handlePostRender
we process these inputs, which also highlights how not necessary this if statement even is:
case "openStore":
if(tutorial.hasStarted)
if(tutorial.current == 5)
{
ajaxEngine.googleTrackEvent("event", {category:"pre-registration", action:"enter-store", label:"set-up-process"});
tutorial.next();
// hide close button
$(".close-button").css("display", "none");
// append tutorial to overlay
$('#tutorial').clone().appendTo('#Award-shopfront-overlay');
$("#Award-shopfront-overlay").find("#tutorial-6").show();
}
setupStoreButtons();
break;
case "loadFriends":
populateFriendList(assetsObject);
break;
case "loadFriendsAfterAdd":
populateFriendList(assetsObject, true);
break;
case "addFriend":
populateFriendList(assetsObject);
break;
case "joinInvitation":
handleJoinInvitation(assetsObject);
break;
case "loadStore":
populateStore(assetsObject);
break;
case "initUser":
setupAwardDetails(assetsObject);
break;
case "updateUser":
updateAwardDetails(assetsObject);
break;
case "loadGiftChoose-appendOverlay-opaqueBack":
populateGiftFriendList(assetsObject);
break;
case "loadCardGiftChoose-appendOverlay-opaqueBack":
populateCardGiftFriendList(assetsObject);
break;
case "customiseAward":
populateAward(assetsObject);
break;
case "customiseGameAward-noOverlay":
populateGameAward(assetsObject);
break;
case "giftConfirmed-noOverlay":
showGiftSent(assetsObject);
break;
case "cardGiftConfirmed-noOverlay":
showCardGiftSent(assetsObject);
break;
case "processMessage":
displayMail(assetsObject,-1);
break;
case "login":
initLogin();
break;
case "submitLogin":
//loaderfeedback.hide();
handleSubmitLogin(assetsObject);
break;
case "submitPasswordRequest":
handleSubmitPasswordRequest(assetsObject);
break;
case "initRequestPasswordForm":
initRequestPasswordForm(assetsObject);
break;
case "resetPassword":
account.resetPassword();
break;
case "accountDeleted":
account.accountDeleted();
break;
case "handleNewPasswordSent":
handleNewPasswordSent(assetsObject);
break;
case "submitGameLogin":
handleGameLogin(assetsObject);
break;
case "submitLogout-noOverlay":
handleLogout();
break;
case "submitLogout-noReload":
// do nought, this is handled earlier
// in AjaxEngine::makeRequest
break;
case "deleteAlertReturn":
handleDeleteAlertReturn(assetsObject);
break;
case "register":
initRegister();
break;
case "submitRegistration":
handleSubmitRegister(assetsObject);
break;
case "submitGameRegistration":
handleGameRegister(assetsObject);
break;
case "acceptGiftReturn":
handleAcceptGiftReturn(assetsObject);
break;
case "sellGiftReturn":
handleSellGiftReturn();
break;
case "pollReturn":
handlePollReturn();
break
case "predictionReturn":
handlePredictionReturn();
break
case "buildTicker":
handleTickerTape(assetsObject);
break;
case "processNews":
handleNewsItems(assetsObject);
break;
case "submitAward":
handleAwardSubmitted(assetsObject);
break;
case "submitGameAward":
handleGameAwardSubmitted(assetsObject);
break;
case "buildPlayerDetails":
populatePlayerDetails(assetsObject);
break;
case "buildPlayerBio":
populatePlayerBio(assetsObject);
break;
case "competitionReturn-noOverlay":
handleCompetitionPromoReturn(assetsObject);
break
case "processCompletedCompetition":
handleCompetitionFormReturn()
//handleProcessCompletedCompetition();
break;
case "processCompetitionForm":
handleCompetitionFormReturn(assetsObject);
break;
case "submitCompetitionCollectedData":
handleCompetitionFormReturn(assetsObject);
break;
case "sellAsset":
handleStoreSell();
// When selling item, make sure the turf world
// is re-built (and inited, particularly)
setupAwardDetails(assetsObject.turf);
break;
case "buyAsset":
handleStoreBuy();
break;
case "giftAsset":
handleStoreGive();
break;
case "handleUnwear":
handleUnwear();
break;
case "handleWear":
handleWear();
break;
case "quizReturn-noOverlay":
handleQuizPromoReturn(assetsObject);
break;
case "processQuizForm":
handleQuizFormReturn(assetsObject);
break;
case "printPlayers-noOverlay":
handlePrintingPlayerCards(assetsObject);
break;
case "handlePrintAllTrophiesAndMedals-noOverlay":
handlePrintAllTrophiesAndMedals(assetsObject);
break;
case "handlePrintAllTrophies-noOverlay":
handlePrintAllTrophies(assetsObject);
break;
case "handlePrintAllMedals-noOverlay":
handlePrintAllMedals(assetsObject);
break;
case "printInvite-noOverlay":
handlePrintInvite(assetsObject);
break;
case "loadArcadeProgress":
handleArcadeLoaded(assetsObject);
break;
case "getGameCompositeAward":
handleGameCompositeAward(assetsObject);
break;
case "visitFriend":
handleVisitFriend(assetsObject);
break;
case "createGuestUser":
handleCreateGuestUser(assetsObject);
break;
case "isOnBoard":
handleOnBoarding(assetsObject);
break;
case "submitFriendMail":
handleFriendRequestAccepted(assetsObject);
break;
case "savePitchPosition" :
startApp(assetsObject);
break;
case "handleTrophyOverlay" :
handleTrophyOverlay();
break;
case "loadMedal" :
handleMedalsOverlay();
break;
case "setupSaveProfile":
account.setupSaveProfile();
break;
case "processFinalisedQuiz":
processFinalisedQuiz();
break;
case "startCompetitionAfterCRNUpload" :
processCompetitionCRNThanks();
break;
case "handleReferralThanks" :
handleSubmitRegisterReferral(assetsObject);
break;
case "loadPriamStore" :
populatePriamStore(assetsObject);
break;
case "buyPriamItem" :
handleBoughtPriamItem(assetsObject);
break;
As you might gather here, this repeats all the same validation, but also dispatches to actual actions. Rhys suggests a solution which would likely be close to my own: an object acting as a lookup table with callback functions as the values, e.g.:
let action = {
//…
"loadPriamStore": () => populatePriamStore(assetsObject),
"buyPriamItem": () => handleBoughtPriamItem(assetsObject)
}
if (propertyName in action) action[propertyName]();
The original code was probably written in an older dialect of JavaScript, but the basic principle is viable, with perhaps some minor modifications to properly enclose the data required by the callbacks, or maybe ways to break the actions into categories and have different lookup tables for different categories of action, so that nothing gets too gigantic. One can nitpick the solutions, but the one that doesn't involve a nearly 300 line switch
is probably the right one.
Rhys adds: "I'm able to share this because on the live site, none of the code is minified or obfuscated."
Метки: CodeSOD |
Error'd: Down the Rarebit Hole |
This week's Error'd episode includes a bonus submission from our good friend A. Bargle, at no extra charge. TRWTF is in the browser, perhaps, but it's just so gosh darn funny. It's likely that what tickles me is entirely inscrutable to all of you (especially those coming to English as a second language), and if so, I apologize. But at least one of us is having a good time!
Not actually a hydrologist Carl C. has uncovered evidence of an entire cadre of wayward rivers roamin' the wikipedian underground. "I got so excited by this that I forgot why I was looking up the Nueces River on Wikipedia." It's not just the poor lost Rubicon!
Cheesemaster Phil Adelphia striving to engage the TDWTF editors' punsterism, suggests "Microsoft Edge knows the Welsh language when it sees it."
Possibly Welsh Simon T. waffles "I think I'm going to see the film."
Probably not Welsh Henry lets his fingers do the talking: "Innovation never ceases at Ticketmaster UK where, instead of stringly typing ints, they prefer to intly type strings."
Topical Michael rips a comment straight from the latest headlines: "That whole Russia / Ukraine thing gives that 'undefined country' a completely new meaning."
Alexa acolyte Timothy W. announces "Ah! source of infinite knowledge! I also found out carrots last eight minutes if kept outside the fridge."
And now the promised bonus from reliable Argle Bargle: "Snarky comment? You already took all the fun out of it. :-D" After posting our own page back to us in a deliciously meta riposte, Friend Argle found the fault in his stars and described in last week's comments: "It never dawned on me that my ad blocker would actually analyze an image that I created by screen capture." It never occurred to me, either, buddy Bargle. Here's hoping that the meta-meta post is equally meta-invisible. Comment below!
Метки: Error'd |
Double Bagged |
Many years ago, Yazeran needed to work with some floor-plan data. Now, fortunately for Yazeran, a vendor had already collected this information and packed it up behind a SOAP-based web service, so it would in theory be easy to get. This was long enough ago that SOAP was all the rage, and computers with multiple gigabytes of RAM were still on the higher end of things.
In fact, there was an end-point called GetFullDataExtract
which promised to, as the name implies, get him all the data. Yazeran didn't need all the data, but the other end-point, GetGMLBuildings
returned only the subset of data Yazeran didn't need. So Yazeran simply had to request too much.
Yazeran fired up a wget
session with all the appropriate credentials and waited. And waited. And waited…
When the 20MB XML response finally downloaded, minutes later, it looked like this:
<string xmlns="http://tempuri.org/"><DataExtract>
<Location ID="13" MasterID="13" Name="Nunich " IsMunicipality="true" ParentMasterID="" DisplayName="Munich ">
<Estate ID="31" MasterID="10" LocationID="13" LocationMasterID="13" Name="Munich Campus" Description="" DisplayName="Munich Campus">
<Building ID="6462" MasterID="323" EstateID="31" EstateMasterID="10" Name="Munich" Description="" Address="Kings Road" AlternativeName="" Number="15" City="Munich" ZipCode="6542" BasementGrossArea="17474.495491066667" GrossArea="56913.5818959713" NetArea="47924.600000000079" DisplayName="Munich">
<GIS>
<OuterPolygon>
<Coordinate x="45.73155848600081" y="11.395289797465072" />
<Coordinate x="45.73133750252103" y="11.396345176530383" />
<Coordinate x="45.73158863712057" y="11.396510410132915" />
</OuterPolygon>
</GIS>
<Drawing ID="8587" MasterID="1010" BuildingID="6462" BuildingMasterID="198" EstateID="31" EstateMasterID="8" DrawingTypeID="3">
<Floor ID="8640" MasterID="1010" FloorNumber="0" FloorType="1" DrawingID="8587" GrossArea="131.99712099999974" DrawingMasterID="1010">
<Room ID="364742" MasterID="41627" DrawingMasterID="1010" FloorMasterID="1010" NetArea="132" EstimatedGrossArea="132">
<LayerData ID="231389" LayerFieldID="24" Name="ROOM NR." Value="g01" />
<LayerData ID="231390" LayerFieldID="27" Name="TYPE" Value="ROOM" />
<LayerData ID="231391" LayerFieldID="28" Name="CATEGORY" Value="STORRAGE" />
<LayerData ID="231392" LayerFieldID="30" Name="BRUGER" Value="PHYSICS" />
<LayerData ID="231393" LayerFieldID="34" Name="x_UID" Value="B712D571AB564658999E75D65543" />
</Room>
</Floor>
</Drawing>
</Building>
</Estate>
</Location>
<Location ID="14"....
</Building>
</Estate>
</Location>
</DataExtract>string>
They produced an XML document with one element,
and within that element put an escaped version of another XML document.
XML is large and bureaucratic and complicated, but that complexity comes with benefits- namespaces, schemas, validation, and so on. None of that is possible when you've just mashed your XML into text and then wrapped the text in XML again.
There are other problems with this, beside the obvious. XML parsers are notoriously memory intensive. Your core options are a DOM-based parser, which loads the entire document in memory, and thus is very expensive, or a SAX-based parser, which streams the document and emits events as it encounters nodes in the document. SAX is more difficult to use, but is potentially faster and definitely more memory efficient.
Unless, of course, your XML document just contains one giant text node. In that case, SAX has to load the entire document into memory anyway, and all those efficiency gains vanish. In fact, when Yazeran tried to parse this as-is, it took 40+ minutes and involved a lot of paging.
Now Yazeran was using Perl to do this work, and fixed this with the obvious tool at hand: regexes. No, no parsing XML with regexes, but "un-escaping" the internal text. Since Yazeran was already munging some data, it was easy to add a few more regexes which stripped out the unnecessary data in the document, like the large blocks of Coordinate
elements.
That shrunk the document down to a reasonable size that could be parsed using a DOM parser in under 5 minutes.
Yazeran sums it up thus:
It corresponds to writing a letter, putting it in an envelope, adding postage, and then instead of sending the letter, putting it into yet another envelope and adding postage before sending… (in effect paying twice for the same service)
Метки: Feature Articles |
CodeSOD: Swwwitch |
We're going to do something a little different. I don't like to do posts about game related code. Games are entirely about shipping something out the door on tight timelines and tight budgets, and it's very much the category of "if it works it's good". There are exceptions, like when you ship an actual WTF, but "bad game code" is not really that interesting.
Awhile back, indie game VVVVVV went open source which gives us a picture of how the sausage is actually made. Now, this is emphatically not a WTF, this isn't wrong or a mistake, this is just the kind of thing that gets a game shipped, especially when it's a small budget indie game, by basically one person.
So instead of getting angry or annoyed, let's just marvel at this 3,440 line switch
which represents the game's main loop.
switch(state)
{
case 0:
//Do nothing here! Standard game state
break;
case 1:
//Game initilisation
state = 0;
break;
case 2:
//Opening cutscene
advancetext = true;
hascontrol = false;
state = 3;
dwgfx.createtextbox("To do: write quick", 50, 80, 164, 164, 255);
dwgfx.addline("intro to story!");
//Oh no! what happen to rest of crew etc crash into dimension
break;
case 4:
//End of opening cutscene for now
dwgfx.createtextbox(" Press arrow keys or WASD to move ", -1, 195, 174, 174, 174);
dwgfx.textboxtimer(60);
state = 0;
break;
case 5:
//Demo over
advancetext = true;
hascontrol = false;
/*dwgfx.createtextbox(" Prototype Complete ", 50, 80, 164, 164, 255);
dwgfx.addline("Congrats! More Info Soon!");
dwgfx.textboxcenter();
*/
startscript = true;
newscript="returntohub";
obj.removetrigger(5);
state = 6;
break;
case 7:
//End of opening cutscene for now
dwgfx.textboxremove();
hascontrol = true;
advancetext = false;
state = 0;
break;
case 8:
//Enter dialogue
obj.removetrigger(8);
if (obj.flags[13] == 0)
{
obj.changeflag(13, 1);
dwgfx.createtextbox(" Press ENTER to view map ", -1, 155, 174, 174, 174);
dwgfx.addline(" and quicksave");
dwgfx.textboxtimer(60);
}
state = 0;
break;
case 9:
//Start SWN Minigame Mode B
obj.removetrigger(9);
swnmode = true;
swngame = 6;
swndelay = 150;
swntimer = 60 * 30;
//set the checkpoint in the middle of the screen
savepoint = 0;
savex = 148;
savey = 100;
savegc = 0;
saverx = roomx;
savery = roomy;
savedir = 0;
state = 0;
break;
case 10:
//Start SWN Minigame Mode A
obj.removetrigger(10);
swnmode = true;
swngame = 4;
swndelay = 150;
swntimer = 60 * 30;
//set the checkpoint in the middle of the screen
savepoint = 0;
savex = 148;
savey = 100;
savegc = 0;
saverx = roomx;
savery = roomy;
savedir = 0;
state = 0;
break;
case 11:
//Intermission 1 instructional textbox, depends on last saved
dwgfx.textboxremovefast();
dwgfx.createtextbox(" When you're NOT standing on ", -1, 3, 174, 174, 174);
if (dwgfx.flipmode)
{
if (lastsaved == 2)
{
dwgfx.addline(" the ceiling, Vitellary will");
}
else if (lastsaved == 3)
{
dwgfx.addline(" the ceiling, Vermilion will");
}
else if (lastsaved == 4)
{
dwgfx.addline(" the ceiling, Verdigris will");
}
else if (lastsaved == 5)
{
dwgfx.addline(" the ceiling, Victoria will");
}
}
else
{
if (lastsaved == 2)
{
dwgfx.addline(" the floor, Vitellary will");
}
else if (lastsaved == 3)
{
dwgfx.addline(" the floor, Vermilion will");
}
else if (lastsaved == 4)
{
dwgfx.addline(" the floor, Verdigris will");
}
else if (lastsaved == 5)
{
dwgfx.addline(" the floor, Victoria will");
}
}
dwgfx.addline(" stop and wait for you.");
dwgfx.textboxtimer(180);
state = 0;
break;
case 12:
//Intermission 1 instructional textbox, depends on last saved
obj.removetrigger(12);
if (obj.flags[61] == 0)
{
obj.changeflag(61, 1);
dwgfx.textboxremovefast();
dwgfx.createtextbox(" You can't continue to the next ", -1, 8, 174, 174, 174);
if (lastsaved == 5)
{
dwgfx.addline(" room until she is safely across. ");
}
else
{
dwgfx.addline(" room until he is safely across. ");
}
dwgfx.textboxtimer(120);
}
state = 0;
break;
case 13:
//textbox removal
obj.removetrigger(13);
dwgfx.textboxremovefast();
state = 0;
break;
case 14:
//Intermission 1 instructional textbox, depends on last saved
if (dwgfx.flipmode)
{
dwgfx.createtextbox(" When you're standing on the ceiling, ", -1, 3, 174, 174, 174);
}
else
{
dwgfx.createtextbox(" When you're standing on the floor, ", -1, 3, 174, 174, 174);
}
if (lastsaved == 2)
{
dwgfx.addline(" Vitellary will try to walk to you. ");
}
else if (lastsaved == 3)
{
dwgfx.addline(" Vermilion will try to walk to you. ");
}
else if (lastsaved == 4)
{
dwgfx.addline(" Verdigris will try to walk to you. ");
}
else if (lastsaved == 5)
{
dwgfx.addline(" Victoria will try to walk to you. ");
}
dwgfx.textboxtimer(280);
state = 0;
break;
case 15:
//leaving the naughty corner
obj.entities[obj.getplayer()].tile = 0;
state = 0;
break;
case 16:
//entering the naughty corner
if(obj.entities[obj.getplayer()].tile == 0)
{
obj.entities[obj.getplayer()].tile = 144;
music.playef(2, 10);
}
state = 0;
break;
case 17:
//Arrow key tutorial
obj.removetrigger(17);
dwgfx.createtextbox(" If you prefer, you can press UP or ", -1, 195, 174, 174, 174);
dwgfx.addline(" DOWN instead of ACTION to flip.");
dwgfx.textboxtimer(100);
state = 0;
break;
case 20:
if (obj.flags[1] == 0)
{
obj.changeflag(1, 1);
state = 0;
dwgfx.textboxremove();
}
obj.removetrigger(20);
break;
case 21:
if (obj.flags[2] == 0)
{
obj.changeflag(2, 1);
state = 0;
dwgfx.textboxremove();
}
obj.removetrigger(21);
break;
case 22:
if (obj.flags[3] == 0)
{
dwgfx.textboxremovefast();
obj.changeflag(3, 1);
state = 0;
dwgfx.createtextbox(" Press ACTION to flip ", -1, 25, 174, 174, 174);
dwgfx.textboxtimer(60);
}
obj.removetrigger(22);
break;
case 30:
//Generic "run script"
if (obj.flags[4] == 0)
{
obj.changeflag(4, 1);
startscript = true;
newscript="firststeps";
state = 0;
}
obj.removetrigger(30);
state = 0;
break;
case 31:
//state = 55; statedelay = 50;
state = 0;
statedelay = 0;
if (obj.flags[6] == 0)
{
obj.changeflag(6, 1);
obj.changeflag(5, 1);
startscript = true;
newscript="communicationstation";
state = 0;
statedelay = 0;
}
obj.removetrigger(31);
break;
case 32:
//Generic "run script"
if (obj.flags[7] == 0)
{
obj.changeflag(7, 1);
startscript = true;
newscript="teleporterback";
state = 0;
}
obj.removetrigger(32);
state = 0;
break;
case 33:
//Generic "run script"
if (obj.flags[9] == 0)
{
obj.changeflag(9, 1);
startscript = true;
newscript="rescueblue";
state = 0;
}
obj.removetrigger(33);
state = 0;
break;
case 34:
//Generic "run script"
if (obj.flags[10] == 0)
{
obj.changeflag(10, 1);
startscript = true;
newscript="rescueyellow";
state = 0;
}
obj.removetrigger(34);
state = 0;
break;
case 35:
//Generic "run script"
if (obj.flags[11] == 0)
{
obj.changeflag(11, 1);
startscript = true;
newscript="rescuegreen";
state = 0;
}
obj.removetrigger(35);
state = 0;
break;
case 36:
//Generic "run script"
if (obj.flags[8] == 0)
{
obj.changeflag(8, 1);
startscript = true;
newscript="rescuered";
state = 0;
}
obj.removetrigger(36);
state = 0;
break;
case 37:
//Generic "run script"
if (companion == 0)
{
startscript = true;
newscript="int2_yellow";
state = 0;
}
obj.removetrigger(37);
state = 0;
break;
case 38:
//Generic "run script"
if (companion == 0)
{
startscript = true;
newscript="int2_red";
state = 0;
}
obj.removetrigger(38);
state = 0;
break;
case 39:
//Generic "run script"
if (companion == 0)
{
startscript = true;
newscript="int2_green";
state = 0;
}
obj.removetrigger(39);
state = 0;
break;
case 40:
//Generic "run script"
if (companion == 0)
{
startscript = true;
newscript="int2_blue";
state = 0;
}
obj.removetrigger(40);
state = 0;
break;
case 41:
//Generic "run script"
if (obj.flags[60] == 0)
{
obj.changeflag(60, 1);
startscript = true;
if (lastsaved == 2)
{
newscript = "int1yellow_2";
}
else if (lastsaved == 3)
{
newscript = "int1red_2";
}
else if (lastsaved == 4)
{
newscript = "int1green_2";
}
else if (lastsaved == 5)
{
newscript = "int1blue_2";
}
state = 0;
}
obj.removetrigger(41);
state = 0;
break;
case 42:
//Generic "run script"
if (obj.flags[62] == 0)
{
obj.changeflag(62, 1);
startscript = true;
if (lastsaved == 2)
{
newscript = "int1yellow_3";
}
else if (lastsaved == 3)
{
newscript = "int1red_3";
}
else if (lastsaved == 4)
{
newscript = "int1green_3";
}
else if (lastsaved == 5)
{
newscript = "int1blue_3";
}
state = 0;
}
obj.removetrigger(42);
state = 0;
break;
case 43:
//Generic "run script"
if (obj.flags[63] == 0)
{
obj.changeflag(63, 1);
startscript = true;
if (lastsaved == 2)
{
newscript = "int1yellow_4";
}
else if (lastsaved == 3)
{
newscript = "int1red_4";
}
else if (lastsaved == 4)
{
newscript = "int1green_4";
}
else if (lastsaved == 5)
{
newscript = "int1blue_4";
}
state = 0;
}
obj.removetrigger(43);
state = 0;
break;
case 44:
//Generic "run script"
if (obj.flags[64] == 0)
{
obj.changeflag(64, 1);
startscript = true;
if (lastsaved == 2)
{
newscript = "int1yellow_5";
}
else if (lastsaved == 3)
{
newscript = "int1red_5";
}
else if (lastsaved == 4)
{
newscript = "int1green_5";
}
else if (lastsaved == 5)
{
newscript = "int1blue_5";
}
state = 0;
}
obj.removetrigger(44);
state = 0;
break;
case 45:
//Generic "run script"
if (obj.flags[65] == 0)
{
obj.changeflag(65, 1);
startscript = true;
if (lastsaved == 2)
{
newscript = "int1yellow_6";
}
else if (lastsaved == 3)
{
newscript = "int1red_6";
}
else if (lastsaved == 4)
{
newscript = "int1green_6";
}
else if (lastsaved == 5)
{
newscript = "int1blue_6";
}
state = 0;
}
obj.removetrigger(45);
state = 0;
break;
case 46:
//Generic "run script"
if (obj.flags[66] == 0)
{
obj.changeflag(66, 1);
startscript = true;
if (lastsaved == 2)
{
newscript = "int1yellow_7";
}
else if (lastsaved == 3)
{
newscript = "int1red_7";
}
else if (lastsaved == 4)
{
newscript = "int1green_7";
}
else if (lastsaved == 5)
{
newscript = "int1blue_7";
}
state = 0;
}
obj.removetrigger(46);
state = 0;
break;
case 47:
//Generic "run script"
if (obj.flags[69] == 0)
{
obj.changeflag(69, 1);
startscript = true;
newscript="trenchwarfare";
state = 0;
}
obj.removetrigger(47);
state = 0;
break;
case 48:
//Generic "run script"
if (obj.flags[70] == 0)
{
obj.changeflag(70, 1);
startscript = true;
newscript="trinketcollector";
state = 0;
}
obj.removetrigger(48);
state = 0;
break;
case 49:
//Start final level music
if (obj.flags[71] == 0)
{
obj.changeflag(71, 1);
music.niceplay(15); //Final level remix
state = 0;
}
obj.removetrigger(49);
state = 0;
break;
case 50:
music.playef(15, 10);
dwgfx.createtextbox("Help! Can anyone hear", 35, 15, 255, 134, 255);
dwgfx.addline("this message?");
dwgfx.textboxtimer(60);
state++;
statedelay = 100;
break;
case 51:
music.playef(15, 10);
dwgfx.createtextbox("Verdigris? Are you out", 30, 12, 255, 134, 255);
dwgfx.addline("there? Are you ok?");
dwgfx.textboxtimer(60);
state++;
statedelay = 100;
break;
case 52:
music.playef(15, 10);
dwgfx.createtextbox("Please help us! We've crashed", 5, 22, 255, 134, 255);
dwgfx.addline("and need assistance!");
dwgfx.textboxtimer(60);
state++;
statedelay = 100;
break;
case 53:
music.playef(15, 10);
dwgfx.createtextbox("Hello? Anyone out there?", 40, 15, 255, 134, 255);
dwgfx.textboxtimer(60);
state++;
statedelay = 100;
break;
case 54:
music.playef(15, 10);
dwgfx.createtextbox("This is Doctor Violet from the", 5, 8, 255, 134, 255);
dwgfx.addline("D.S.S. Souleye! Please respond!");
dwgfx.textboxtimer(60);
state++;
statedelay = 100;
break;
case 55:
music.playef(15, 10);
dwgfx.createtextbox("Please... Anyone...", 45, 14, 255, 134, 255);
dwgfx.textboxtimer(60);
state++;
statedelay = 100;
break;
case 56:
music.playef(15, 10);
dwgfx.createtextbox("Please be alright, everyone...", 25, 18, 255, 134, 255);
dwgfx.textboxtimer(60);
state=50;
statedelay = 100;
break;
case 80:
//Used to return to menu from the game
if(dwgfx.fademode == 1) state++;
break;
case 81:
gamestate = 1;
dwgfx.fademode = 4;
music.play(6);
dwgfx.backgrounddrawn = false;
map.tdrawback = true;
dwgfx.flipmode = false;
createmenu("mainmenu");
state = 0;
break;
case 82:
//Time Trial Complete!
obj.removetrigger(82);
hascontrol = false;
timetrialresulttime = seconds + (minutes * 60);
timetrialrank = 0;
if (timetrialresulttime <= timetrialpar) timetrialrank++;
if (trinkets >= timetrialshinytarget) timetrialrank++;
if (deathcounts == 0) timetrialrank++;
if (timetrialresulttime < besttimes[timetriallevel] || besttimes[timetriallevel]==-1)
{
besttimes[timetriallevel] = timetrialresulttime;
}
if (trinkets > besttrinkets[timetriallevel] || besttrinkets[timetriallevel]==-1)
{
besttrinkets[timetriallevel] = trinkets;
}
if (deathcounts < bestlives[timetriallevel] || bestlives[timetriallevel]==-1)
{
bestlives[timetriallevel] = deathcounts;
}
if (timetrialrank > bestrank[timetriallevel] || bestrank[timetriallevel]==-1)
{
bestrank[timetriallevel] = timetrialrank;
if(timetrialrank>=3){
if(timetriallevel==0) NETWORK_unlockAchievement("vvvvvvtimetrial_station1_fixed");
if(timetriallevel==1) NETWORK_unlockAchievement("vvvvvvtimetrial_lab_fixed");
if(timetriallevel==2) NETWORK_unlockAchievement("vvvvvvtimetrial_tower_fixed");
if(timetriallevel==3) NETWORK_unlockAchievement("vvvvvvtimetrial_station2_fixed");
if(timetriallevel==4) NETWORK_unlockAchievement("vvvvvvtimetrial_warp_fixed");
if(timetriallevel==5) NETWORK_unlockAchievement("vvvvvvtimetrial_final_fixed");
}
}
savestats(map, dwgfx);
dwgfx.fademode = 2;
music.fadeout();
state++;
break;
case 83:
frames--;
if(dwgfx.fademode == 1) state++;
break;
case 84:
dwgfx.flipmode = false;
gamestate = 1;
dwgfx.fademode = 4;
dwgfx.backgrounddrawn = true;
map.tdrawback = true;
createmenu("timetrialcomplete");
state = 0;
break;
case 85:
//Cutscene skip version of final level change
obj.removetrigger(85);
//Init final stretch
state++;
music.playef(9, 10);
music.play(2);
obj.flags[72] = 1;
screenshake = 10;
flashlight = 5;
map.finalstretch = true;
map.warpx = false;
map.warpy = false;
map.background = 6;
map.final_colormode = true;
map.final_colorframe = 1;
state = 0;
break;
//From 90-100 are run scripts for the eurogamer expo only, remove later
case 90:
//Generic "run script"
startscript = true;
newscript="startexpolevel_station1";
obj.removetrigger(90);
state = 0;
break;
case 91:
//Generic "run script"
startscript = true;
newscript="startexpolevel_lab";
obj.removetrigger(91);
state = 0;
break;
case 92:
//Generic "run script"
startscript = true;
newscript="startexpolevel_warp";
obj.removetrigger(92);
state = 0;
break;
case 93:
//Generic "run script"
startscript = true;
newscript="startexpolevel_tower";
obj.removetrigger(93);
state = 0;
break;
case 94:
//Generic "run script"
startscript = true;
newscript="startexpolevel_station2";
obj.removetrigger(94);
state = 0;
break;
case 95:
//Generic "run script"
startscript = true;
newscript="startexpolevel_final";
obj.removetrigger(95);
state = 0;
break;
case 96:
//Used to return to gravitron to game
if(dwgfx.fademode == 1) state++;
break;
case 97:
gamestate = 0;
dwgfx.fademode = 4;
startscript = true;
newscript="returntolab";
state = 0;
break;
case 100:
//
// Meeting crewmate in the warpzone
//
obj.removetrigger(100);
if (obj.flags[4] == 0)
{
obj.changeflag(4, 1);
state++;
}
break;
case 101:
{
i = obj.getplayer();
hascontrol = false;
if (obj.entities[i].onroof > 0 && gravitycontrol == 1)
{
gravitycontrol = 0;
music.playef(1, 10);
}
if (obj.entities[i].onground > 0)
{
state++;
}
}
break;
case 102:
{
companion = 6;
i = obj.getcompanion(6);
obj.entities[i].tile = 0;
obj.entities[i].state = 1;
advancetext = true;
hascontrol = false;
dwgfx.createtextbox("Captain! I've been so worried!", 60, 90, 164, 255, 164);
state++;
music.playef(12, 10);
}
break;
case 104:
dwgfx.createtextbox("I'm glad you're ok!", 135, 152, 164, 164, 255);
state++;
music.playef(11, 10);
dwgfx.textboxactive();
break;
case 106:
{
dwgfx.createtextbox("I've been trying to find a", 74, 70, 164, 255, 164);
dwgfx.addline("way out, but I keep going");
dwgfx.addline("around in circles...");
state++;
music.playef(2, 10);
dwgfx.textboxactive();
i = obj.getcompanion(6);
obj.entities[i].tile = 54;
obj.entities[i].state = 0;
}
break;
case 108:
dwgfx.createtextbox("Don't worry! I have a", 125, 152, 164, 164, 255);
dwgfx.addline("teleporter key!");
state++;
music.playef(11, 10);
dwgfx.textboxactive();
break;
case 110:
{
i = obj.getcompanion(6);
obj.entities[i].tile = 0;
obj.entities[i].state = 1;
dwgfx.createtextbox("Follow me!", 185, 154, 164, 164, 255);
state++;
music.playef(11, 10);
dwgfx.textboxactive();
}
break;
case 112:
dwgfx.textboxremove();
hascontrol = true;
advancetext = false;
state = 0;
break;
case 115:
//
// Test script for space station, totally delete me!
//
{
i = obj.getplayer();
hascontrol = false;
state++;
}
break;
case 116:
advancetext = true;
hascontrol = false;
dwgfx.createtextbox("Sorry Eurogamers! Teleporting around", 60 - 20, 200, 255, 64, 64);
dwgfx.addline("the map doesn't work in this version!");
dwgfx.textboxcenterx();
state++;
break;
case 118:
dwgfx.textboxremove();
hascontrol = true;
advancetext = false;
state = 0;
break;
case 120:
//
// Meeting crewmate in the space station
//
obj.removetrigger(120);
if (obj.flags[5] == 0)
{
obj.changeflag(5, 1);
state++;
}
break;
case 121:
{
i = obj.getplayer();
hascontrol = false;
if (obj.entities[i].onground > 0 && gravitycontrol == 0)
{
gravitycontrol = 1;
music.playef(1, 10);
}
if (obj.entities[i].onroof > 0)
{
state++;
}
}
break;
case 122:
companion = 7;
i = obj.getcompanion(7);
obj.entities[i].tile = 6;
obj.entities[i].state = 1;
advancetext = true;
hascontrol = false;
dwgfx.createtextbox("Captain! You're ok!", 60-10, 90-40, 255, 255, 134);
state++;
music.playef(14, 10);
break;
case 124:
dwgfx.createtextbox("I've found a teleporter, but", 60-20, 90 - 40, 255, 255, 134);
dwgfx.addline("I can't get it to go anywhere...");
state++;
music.playef(2, 10);
dwgfx.textboxactive();
i = obj.getcompanion(7); //obj.entities[i].tile = 66; obj.entities[i].state = 0;
break;
case 126:
dwgfx.createtextbox("I can help with that!", 125, 152-40, 164, 164, 255);
state++;
music.playef(11, 10);
dwgfx.textboxactive();
break;
case 128:
dwgfx.createtextbox("I have the teleporter", 130, 152-35, 164, 164, 255);
dwgfx.addline("codex for our ship!");
state++;
music.playef(11, 10);
dwgfx.textboxactive();
break;
case 130:
dwgfx.createtextbox("Yey! Let's go home!", 60-30, 90-35, 255, 255, 134);
state++;
music.playef(14, 10);
dwgfx.textboxactive();
i = obj.getcompanion(7);
obj.entities[i].tile = 6;
obj.entities[i].state = 1;
break;
case 132:
dwgfx.textboxremove();
hascontrol = true;
advancetext = false;
state = 0;
break;
case 200:
//Init final stretch
state++;
music.playef(9, 10);
//music.play(2);
obj.flags[72] = 1;
screenshake = 10;
flashlight = 5;
map.finalstretch = true;
map.warpx = false;
map.warpy = false;
map.background = 6;
map.final_colormode = true;
map.final_colorframe = 1;
startscript = true;
newscript="finalterminal_finish";
state = 0;
break;
case 300:
startscript = true;
newscript="custom_"+customscript[0];
obj.removetrigger(300);
state = 0;
break;
case 301:
startscript = true;
newscript="custom_"+customscript[1];
obj.removetrigger(301);
state = 0;
break;
case 302:
startscript = true;
newscript="custom_"+customscript[2];
obj.removetrigger(302);
state = 0;
break;
case 303:
startscript = true;
newscript="custom_"+customscript[3];
obj.removetrigger(303);
state = 0;
break;
case 304:
startscript = true;
newscript="custom_"+customscript[4];
obj.removetrigger(304);
state = 0;
break;
case 305:
startscript = true;
newscript="custom_"+customscript[5];
obj.removetrigger(305);
state = 0;
break;
case 306:
startscript = true;
newscript="custom_"+customscript[6];
obj.removetrigger(306);
state = 0;
break;
case 307:
startscript = true;
newscript="custom_"+customscript[7];
obj.removetrigger(307);
state = 0;
break;
case 308:
startscript = true;
newscript="custom_"+customscript[8];
obj.removetrigger(308);
state = 0;
break;
case 309:
startscript = true;
newscript="custom_"+customscript[9];
obj.removetrigger(309);
state = 0;
break;
case 310:
startscript = true;
newscript="custom_"+customscript[10];
obj.removetrigger(310);
state = 0;
break;
case 311:
startscript = true;
newscript="custom_"+customscript[11];
obj.removetrigger(311);
state = 0;
break;
case 312:
startscript = true;
newscript="custom_"+customscript[12];
obj.removetrigger(312);
state = 0;
break;
case 313:
startscript = true;
newscript="custom_"+customscript[13];
obj.removetrigger(313);
state = 0;
break;
case 314:
startscript = true;
newscript="custom_"+customscript[14];
obj.removetrigger(314);
state = 0;
break;
case 315:
startscript = true;
newscript="custom_"+customscript[15];
obj.removetrigger(315);
state = 0;
break;
case 316:
startscript = true;
newscript="custom_"+customscript[16];
obj.removetrigger(316);
state = 0;
break;
case 317:
startscript = true;
newscript="custom_"+customscript[17];
obj.removetrigger(317);
state = 0;
break;
case 318:
startscript = true;
newscript="custom_"+customscript[18];
obj.removetrigger(318);
state = 0;
break;
case 319:
startscript = true;
newscript="custom_"+customscript[19];
obj.removetrigger(319);
state = 0;
break;
case 320:
startscript = true;
newscript="custom_"+customscript[20];
obj.removetrigger(320);
state = 0;
break;
case 321:
startscript = true;
newscript="custom_"+customscript[21];
obj.removetrigger(321);
state = 0;
break;
case 322:
startscript = true;
newscript="custom_"+customscript[22];
obj.removetrigger(322);
state = 0;
break;
case 323:
startscript = true;
newscript="custom_"+customscript[23];
obj.removetrigger(323);
state = 0;
break;
case 324:
startscript = true;
newscript="custom_"+customscript[24];
obj.removetrigger(324);
state = 0;
break;
case 325:
startscript = true;
newscript="custom_"+customscript[25];
obj.removetrigger(325);
state = 0;
break;
case 326:
startscript = true;
newscript="custom_"+customscript[26];
obj.removetrigger(326);
state = 0;
break;
case 327:
startscript = true;
newscript="custom_"+customscript[27];
obj.removetrigger(327);
state = 0;
break;
case 328:
startscript = true;
newscript="custom_"+customscript[28];
obj.removetrigger(328);
state = 0;
break;
case 329:
startscript = true;
newscript="custom_"+customscript[29];
obj.removetrigger(329);
state = 0;
break;
case 330:
startscript = true;
newscript="custom_"+customscript[30];
obj.removetrigger(330);
state = 0;
break;
case 331:
startscript = true;
newscript="custom_"+customscript[31];
obj.removetrigger(331);
state = 0;
break;
case 332:
startscript = true;
newscript="custom_"+customscript[32];
obj.removetrigger(332);
state = 0;
break;
case 333:
startscript = true;
newscript="custom_"+customscript[33];
obj.removetrigger(333);
state = 0;
break;
case 334:
startscript = true;
newscript="custom_"+customscript[34];
obj.removetrigger(334);
state = 0;
break;
case 335:
startscript = true;
newscript="custom_"+customscript[35];
obj.removetrigger(335);
state = 0;
break;
case 336:
startscript = true;
newscript="custom_"+customscript[36];
obj.removetrigger(336);
state = 0;
break;
case 1000:
dwgfx.showcutscenebars = true;
hascontrol = false;
completestop = true;
state++;
statedelay = 15;
break;
case 1001:
//Found a trinket!
advancetext = true;
state++;
if (dwgfx.flipmode)
{
dwgfx.createtextbox(" Congratulations! ", 50, 105, 174, 174, 174);
dwgfx.addline("");
dwgfx.addline("You have found a shiny trinket!");
dwgfx.textboxcenterx();
if(map.custommode)
{
dwgfx.createtextbox(" " + help.number(trinkets) + " out of " + help.number(map.customtrinkets)+ " ", 50, 65, 174, 174, 174);
dwgfx.textboxcenterx();
}
else
{
dwgfx.createtextbox(" " + help.number(trinkets) + " out of Twenty ", 50, 65, 174, 174, 174);
dwgfx.textboxcenterx();
}
}
else
{
dwgfx.createtextbox(" Congratulations! ", 50, 85, 174, 174, 174);
dwgfx.addline("");
dwgfx.addline("You have found a shiny trinket!");
dwgfx.textboxcenterx();
if(map.custommode)
{
dwgfx.createtextbox(" " + help.number(trinkets) + " out of " + help.number(map.customtrinkets)+ " ", 50, 135, 174, 174, 174);
dwgfx.textboxcenterx();
}
else
{
dwgfx.createtextbox(" " + help.number(trinkets) + " out of Twenty ", 50, 135, 174, 174, 174);
dwgfx.textboxcenterx();
}
}
break;
case 1003:
dwgfx.textboxremove();
hascontrol = true;
advancetext = false;
completestop = false;
state = 0;
//music.play(music.resumesong);
if(!muted && music.currentsong>-1) music.fadeMusicVolumeIn(3000);
dwgfx.showcutscenebars = false;
break;
case 1010:
dwgfx.showcutscenebars = true;
hascontrol = false;
completestop = true;
state++;
statedelay = 15;
break;
case 1011:
//Found a trinket!
advancetext = true;
state++;
if (dwgfx.flipmode)
{
dwgfx.createtextbox(" Congratulations! ", 50, 105, 174, 174, 174);
dwgfx.addline("");
dwgfx.addline("You have found a lost crewmate!");
dwgfx.textboxcenterx();
if(int(map.customcrewmates-crewmates)==0)
{
dwgfx.createtextbox(" All crewmates rescued! ", 50, 135, 174, 174, 174);
}
else if(map.customcrewmates-crewmates==1)
{
dwgfx.createtextbox(" " + help.number(int(map.customcrewmates-crewmates))+ " remains ", 50, 135, 174, 174, 174);
}
else
{
dwgfx.createtextbox(" " + help.number(int(map.customcrewmates-crewmates))+ " remain ", 50, 135, 174, 174, 174);
}
dwgfx.textboxcenterx();
}
else
{
dwgfx.createtextbox(" Congratulations! ", 50, 85, 174, 174, 174);
dwgfx.addline("");
dwgfx.addline("You have found a lost crewmate!");
dwgfx.textboxcenterx();
if(int(map.customcrewmates-crewmates)==0)
{
dwgfx.createtextbox(" All crewmates rescued! ", 50, 135, 174, 174, 174);
}
else if(map.customcrewmates-crewmates==1)
{
dwgfx.createtextbox(" " + help.number(int(map.customcrewmates-crewmates))+ " remains ", 50, 135, 174, 174, 174);
}
else
{
dwgfx.createtextbox(" " + help.number(int(map.customcrewmates-crewmates))+ " remain ", 50, 135, 174, 174, 174);
}
dwgfx.textboxcenterx();
}
break;
case 1013:
dwgfx.textboxremove();
hascontrol = true;
advancetext = false;
completestop = false;
state = 0;
if(map.customcrewmates-crewmates==0)
{
if(map.custommodeforreal)
{
dwgfx.fademode = 2;
if(!muted && ed.levmusic>0) music.fadeMusicVolumeIn(3000);
if(ed.levmusic>0) music.fadeout();
state=1014;
}
else
{
gamestate = EDITORMODE;
dwgfx.backgrounddrawn=false;
if(!muted && ed.levmusic>0) music.fadeMusicVolumeIn(3000);
if(ed.levmusic>0) music.fadeout();
}
}
else
{
if(!muted && ed.levmusic>0) music.fadeMusicVolumeIn(3000);
}
dwgfx.showcutscenebars = false;
break;
case 1014:
frames--;
if(dwgfx.fademode == 1) state++;
break;
case 1015:
dwgfx.flipmode = false;
gamestate = TITLEMODE;
dwgfx.fademode = 4;
music.play(6);
dwgfx.backgrounddrawn = true;
map.tdrawback = true;
//Update level stats
if(map.customcrewmates-crewmates==0)
{
//Finished level
if(map.customtrinkets-trinkets==0)
{
//and got all the trinkets!
updatecustomlevelstats(customlevelfilename, 3);
}
else
{
updatecustomlevelstats(customlevelfilename, 1);
}
}
createmenu("levellist");
state = 0;
break;
case 2000:
//Game Saved!
if (intimetrial || nodeathmode || inintermission)
{
state = 0;
}
else
{
savetele(map, obj, music);
if (dwgfx.flipmode)
{
dwgfx.createtextbox(" Game Saved ", -1, 202, 174, 174, 174);
dwgfx.textboxtimer(25);
}
else
{
dwgfx.createtextbox(" Game Saved ", -1, 12, 174, 174, 174);
dwgfx.textboxtimer(25);
}
state = 0;
}
break;
case 2500:
music.play(5);
//Activating a teleporter (appear)
state++;
statedelay = 15;
flashlight = 5;
screenshake = 90;
music.playef(9, 10);
break;
case 2501:
//Activating a teleporter 2
state++;
statedelay = 0;
flashlight = 5;
screenshake = 0;
//we're done here!
music.playef(10, 10);
break;
case 2502:
//Activating a teleporter 2
state++;
statedelay = 5;
i = obj.getplayer();
obj.entities[i].colour = 0;
obj.entities[i].invis = false;
obj.entities[i].xp = obj.entities[obj.getteleporter()].xp+44;
obj.entities[i].yp = obj.entities[obj.getteleporter()].yp+44;
obj.entities[i].ay = -6;
obj.entities[i].ax = 6;
obj.entities[i].vy = -6;
obj.entities[i].vx = 6;
i = obj.getteleporter();
obj.entities[i].tile = 1;
obj.entities[i].colour = 101;
break;
case 2503:
state++;
i = obj.getplayer();
obj.entities[i].xp += 10;
break;
case 2504:
state++;
i = obj.getplayer();
//obj.entities[i].xp += 10;
break;
case 2505:
state++;
i = obj.getplayer();
obj.entities[i].xp += 8;
break;
case 2506:
state++;
i = obj.getplayer();
obj.entities[i].xp += 6;
break;
case 2507:
state++;
i = obj.getplayer();
//obj.entities[i].xp += 4;
break;
case 2508:
state++;
i = obj.getplayer();
obj.entities[i].xp += 2;
break;
case 2509:
state++;
statedelay = 15;
i = obj.getplayer();
obj.entities[i].xp += 1;
break;
case 2510:
advancetext = true;
hascontrol = false;
dwgfx.createtextbox("Hello?", 125+24, 152-20, 164, 164, 255);
state++;
music.playef(11, 10);
dwgfx.textboxactive();
break;
case 2512:
advancetext = true;
hascontrol = false;
dwgfx.createtextbox("Is anyone there?", 125+8, 152-24, 164, 164, 255);
state++;
music.playef(11, 10);
dwgfx.textboxactive();
break;
case 2514:
dwgfx.textboxremove();
hascontrol = true;
advancetext = false;
state = 0;
music.play(3);
break;
case 3000:
//Activating a teleporter (long version for level complete)
state++;
statedelay = 30;
flashlight = 5;
screenshake = 90;
music.playef(9, 10);
break;
case 3001:
//Activating a teleporter 2
state++;
statedelay = 15;
flashlight = 5;
music.playef(9, 10);
break;
case 3002:
//Activating a teleporter 2
state++;
statedelay = 15;
flashlight = 5;
music.playef(9, 10);
break;
case 3003:
//Activating a teleporter 2
state++;
statedelay = 15;
flashlight = 5;
music.playef(9, 10);
break;
case 3004:
//Activating a teleporter 2
state++;
statedelay = 0;
flashlight = 5;
screenshake = 0;
//we're done here!
music.playef(10, 10);
break;
case 3005:
//Activating a teleporter 2
state++;
statedelay = 50;
//testing!
//state = 3006; //Warp Zone
//state = 3020; //Space Station
switch(companion)
{
case 6:
state = 3006;
break; //Warp Zone
case 7:
state = 3020;
break; //Space Station
case 8:
state = 3040;
break; //Lab
case 9:
state = 3060;
break; //Tower
case 10:
state = 3080;
break; //Intermission 2
case 11:
state = 3085;
break; //Intermission 1
}
i = obj.getplayer();
obj.entities[i].colour = 0;
obj.entities[i].invis = true;
i = obj.getcompanion(companion);
if(i>-1)
{
obj.entities[i].active = false;
}
i = obj.getteleporter();
obj.entities[i].tile = 1;
obj.entities[i].colour = 100;
break;
case 3006:
//Level complete! (warp zone)
unlocknum(4, map, dwgfx);
lastsaved = 4;
music.play(0);
state++;
statedelay = 75;
if (dwgfx.flipmode)
{
dwgfx.createtextbox("", -1, 180, 165, 165, 255);
}
else
{
dwgfx.createtextbox("", -1, 12, 165, 165, 255);
}
//dwgfx.addline(" Level Complete! ");
dwgfx.addline(" ");
dwgfx.addline("");
dwgfx.addline("");
dwgfx.textboxcenterx();
/* advancetext = true;
hascontrol = false;
state = 3;
dwgfx.createtextbox("To do: write quick", 50, 80, 164, 164, 255);
dwgfx.addline("intro to story!");*/
break;
case 3007:
state++;
statedelay = 45;
if (dwgfx.flipmode)
{
dwgfx.createtextbox("", -1, 104, 175,174,174);
}
else
{
dwgfx.createtextbox("", -1, 64+8+16, 175,174,174);
}
dwgfx.addline(" You have rescued ");
dwgfx.addline(" a crew member! ");
dwgfx.addline("");
dwgfx.textboxcenterx();
break;
case 3008:
state++;
statedelay = 45;
temp = 6 - crewrescued();
if (temp == 1)
{
tempstring = " One remains ";
if (dwgfx.flipmode)
{
dwgfx.createtextbox(tempstring, -1, 72, 174, 174, 174);
}
else
{
dwgfx.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
}
}
else if (temp > 0)
{
tempstring = " " + help.number(temp) + " remain ";
if (dwgfx.flipmode)
{
dwgfx.createtextbox(tempstring, -1, 72, 174, 174, 174);
}
else
{
dwgfx.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
}
}
else
{
if (dwgfx.flipmode)
{
dwgfx.createtextbox(" All Crew Members Rescued! ", -1, 72, 174, 174, 174);
}
else
{
dwgfx.createtextbox(" All Crew Members Rescued! ", -1, 128+16, 174, 174, 174);
}
}
dwgfx.textboxcenterx();
break;
case 3009:
state++;
statedelay = 0;
if (dwgfx.flipmode)
{
dwgfx.createtextbox(" Press ACTION to continue ", -1, 20, 164, 164, 255);
}
else
{
dwgfx.createtextbox(" Press ACTION to continue ", -1, 196, 164, 164, 255);
}
dwgfx.textboxcenterx();
break;
case 3010:
if (jumppressed)
{
state++;
statedelay = 30;
dwgfx.textboxremove();
}
break;
case 3011:
state = 3070;
statedelay = 0;
break;
case 3020:
//Level complete! (Space Station 2)
unlocknum(3, map, dwgfx);
lastsaved = 2;
music.play(0);
state++;
statedelay = 75;
if (dwgfx.flipmode)
{
dwgfx.createtextbox("", -1, 180, 165, 165, 255);
}
else
{
dwgfx.createtextbox("", -1, 12, 165, 165, 255);
}
//dwgfx.addline(" Level Complete! ");
dwgfx.addline(" ");
dwgfx.addline("");
dwgfx.addline("");
dwgfx.textboxcenterx();
/* advancetext = true;
hascontrol = false;
state = 3;
dwgfx.createtextbox("To do: write quick", 50, 80, 164, 164, 255);
dwgfx.addline("intro to story!");*/
break;
case 3021:
state++;
statedelay = 45;
if (dwgfx.flipmode)
{
dwgfx.createtextbox("", -1, 104, 174,175,174);
}
else
{
dwgfx.createtextbox("", -1, 64+8+16, 174,175,174);
}
dwgfx.addline(" You have rescued ");
dwgfx.addline(" a crew member! ");
dwgfx.addline("");
dwgfx.textboxcenterx();
break;
case 3022:
state++;
statedelay = 45;
temp = 6 - crewrescued();
if (temp == 1)
{
tempstring = " One remains ";
if (dwgfx.flipmode)
{
dwgfx.createtextbox(tempstring, -1, 72, 174, 174, 174);
}
else
{
dwgfx.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
}
}
else if (temp > 0)
{
tempstring = " " + help.number(temp) + " remain ";
if (dwgfx.flipmode)
{
dwgfx.createtextbox(tempstring, -1, 72, 174, 174, 174);
}
else
{
dwgfx.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
}
}
else
{
if (dwgfx.flipmode)
{
dwgfx.createtextbox(" All Crew Members Rescued! ", -1, 72, 174, 174, 174);
}
else
{
dwgfx.createtextbox(" All Crew Members Rescued! ", -1, 128+16, 174, 174, 174);
}
}
dwgfx.textboxcenterx();
break;
case 3023:
state++;
statedelay = 0;
if (dwgfx.flipmode)
{
dwgfx.createtextbox(" Press ACTION to continue ", -1, 20, 164, 164, 255);
}
else
{
dwgfx.createtextbox(" Press ACTION to continue ", -1, 196, 164, 164, 255);
}
dwgfx.textboxcenterx();
break;
case 3024:
if (jumppressed)
{
state++;
statedelay = 30;
dwgfx.textboxremove();
}
break;
case 3025:
state = 3070;
statedelay = 0;
break;
case 3040:
//Level complete! (Lab)
unlocknum(1, map, dwgfx);
lastsaved = 5;
music.play(0);
state++;
statedelay = 75;
if (dwgfx.flipmode)
{
dwgfx.createtextbox("", -1, 180, 165, 165, 255);
}
else
{
dwgfx.createtextbox("", -1, 12, 165, 165, 255);
}
//dwgfx.addline(" Level Complete! ");
dwgfx.addline(" ");
dwgfx.addline("");
dwgfx.addline("");
dwgfx.textboxcenterx();
/* advancetext = true;
hascontrol = false;
state = 3;
dwgfx.createtextbox("To do: write quick", 50, 80, 164, 164, 255);
dwgfx.addline("intro to story!");*/
break;
case 3041:
state++;
statedelay = 45;
if (dwgfx.flipmode)
{
dwgfx.createtextbox("", -1, 104, 174,174,175);
}
else
{
dwgfx.createtextbox("", -1, 64+8+16, 174,174,175);
}
dwgfx.addline(" You have rescued ");
dwgfx.addline(" a crew member! ");
dwgfx.addline("");
dwgfx.textboxcenterx();
break;
case 3042:
state++;
statedelay = 45;
temp = 6 - crewrescued();
if (temp == 1)
{
tempstring = " One remains ";
if (dwgfx.flipmode)
{
dwgfx.createtextbox(tempstring, -1, 72, 174, 174, 174);
}
else
{
dwgfx.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
}
}
else if (temp > 0)
{
tempstring = " " + help.number(temp) + " remain ";
if (dwgfx.flipmode)
{
dwgfx.createtextbox(tempstring, -1, 72, 174, 174, 174);
}
else
{
dwgfx.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
}
}
else
{
if (dwgfx.flipmode)
{
dwgfx.createtextbox(" All Crew Members Rescued! ", -1, 72, 174, 174, 174);
}
else
{
dwgfx.createtextbox(" All Crew Members Rescued! ", -1, 128+16, 174, 174, 174);
}
}
dwgfx.textboxcenterx();
break;
case 3043:
state++;
statedelay = 0;
if (dwgfx.flipmode)
{
dwgfx.createtextbox(" Press ACTION to continue ", -1, 20, 164, 164, 255);
}
else
{
dwgfx.createtextbox(" Press ACTION to continue ", -1, 196, 164, 164, 255);
}
dwgfx.textboxcenterx();
break;
case 3044:
if (jumppressed)
{
state++;
statedelay = 30;
dwgfx.textboxremove();
}
break;
case 3045:
state = 3070;
statedelay = 0;
break;
case 3050:
//Level complete! (Space Station 1)
unlocknum(0, map, dwgfx);
lastsaved = 1;
music.play(0);
state++;
statedelay = 75;
if (dwgfx.flipmode)
{
dwgfx.createtextbox("", -1, 180, 165, 165, 255);
}
else
{
dwgfx.createtextbox("", -1, 12, 165, 165, 255);
}
//dwgfx.addline(" Level Complete! ");
dwgfx.addline(" ");
dwgfx.addline("");
dwgfx.addline("");
dwgfx.textboxcenterx();
/* advancetext = true;
hascontrol = false;
state = 3;
dwgfx.createtextbox("To do: write quick", 50, 80, 164, 164, 255);
dwgfx.addline("intro to story!");*/
break;
case 3051:
state++;
statedelay = 45;
if (dwgfx.flipmode)
{
dwgfx.createtextbox("", -1, 104, 175,175,174);
}
else
{
dwgfx.createtextbox("", -1, 64+8+16, 175,175,174);
}
dwgfx.addline(" You have rescued ");
dwgfx.addline(" a crew member! ");
dwgfx.addline("");
dwgfx.textboxcenterx();
break;
case 3052:
state++;
statedelay = 45;
temp = 6 - crewrescued();
if (temp == 1)
{
tempstring = " One remains ";
if (dwgfx.flipmode)
{
dwgfx.createtextbox(tempstring, -1, 72, 174, 174, 174);
}
else
{
dwgfx.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
}
}
else if (temp > 0)
{
tempstring = " " + help.number(temp) + " remain ";
if (dwgfx.flipmode)
{
dwgfx.createtextbox(tempstring, -1, 72, 174, 174, 174);
}
else
{
dwgfx.createtextbox(tempstring, -1, 128+16, 174, 174, 174);
}
}
else
{
if (dwgfx.flipmode)
{
dwgfx.createtextbox(" All Crew Members Rescued! ", -1, 72, 174, 174, 174);
}
else
{
dwgfx.createtextbox(" All Crew Members Rescued! ", -1, 128+16, 174, 174, 174);
}
}
dwgfx.textboxcenterx();
break;
case 3053:
state++;
statedelay = 0;
if (dwgfx.flipmode)
{
dwgfx.createtextbox(" Press ACTION to continue ", -1, 20, 164, 164, 255);
}
else
{
dwgfx.createtextbox(" Press ACTION to continue ", -1, 196, 164, 164, 255);
}
dwgfx.textboxcenterx();
break;
case 3054:
if (jumppressed)
{
state++;
statedelay = 30;
dwgfx.textboxremove();
crewstats[1] = 0; //Set violet's rescue script to 0 to make the next bit easier
teleportscript = "";
}
break;
case 3055:
dwgfx.fademode = 2;
state++;
statedelay = 10;
break;
case 3056:
if(dwgfx.fademode==1)
{
startscript = true;
if (nocutscenes)
{
newscript="bigopenworldskip";
}
else
{
newscript = "bigopenworld";
}
state = 0;
}
break;
case 3060:
//Level complete! (Tower)
unlocknum(2, map, dwgfx);
lastsaved = 3;
music.play(0);
state++;
statedelay = 75;
if (dwgfx.flipmode)
{
dwgfx.createtextbox("", -1, 180, 165, 165, 255);
}
else
{
dwgfx.createtextbox("", -1, 12, 165, 165, 255);
}
//dwgfx.addline(" Level Complete! ");
dwgfx.addline(" ");
dwgfx.addline("");
dwgfx.addline("");
dwgfx.textboxcenterx();
/* advancetext = true;
hascontrol = false;
state = 3;
dwgfx.createtextbox("To do: write quick", 50, 80, 164, 164, 255);
dwgfx.addline("intro to story!");*/
break;
case 3061:
state++;
statedelay = 45;
if (dwgfx.flipmode)
{
dwgfx.createtextbox("", -1, 104, 175,174,175);
}
else
{
dwgfx.createtextbox("", -1, 64+8+16, 175,174,175);
}
dwgfx.addline(" Y
Метки:
CodeSOD
CodeSOD: A Commentary on Military Force |
Once upon a time, Simon worked for a company making software for missile systems. This was serious work, with serious testing procedures, and important standards around documentation.
Shortly before a live-fire test of a missile system, Simon knew better than to make any code changes, but he did want to improve the documentation. Adding comments was pretty low risk, so he went ahead and did that. By the time he was done, the Turbo Pascal code that controlled the missile looked like this:
{ add demands for pitch, yaw and roll, y x and z for this duty cycle }
phidem := phidem + phidemc;
psidem := psidem + psidemc;
rdem := rdem + rdemc / 2; { NOTE: ROLL THRUSTERS MORE EFFICIENT THAN EXPECTED, CUT IT IN HALF
zcdem := zcdem + zdemt[q]; { Add gravity over predicted velocity, from the table }
ycdem := ycdem + ydemc;
xcdem := xcdem + xdemc;
Specifically, it was the "magic number" of 2
that Simon wanted to explain. There was just one problem: he didn't terminate his comment. So the comment continued until the next }
.
When the missile launched, it erupted from the tube, and then traced a perfect parabola into the dirt five meters in front of the launch tube.
It's unclear which was louder: the "thunk" sound of the missile striking the ground or Simon shouting an expletive when he realized his mistake.
https://thedailywtf.com/articles/a-commentary-on-military-force
Метки: CodeSOD |
CodeSOD: Put Down the Pipe |
Camden's team works on an internal Angular application. Angular is constantly releasing new versions, and while they're largely backwards compatible, as the rule goes: every change breaks someone's workflow. Camden's team started to upgrade to Angular 12, only to discover that one of their dependencies wouldn't resolve. It refused to work with any version of Angular greater than 8.
The specific dependency promised to safely sanitize external resources, like DOM snippets and URLs fetched from an external source. At its core, it wrapped around the Angular DomSanitizer
object, which provided all the plumbing for handling sanitization.
So the TypeScript method looked like this:
public safePipe(value: string, type: SafePipeType): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl {
switch (type) {
case 'html':
return this.sanitizer.bypassSecurityTrustHtml(value);
case 'style':
return this.sanitizer.bypassSecurityTrustStyle(value);
case 'script':
return this.sanitizer.bypassSecurityTrustScript(value);
case 'url':
return this.sanitizer.bypassSecurityTrustUrl(value);
case 'resourceUrl':
return this.sanitizer.bypassSecurityTrustResourceUrl(value);
default:
throw new Error(`SafePipe unable to bypass security for invalid type: ${type}`);
}
}
}
As you might guess from a method named bypassSecurityTrust…
, this disables sanitization for that value. If anyone, in the future, calls sanitize
on that value, no sanitization happens. So safePipe
simply disables any security checks.
But let's just go to the Angular docs to see how they feel about this:
In specific situations, it might be necessary to disable sanitization, for example if the application genuinely needs to produce a javascript: style link with a dynamic value in it. Users can bypass security by constructing a value with one of the bypassSecurityTrust... methods, and then binding to that value from the template.
These situations should be very rare, and extraordinary care must be taken to avoid creating a Cross Site Scripting (XSS) security bug!
It is not required (and not recommended) to bypass security if the value is safe, e.g. a URL that does not start with a suspicious protocol, or an HTML snippet that does not contain dangerous code. The sanitizer leaves safe values intact.
Camden described this as the "least-applicable name" he'd seen in awhile.
Метки: CodeSOD |
Error'd: In the fullness of time. |
Regulargle Argle garbles "It's an advertisement. At least... it says so." Hey, if you can't believe advertisements, what can you believe?
Time-traveling Kiwi Paul M. shares an adventure: "Notice the date in the menu bar. Presumably the covid passenger had booked a return sailing." Mumble mumble something international date line iwishalltimestampsincludedthebloodytimezone.
Yodeller Tom H. mutters "I look forward to receiving my parcel in n days time".
Opportunist Jeremy A. is being driven to a state of madness. "Don't worry, I tried entering my phone number too. Of course it says Error: You must enter a valid email address. " Excelsior!
Marc W"urth posts practically parodically "Scott Adam's Dilbert website gave an error on the strip for today. But it could find one for this other day...". I was unable to reproduce this behavior, perhaps because the servers were full.
Метки: Error'd |