The process of modding old games is mostly dead links. Some of these dead links are
behind logins or paywalls. For an upcoming lanparty, we needed teleports
in our map for Soldier of Fortune 2. After some googling and reading documentation,
this post summarizes how we did it.
SOF2 runs the Quake 3 engine, meaning it can be modded using GTKradiant. Radiant’s file
format is .map.
Here’s what we needed to add to our .map-file. Replace the two occurrences of 487 655 30,
with the x y z coordinates of the target of your teleport. The other lines (after brush 0)
are the six planes of the triggering area (the source of the teleport). It’s probably easier
to move this area around using GTKradiant.
The three entities are numbered. The syntax may suggest that this is a comment, but I think it
couldn’t hurt to make it a logical sequence with the rest of your file.
Of course, it’s not necessary to edit the map in a text editor. You could add the same key-value
pairs using the entities menu. This can be opened by pressing N in GTKradiant, while having an object
selected in the interface.
For the source of the teleport, it’s important to use the _trigger texture. Give this the
following key-value pairs:
The Python library bokeh is great for plotting
all kinds of data in the browser. Bokeh includes a serve command, which can
host a document, having a per-user state in the associated Python code. When
creating a visualisation, you can have callback functions in both Python and
JavaScript code. I used this here to sync an HTML video-element to a bokeh line
plot.
/* Select the video with the id frontcamera */varv=document.getElementById("frontcamera");/* If the time of the video element updates, run the callback */v.addEventListener("timeupdate",function(){/* The selector below assumes there are no other input fields in your
code */inputs=document.getElementsByTagName('input');for(index=0;index<inputs.length;++index){/* Update the input field with the new time of the video */inputs[index].value=v.currentTime;/* Trigger a change of the input field, to call the Python code */varevent=newEvent('change',{bubbles:true});inputs[index].dispatchEvent(event);}},true);
The Python code, with the update callback function:
importpandasfrombokeh.layoutsimportrowfrombokeh.modelsimportColumnDataSource,TextInputfrombokeh.plottingimportcurdoc,figuredefupdate(_,old,new):"""The callback function we want to invoke if the time in the video
advances."""# Our data source is in milliseconds, but the callback receives seconds,
# therefore we multiply the input by 1000
new,old=float(new)*1000,float(old)*1000subset=df[(df['ms_since_start']>old)&(df['ms_since_start']<new)]data.stream({'ms':list(subset['ms_since_start']),'z':list(subset['z_int']),'x':list(subset['x_int']),'y':list(subset['y_int']),})# Read in the data using pandas
df=pandas.read_csv('./20190912_041033_EF.acc.csv')# Create a new bokeh figure
p=figure(plot_width=1900,plot_height=400)# Define the columns to plot later
data=ColumnDataSource({'ms':[],'z':[],'x':[],'y':[],})# Plot three lines, that listen to changes in the ColumnDataSource
p.line('ms','z',source=data,color='red')p.line('ms','x',source=data,color='white')p.line('ms','y',source=data,color='lightgreen')# Add a TextInput() that we use to pass on the current time in the video
# This is hacky, and could potentially be done in a nicer way
current_time=TextInput()# If the text field current_time changes, invoke the update callback function
current_time.on_change('value',update)# Add all elements to the bokeh document
curdoc().add_root(row(p))curdoc().add_root(row(current_time))
Although I like this first try at syncing plots, there is still room for
improvement. It would be nicer to have the video element call the
Python function directly, instead of through an input field. This
could be achieved by implementing the video element in bokeh.models.
Currently, the line will not disappear when you rewind the video, or start
it a second time. All these things can be achieved using the
HTML5 media events.
Maybe I’ll make a proper media player for bokeh one day…
TLDR: In this post I show how to take advantage of Netflix delivering your
new subscription before your payment starts. You could do this manually, but
of course
"it’s more fun to compute".
Netflix has an interesting upgrade flow: Once you upgrade, you get the
upgraded plan for the remainder of the billing period. You only
start paying your new fee at the start of the new period.
However, if you upgrade, and downgrade in the same billing period, you’ll
get the upgraded plan for the remainder of the current billing period. At
the start of the new billing period, you’ll be downgraded to your original
plan again. Of course, there’s nothing stopping you from doing the same thing
again. Therefore, if you repeat this every billing period, you can have the
best plan for the lowest price.
This raises a question: “This can’t be intentional, can it?”.
After I submitted a short bug report, Netflix replied that it is indeed
intended behaviour:
We actually received a similar report previously about this one and [decided]
that this is actually an intended functionality.
importloggingfromcollectionsimportnamedtuplefromseleniumimportwebdriverlogger=logging.getLogger(__name__)logger.setLevel(logging.INFO)Configuration=namedtuple('Configuration',['username','password'])config=Configuration(username='username_here',password='password_here')options=webdriver.ChromeOptions()options.binary_location="./headless-chromium"browser=webdriver.Chrome(executable_path='./chromedriver',chrome_options=options)browser.implicitly_wait(time_to_wait=10)browser.get('https://www.netflix.com/ChangePlan')browser.find_element_by_id('id_userLoginId').send_keys(config.username)browser.find_element_by_id('id_password').send_keys(config.password)browser.find_element_by_css_selector('button.btn.login-button.btn-submit.btn-small').click()try:message=browser.find_element_by_css_selector('div.ui-message-contents').textlogging.info('Page contains infobox (probably stating that Netflix ''has already been upgraded this month')logging.info(message)logging.info('Nothing to do left')quit(0)exceptTimeoutError:# The upgrade has not been done this month yet, because there's no
# infobox saying so
current_plan=browser.find_element_by_css_selector('li.selected > div > h2 > div > div > span.plan-name').textlogging.info(f'Currently the {current_plan} is selected')plans=browser.find_elements_by_css_selector('span.plan-name')# Now we click the premium plan (the exact term here may be
# language dependent)
forplaninplans:ifplan.text=='Premium':plan.click()browser.find_element_by_css_selector('button.btn.save-plan-button.btn-blue.btn-small').click()browser.find_element_by_css_selector('button.btn.modal-action-button.btn-blue.btn-small').click()logging.info('Upgraded to Premium')# Now we downgrade to our original plan
browser.get('https://www.netflix.com/ChangePlan')forplaninplans:ifplan.text==current_plan:plan.click()browser.find_element_by_css_selector('button.btn.save-plan-button.btn-blue.btn-small').click()browser.find_element_by_css_selector('button.btn.modal-action-button.btn-blue.btn-small').click()logging.info("Downgraded to the original plan again")
Of course this trick has to be deployed to
AWS Lambda. We can’t be bothered to do
this each month 🙈. I am working on that using Selenium and
serverless Chrome.
Disclaimer: This code may or may not do what you and I expect. Run it at
your own risk. In the worst case, it may actually upgrade your account,
without doing the downgrade.